Skip to content

Commit

Permalink
Merge pull request #920 from input-output-hk/feature/LW-8426-CIP-30-G…
Browse files Browse the repository at this point in the history
…et-extensions

feature/LW-8426 - CIP-30 `getExtensions()`
  • Loading branch information
lgobbi-atix committed Sep 20, 2023
2 parents 1daae5f + c786042 commit 6593cfa
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 10 deletions.
21 changes: 16 additions & 5 deletions packages/dapp-connector/src/WalletApi/Cip30Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { Logger } from 'ts-log';
import { RemoteAuthenticator } from '../AuthenticatorApi';
import uniq from 'lodash/uniq';

type AllowedApiMethods = { allowedApiMethods: WalletMethod[]; enabledExtensions: WalletApiExtension[] };

export const CipMethodsMapping: Record<number, WalletMethod[]> = {
30: [
'getNetworkId',
'getUtxos',
'getCollateral',
'getBalance',
'getExtensions',
'getUsedAddresses',
'getUnusedAddresses',
'getChangeAddress',
Expand All @@ -28,12 +31,16 @@ export const WalletApiMethodNames: WalletMethod[] = Object.values(CipMethodsMapp
*
* Only return the allowed API methods.
*/
const wrapAndEnableApi = (walletApi: WalletApi, allowedApiMethods: WalletMethod[]): WalletApi => {
const wrapAndEnableApi = (
walletApi: WalletApi,
{ allowedApiMethods, enabledExtensions }: AllowedApiMethods
): WalletApi => {
const objectApi: WalletApi = {
getActivePubStakeKeys: () => walletApi.getActivePubStakeKeys(),
getBalance: () => walletApi.getBalance(),
getChangeAddress: () => walletApi.getChangeAddress(),
getCollateral: (params?: { amount?: Cbor }) => walletApi.getCollateral(params),
getExtensions: () => Promise.resolve(enabledExtensions),
getNetworkId: () => walletApi.getNetworkId(),
getPubDRepKey: () => walletApi.getPubDRepKey(),
getRewardAddresses: () => walletApi.getRewardAddresses(),
Expand Down Expand Up @@ -100,9 +107,7 @@ export class Cip30Wallet {
readonly #authenticator: RemoteAuthenticator;

constructor(properties: WalletProperties, { api, authenticator, logger }: WalletDependencies) {
this.enable = this.enable.bind(this);
this.icon = properties.icon;
this.isEnabled = this.isEnabled.bind(this);
this.name = properties.walletName;
this.#api = api;
this.#logger = logger;
Expand All @@ -113,11 +118,17 @@ export class Cip30Wallet {
* Receives the array of extensions provided when `wallet.enable` was called and
* returns a list of methods names for the CIP-30 API and supported extensions.
*/
#getAllowedApiMethods(extensions: WalletApiExtension[] = []): WalletMethod[] {
#getAllowedApiMethods(extensions: WalletApiExtension[] = []): AllowedApiMethods {
const enabledExtensions: WalletApiExtension[] = extensions.filter((extension) =>
this.supportedExtensions.some(({ cip }) => cip === extension.cip)
);
return uniq([...CipMethodsMapping[30], ...enabledExtensions.flatMap(({ cip }) => CipMethodsMapping[cip])]);
return {
allowedApiMethods: uniq([
...CipMethodsMapping[30],
...enabledExtensions.flatMap(({ cip }) => CipMethodsMapping[cip])
]),
enabledExtensions
};
}

#validateExtensions(extensions: WalletApiExtension[] = []): void {
Expand Down
14 changes: 12 additions & 2 deletions packages/dapp-connector/src/WalletApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface Cip30DataSignature {
signature: CoseSign1CborHex;
}

export type WalletApiExtension = { cip: number };

/**
* Returns the network id of the currently connected account.
* 0 is testnet and 1 is mainnet but other networks can possibly be returned by wallets.
Expand Down Expand Up @@ -78,6 +80,14 @@ export type GetCollateral = (params?: { amount?: Cbor }) => Promise<Cbor[] | nul
*/
export type GetBalance = () => Promise<Cbor>;

/**
* Retrieves the list of extensions enabled by the wallet.
* This may be influenced by the set of extensions requested in the initial enable request.
*
* @throws ApiError
*/
export type GetExtensions = () => Promise<WalletApiExtension[]>;

/**
* Returns a list of all used (included in some on-chain transaction) addresses controlled by the wallet.
*
Expand Down Expand Up @@ -172,6 +182,8 @@ export interface Cip30WalletApi {

getCollateral: GetCollateral;

getExtensions: GetExtensions;

getUsedAddresses: GetUsedAddresses;

getUnusedAddresses: GetUnusedAddresses;
Expand All @@ -196,5 +208,3 @@ export interface Cip95WalletApi {
export type WalletApi = Cip30WalletApi & Cip95WalletApi;

export type WalletMethod = keyof WalletApi;

export type WalletApiExtension = { cip: number };
14 changes: 14 additions & 0 deletions packages/dapp-connector/test/WalletApi/Cip30Wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('Wallet', () => {
const methods = new Set(Object.keys(api));
expect(methods).toEqual(new Set([...CipMethodsMapping[30], 'experimental']));
expect(await wallet.isEnabled()).toBe(true);
expect(await api.getExtensions()).toEqual([]);
});

test('with cip95 extension', async () => {
Expand All @@ -57,18 +58,21 @@ describe('Wallet', () => {
const methods = new Set(Object.keys(api));
expect(methods).toEqual(new Set([...CipMethodsMapping[30], ...CipMethodsMapping[95], 'experimental']));
expect(await wallet.isEnabled()).toBe(true);
expect(await api.getExtensions()).toEqual([{ cip: 95 }]);
});

test('change extensions after enabling once', async () => {
const cip30api = await wallet.enable();
const cip30methods = new Set(Object.keys(cip30api));
expect(cip30methods).toEqual(new Set([...CipMethodsMapping[30], 'experimental']));
expect(await wallet.isEnabled()).toBe(true);
expect(await cip30api.getExtensions()).toEqual([]);

const cip95api = await wallet.enable({ extensions: [{ cip: 95 }] });
const cip95methods = new Set(Object.keys(cip95api));
expect(cip95methods).toEqual(new Set([...CipMethodsMapping[30], ...CipMethodsMapping[95], 'experimental']));
expect(await wallet.isEnabled()).toBe(true);
expect(await cip95api.getExtensions()).toEqual([{ cip: 95 }]);
});

test('unsupported extensions does not reject and returns cip30 methods', async () => {
Expand All @@ -77,6 +81,7 @@ describe('Wallet', () => {
const methods = new Set(Object.keys(api));
expect(methods).toEqual(new Set([...CipMethodsMapping[30], 'experimental']));
expect(await wallet.isEnabled()).toBe(true);
expect(await api.getExtensions()).toEqual([]);
});

test('empty enable options does not reject and returns cip30 methods', async () => {
Expand All @@ -85,6 +90,7 @@ describe('Wallet', () => {
const methods = new Set(Object.keys(api));
expect(methods).toEqual(new Set([...CipMethodsMapping[30], 'experimental']));
expect(await wallet.isEnabled()).toBe(true);
expect(await api.getExtensions()).toEqual([]);
});

test('throws if access to wallet api is not authorized', async () => {
Expand Down Expand Up @@ -234,5 +240,13 @@ describe('Wallet', () => {
const txId = await api.submitTx('tx');
expect(txId).toEqual('transactionId');
});

test('getExtensions', async () => {
expect(api.getExtensions).toBeDefined();
expect(typeof api.getExtensions).toBe('function');

const extensions = await api.getExtensions();
expect(extensions).toEqual([]);
});
});
});
6 changes: 4 additions & 2 deletions packages/dapp-connector/test/injectGlobal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ describe('injectGlobal', () => {
expect(window.cardano[properties.walletName].name).toBe(properties.walletName);
expect(typeof window.cardano[properties.walletName].apiVersion).toBe('string');
expect(window.cardano[properties.walletName].icon).toBe(properties.icon);
expect(window.cardano[properties.walletName].isEnabled).toBeDefined();
expect(typeof window.cardano[properties.walletName].isEnabled).toBe('function');
expect(window.cardano[properties.walletName].enable).toBeDefined();
expect(typeof window.cardano[properties.walletName].enable).toBe('function');
expect(Object.keys(window.cardano[properties.walletName])).toEqual([
'apiVersion',
'supportedExtensions',
'enable',
'icon',
'isEnabled',
'name'
]);
expect(window.cardano['another-obj']).toBe(anotherObj);
Expand Down
1 change: 1 addition & 0 deletions packages/dapp-connector/test/testWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const api = <WalletApi>{
getBalance: async () => '100',
getChangeAddress: async () => 'change-address',
getCollateral: async () => null,
getExtensions: async () => [{ cip: 95 }],
getNetworkId: async () => 0,
getPubDRepKey: async () => 'getPubDRepKey' as Ed25519PublicKeyHex,
getRewardAddresses: async () => ['reward-address-1', 'reward-address-2'],
Expand Down
1 change: 1 addition & 0 deletions packages/e2e/test/web-extension/extension/stubWalletApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const stubWalletApi: WalletApi = {
}
]
]),
getExtensions: async () => [{ cip: 95 }],
getNetworkId: async () => 0,
getPubDRepKey: async () => Ed25519PublicKeyHex('deeb8f82f2af5836ebbc1b450b6dbf0b03c93afe5696f10d49e8a8304ebfac01'),
getRewardAddresses: async () => ['stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27'],
Expand Down
7 changes: 6 additions & 1 deletion packages/wallet/src/cip30.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
TxSendErrorCode,
TxSignError,
TxSignErrorCode,
WalletApi
WalletApi,
WalletApiExtension
} from '@cardano-sdk/dapp-connector';
import { Cardano, Serialization, TxCBOR, coalesceValueQuantities } from '@cardano-sdk/core';
import { HexBlob, ManagedFreeableScope } from '@cardano-sdk/util';
Expand Down Expand Up @@ -338,6 +339,10 @@ const baseCip30WalletApi = (
}
return unspendables.map((core) => Serialization.TransactionUnspentOutput.fromCore(core).toCbor());
},
getExtensions: async (): Promise<WalletApiExtension[]> => {
logger.debug('getting enabled extensions');
return Promise.resolve([{ cip: 95 }]);
},
getNetworkId: async (): Promise<Cardano.NetworkId> => {
logger.debug('getting networkId');
const wallet = await firstValueFrom(wallet$);
Expand Down
5 changes: 5 additions & 0 deletions packages/wallet/test/integration/cip30mapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,11 @@ describe('cip30', () => {
expect.assertions(3);
});
});

test('api.getExtensions', async () => {
const extensions = await api.getExtensions();
expect(extensions).toEqual([{ cip: 95 }]);
});
});

describe('confirmation callbacks', () => {
Expand Down

0 comments on commit 6593cfa

Please sign in to comment.