From 944e0cea55bcd8c91e1888e708e717adc7b1ea4b Mon Sep 17 00:00:00 2001 From: lgobbi-atix Date: Tue, 19 Sep 2023 10:46:36 -0300 Subject: [PATCH 1/2] feat: add getExtensions() to CIP-30 wallet API ref: LW-8426 --- .../src/WalletApi/Cip30Wallet.ts | 19 ++++++++++++++++--- .../dapp-connector/src/WalletApi/types.ts | 14 ++++++++++++-- .../test/WalletApi/Cip30Wallet.test.ts | 14 ++++++++++++++ packages/dapp-connector/test/testWallet.ts | 1 + .../web-extension/extension/stubWalletApi.ts | 1 + packages/wallet/src/cip30.ts | 7 ++++++- .../test/integration/cip30mapping.test.ts | 5 +++++ 7 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts b/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts index 7e4a2b467ef..3b79ad9c701 100644 --- a/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts +++ b/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts @@ -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 = { 30: [ 'getNetworkId', 'getUtxos', 'getCollateral', 'getBalance', + 'getExtensions', 'getUsedAddresses', 'getUnusedAddresses', 'getChangeAddress', @@ -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(), @@ -113,11 +120,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 { diff --git a/packages/dapp-connector/src/WalletApi/types.ts b/packages/dapp-connector/src/WalletApi/types.ts index c0739ac02f3..e1f5aad2239 100644 --- a/packages/dapp-connector/src/WalletApi/types.ts +++ b/packages/dapp-connector/src/WalletApi/types.ts @@ -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. @@ -78,6 +80,14 @@ export type GetCollateral = (params?: { amount?: Cbor }) => Promise Promise; +/** + * 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; + /** * Returns a list of all used (included in some on-chain transaction) addresses controlled by the wallet. * @@ -169,6 +179,8 @@ export interface Cip30WalletApi { getCollateral: GetCollateral; + getExtensions: GetExtensions; + getUsedAddresses: GetUsedAddresses; getUnusedAddresses: GetUnusedAddresses; @@ -193,5 +205,3 @@ export interface Cip95WalletApi { export type WalletApi = Cip30WalletApi & Cip95WalletApi; export type WalletMethod = keyof WalletApi; - -export type WalletApiExtension = { cip: number }; diff --git a/packages/dapp-connector/test/WalletApi/Cip30Wallet.test.ts b/packages/dapp-connector/test/WalletApi/Cip30Wallet.test.ts index d5545b4410d..fa14048f1ca 100644 --- a/packages/dapp-connector/test/WalletApi/Cip30Wallet.test.ts +++ b/packages/dapp-connector/test/WalletApi/Cip30Wallet.test.ts @@ -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 () => { @@ -57,6 +58,7 @@ 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 () => { @@ -64,11 +66,13 @@ describe('Wallet', () => { 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 () => { @@ -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 () => { @@ -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 () => { @@ -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([]); + }); }); }); diff --git a/packages/dapp-connector/test/testWallet.ts b/packages/dapp-connector/test/testWallet.ts index ab9a87c1621..b3f74bdc201 100644 --- a/packages/dapp-connector/test/testWallet.ts +++ b/packages/dapp-connector/test/testWallet.ts @@ -8,6 +8,7 @@ export const api = { 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'], diff --git a/packages/e2e/test/web-extension/extension/stubWalletApi.ts b/packages/e2e/test/web-extension/extension/stubWalletApi.ts index 4fdd198da83..ecabf012e1c 100644 --- a/packages/e2e/test/web-extension/extension/stubWalletApi.ts +++ b/packages/e2e/test/web-extension/extension/stubWalletApi.ts @@ -29,6 +29,7 @@ export const stubWalletApi: WalletApi = { } ] ]), + getExtensions: async () => [{ cip: 95 }], getNetworkId: async () => 0, getPubDRepKey: async () => Ed25519PublicKeyHex('deeb8f82f2af5836ebbc1b450b6dbf0b03c93afe5696f10d49e8a8304ebfac01'), getRewardAddresses: async () => ['stake_test1uqfu74w3wh4gfzu8m6e7j987h4lq9r3t7ef5gaw497uu85qsqfy27'], diff --git a/packages/wallet/src/cip30.ts b/packages/wallet/src/cip30.ts index 5fcbed024fe..e9de5cce175 100644 --- a/packages/wallet/src/cip30.ts +++ b/packages/wallet/src/cip30.ts @@ -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'; @@ -338,6 +339,10 @@ const baseCip30WalletApi = ( } return unspendables.map((core) => Serialization.TransactionUnspentOutput.fromCore(core).toCbor()); }, + getExtensions: async (): Promise => { + logger.debug('getting enabled extensions'); + return Promise.resolve([{ cip: 95 }]); + }, getNetworkId: async (): Promise => { logger.debug('getting networkId'); const wallet = await firstValueFrom(wallet$); diff --git a/packages/wallet/test/integration/cip30mapping.test.ts b/packages/wallet/test/integration/cip30mapping.test.ts index 669d1faf27a..f83f6975072 100644 --- a/packages/wallet/test/integration/cip30mapping.test.ts +++ b/packages/wallet/test/integration/cip30mapping.test.ts @@ -534,6 +534,11 @@ describe('cip30', () => { expect.assertions(3); }); }); + + test('api.getExtensions', async () => { + const extensions = await api.getExtensions(); + expect(extensions).toEqual([{ cip: 95 }]); + }); }); describe('confirmation callbacks', () => { From c786042862b1577f409fbab8b83b8b8a05a2d1bc Mon Sep 17 00:00:00 2001 From: lgobbi-atix Date: Tue, 19 Sep 2023 11:05:27 -0300 Subject: [PATCH 2/2] fix(dapp-connector): remove unnecessary bindings for CIP-30 initial API methods --- packages/dapp-connector/src/WalletApi/Cip30Wallet.ts | 2 -- packages/dapp-connector/test/injectGlobal.test.ts | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts b/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts index 3b79ad9c701..399f8613ac3 100644 --- a/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts +++ b/packages/dapp-connector/src/WalletApi/Cip30Wallet.ts @@ -107,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; diff --git a/packages/dapp-connector/test/injectGlobal.test.ts b/packages/dapp-connector/test/injectGlobal.test.ts index bc5930e4d9e..2db226db0e5 100644 --- a/packages/dapp-connector/test/injectGlobal.test.ts +++ b/packages/dapp-connector/test/injectGlobal.test.ts @@ -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);