Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add CIP142 support to dapp-connector #1607

Merged
merged 1 commit into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/dapp-connector/src/WalletApi/Cip30Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { APIErrorCode, ApiError } from '../errors';
import {
Bytes,
Cbor,
Cip142WalletApi,
Cip30WalletApiWithPossibleExtensions,
Cip95WalletApi,
CipExtensionApis,
Paginate,
WalletApi,
Expand All @@ -28,7 +30,8 @@ export const CipMethodsMapping: Record<number, WalletMethod[]> = {
'signData',
'submitTx'
],
95: ['getRegisteredPubStakeKeys', 'getUnregisteredPubStakeKeys', 'getPubDRepKey', 'signData']
95: ['getRegisteredPubStakeKeys', 'getUnregisteredPubStakeKeys', 'getPubDRepKey', 'signData'],
142: ['getNetworkMagic']
};
export const WalletApiMethodNames: WalletMethod[] = Object.values(CipMethodsMapping).flat();

Expand Down Expand Up @@ -79,7 +82,7 @@ export class Cip30Wallet {
readonly name: WalletName;
readonly icon: WalletIcon;
/** Support the full api by default */
readonly supportedExtensions: WalletApiExtension[] = [{ cip: 95 }];
readonly supportedExtensions: WalletApiExtension[] = [{ cip: 95 }, { cip: 142 }];

readonly #logger: Logger;
readonly #api: WalletApi;
Expand Down Expand Up @@ -195,14 +198,17 @@ export class Cip30Wallet {
getUnregisteredPubStakeKeys: () => walletApi.getUnregisteredPubStakeKeys(),
signData: (addr: Cardano.PaymentAddress | Cardano.RewardAccount | Bytes, payload: Bytes) =>
walletApi.signData(addr, payload)
},
cip142: {
getNetworkMagic: () => walletApi.getNetworkMagic()
}
};

if (enabledExtensions) {
for (const extension of enabledExtensions) {
const cipName = `cip${extension.cip}` as keyof CipExtensionApis;
if (additionalCipApis[cipName]) {
baseApi[cipName] = additionalCipApis[cipName];
baseApi[cipName] = additionalCipApis[cipName] as Cip95WalletApi & Cip142WalletApi;
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion packages/dapp-connector/src/WalletApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,16 @@ export interface Cip95WalletApi {
signData: SignData;
}

export type WalletApi = Cip30WalletApi & Cip95WalletApi;
export interface Cip142WalletApi {
getNetworkMagic: () => Promise<Cardano.NetworkMagics>;
}

export type WalletApi = Cip30WalletApi & Cip95WalletApi & Cip142WalletApi;
export type WalletMethod = keyof WalletApi;

export interface CipExtensionApis {
cip95: Cip95WalletApi;
cip142: Cip142WalletApi;
}

export type Cip30WalletApiWithPossibleExtensions = Cip30WalletApi & Partial<CipExtensionApis>;
Expand Down
38 changes: 32 additions & 6 deletions packages/dapp-connector/test/WalletApi/Cip30Wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('Wallet', () => {
expect(wallet.apiVersion).toBe('0.1.0');
expect(typeof wallet.name).toBe('string');
expect(wallet.name).toBe(testWallet.properties.walletName);
expect(wallet.supportedExtensions).toEqual<WalletApiExtension[]>([{ cip: 95 }]);
expect(wallet.supportedExtensions).toEqual<WalletApiExtension[]>([{ cip: 95 }, { cip: 142 }]);
expect(typeof wallet.isEnabled).toBe('function');
const isEnabled = await wallet.isEnabled();
expect(typeof isEnabled).toBe('boolean');
Expand Down Expand Up @@ -98,6 +98,30 @@ describe('Wallet', () => {
expect(await api.getExtensions()).toEqual([{ cip: 95 }]);
});

test('with cip142 extension', async () => {
const api = await wallet.enable({ extensions: [{ cip: 142 }] });
expect(typeof api).toBe('object');
const methods = new Set(Object.keys(api));
expect(methods).toEqual(new Set([...CipMethodsMapping[30], 'cip142', 'experimental']));
const cip142Methods = new Set(Object.keys(api.cip142!));
expect(cip142Methods).toEqual(new Set(CipMethodsMapping[142]));
expect(await wallet.isEnabled()).toBe(true);
expect(await api.getExtensions()).toEqual([{ cip: 142 }]);
});

test('with cip95 and cip142 extensions', async () => {
const api = await wallet.enable({ extensions: [{ cip: 95 }, { cip: 142 }] });
expect(typeof api).toBe('object');
const methods = new Set(Object.keys(api));
expect(methods).toEqual(new Set([...CipMethodsMapping[30], 'cip95', 'cip142', 'experimental']));
const cip95Methods = new Set(Object.keys(api.cip95!));
expect(cip95Methods).toEqual(new Set(CipMethodsMapping[95]));
const cip142Methods = new Set(Object.keys(api.cip142!));
expect(cip142Methods).toEqual(new Set(CipMethodsMapping[142]));
expect(await wallet.isEnabled()).toBe(true);
expect(await api.getExtensions()).toEqual([{ cip: 95 }, { cip: 142 }]);
});

test('no extensions wallet cannot enable cip95 extension', async () => {
const api = await walletNoExtensions.enable({ extensions: [{ cip: 95 }] });
expect(await walletNoExtensions.isEnabled()).toBe(true);
Expand All @@ -112,13 +136,15 @@ describe('Wallet', () => {
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], 'cip95', 'experimental']));
const cip95InnerMethods = new Set(Object.keys(cip95api.cip95!));
const apiWithExtension = await wallet.enable({ extensions: [{ cip: 95 }, { cip: 142 }] });
const cip95methods = new Set(Object.keys(apiWithExtension));
expect(cip95methods).toEqual(new Set([...CipMethodsMapping[30], 'cip95', 'cip142', 'experimental']));
const cip95InnerMethods = new Set(Object.keys(apiWithExtension.cip95!));
expect(cip95InnerMethods).toEqual(new Set(CipMethodsMapping[95]));
const cip142Methods = new Set(Object.keys(apiWithExtension.cip142!));
expect(cip142Methods).toEqual(new Set(CipMethodsMapping[142]));
expect(await wallet.isEnabled()).toBe(true);
expect(await cip95api.getExtensions()).toEqual([{ cip: 95 }]);
expect(await apiWithExtension.getExtensions()).toEqual([{ cip: 95 }, { cip: 142 }]);
});

test('unsupported extensions does not reject and returns cip30 methods', async () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/dapp-connector/test/testWallet.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Cardano, Serialization } from '@cardano-sdk/core';
import { Cip30DataSignature, WalletApi, WalletProperties } from '../src/WalletApi';
import { Cip30DataSignature, RemoteAuthenticator, WalletApi, WalletProperties } from '../src';
import { Ed25519PublicKeyHex } from '@cardano-sdk/crypto';
import { RemoteAuthenticator } from '../src';

export const api = <WalletApi>{
getBalance: async () => '100',
getChangeAddress: async () => 'change-address',
getCollateral: async () => null,
getExtensions: async () => [{ cip: 95 }],
getNetworkId: async () => 0,
getNetworkMagic: async () => Cardano.NetworkMagics.Preprod,
getPubDRepKey: async () => 'getPubDRepKey' as Ed25519PublicKeyHex,
getRegisteredPubStakeKeys: async () =>
['registeredPubStakeKey-1', 'registeredPubStakeKey-2'] as Ed25519PublicKeyHex[],
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e/test/web-extension/extension/stubWalletApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export const stubWalletApi: WalletApi = {
}
]
]),
getExtensions: async () => [{ cip: 95 }],
getExtensions: async () => [{ cip: 95 }, { cip: 142 }],
getNetworkId: async () => 0,
getNetworkMagic: async () => Cardano.NetworkMagics.Mainnet,
getPubDRepKey: async () => Ed25519PublicKeyHex('deeb8f82f2af5836ebbc1b450b6dbf0b03c93afe5696f10d49e8a8304ebfac01'),
getRegisteredPubStakeKeys: async () => [
Ed25519PublicKeyHex('deeb8f82f2af5836ebbc1b450b6dbf0b03c93afe5696f10d49e8a8304ebfac01')
Expand Down
20 changes: 19 additions & 1 deletion packages/wallet/src/cip30.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ApiError,
Bytes,
Cbor,
Cip142WalletApi,
Cip30DataSignature,
Cip95WalletApi,
DataSignError,
Expand Down Expand Up @@ -573,11 +574,28 @@ const extendedCip95WalletApi = (
}
});

const extendedCip142WalletApi = (
wallet$: Observable<ObservableWallet>,
{ logger }: Cip30WalletDependencies
): Cip142WalletApi => ({
getNetworkMagic: async () => {
try {
const wallet = await firstValueFrom(wallet$);
const genesisParameters = await firstValueFrom(wallet.genesisParameters$);
return genesisParameters.networkMagic;
} catch (error) {
logger.error(error);
throw new ApiError(APIErrorCode.InternalError, formatUnknownError(error));
}
}
});

export const createWalletApi = (
wallet$: Observable<ObservableWallet>,
confirmationCallback: CallbackConfirmation,
{ logger }: Cip30WalletDependencies
): WithSenderContext<WalletApi> => ({
...baseCip30WalletApi(wallet$, confirmationCallback, { logger }),
...extendedCip95WalletApi(wallet$, { logger })
...extendedCip95WalletApi(wallet$, { logger }),
...extendedCip142WalletApi(wallet$, { logger })
});
7 changes: 7 additions & 0 deletions packages/wallet/test/integration/cip30mapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,13 @@ describe('cip30', () => {
const extensions = await api.getExtensions(context);
expect(extensions).toEqual([{ cip: 95 }]);
});

describe('api.getNetworkMagic', () => {
it('returns the network magic', async () => {
const networkMagic = await api.getNetworkMagic(context);
expect(networkMagic).toEqual(Cardano.NetworkMagics.Mainnet);
});
});
});

describe('confirmation callbacks', () => {
Expand Down
4 changes: 3 additions & 1 deletion yarn-project.nix
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ cacheEntries = {
"@emurgo/cip14-js@npm:3.0.1" = { filename = "@emurgo-cip14-js-npm-3.0.1-6011030ea2-9eaf312410.zip"; sha512 = "9eaf3124108e8c252a745de9ef1f334ab26a32271077b00fe0ea2a06e40838dd435165dac523ebd4d851ae7a94d8c56766dabc372aabffedd36551c798c607c5"; };
"@endemolshinegroup/cosmiconfig-typescript-loader@npm:3.0.2" = { filename = "@endemolshinegroup-cosmiconfig-typescript-loader-npm-3.0.2-97436e68fc-7fe0198622.zip"; sha512 = "7fe0198622b1063c40572034df7e8ba867865a1b4815afe230795929abcf785758b34d7806a8e2100ba8ab4e92c5a1c3e11a980c466c4406df6e7ec6e50df8b6"; };
"@es-joy/jsdoccomment@npm:0.10.8" = { filename = "@es-joy-jsdoccomment-npm-0.10.8-d03c65b162-3e144ef393.zip"; sha512 = "3e144ef393459a541b64f6c9c8e62fb6d9b47e1a2c626410487ede12c472064f6ce6e0911df60b42ccf126d5a66102707eef59ca14767cb7aeb5e608b227558d"; };
"@esbuild/linux-x64@npm:0.21.5" = { filename = "@esbuild-linux-x64-npm-0.21.5-88079726c4-8.zip"; sha512 = "91c202dca064909b2c56522f98e3a3b24bc5d43405506b4e67923ecb5d0cc2b78dcee8d815f705d71395402f8532670a391777a3cf6a08894049e453becf07a0"; };
"@esbuild/darwin-arm64@npm:0.21.5" = { filename = "@esbuild-darwin-arm64-npm-0.21.5-62349c1520-8.zip"; sha512 = "50d5d633be3d0fe0fce54c4740171ae6d2e8f5220280a6f6996f234c718de25535e50a31cee1745b5b80f2cc9e336c42c7fc2b49f3ea38b5f3ff5d8c97ef4123"; };
"@eslint/eslintrc@npm:0.4.3" = { filename = "@eslint-eslintrc-npm-0.4.3-ee1bbcab87-03a7704150.zip"; sha512 = "03a7704150b868c318aab6a94d87a33d30dc2ec579d27374575014f06237ba1370ae11178db772f985ef680d469dc237e7b16a1c5d8edaaeb8c3733e7a95a6d3"; };
"@ethereumjs/common@npm:4.4.0" = { filename = "@ethereumjs-common-npm-4.4.0-ee991f5124-6b8cbfcfb5.zip"; sha512 = "6b8cbfcfb5bdde839545c89dce3665706733260e26455d0eb3bcbc3c09e371ae629d51032b95d86f2aeeb15325244a6622171f9005165266fefd923eaa99f1c5"; };
"@ethereumjs/rlp@npm:5.0.2" = { filename = "@ethereumjs-rlp-npm-5.0.2-72fb389b37-b569061ddb.zip"; sha512 = "b569061ddb1f4cf56a82f7a677c735ba37f9e94e2bbaf567404beb9e2da7aa1f595e72fc12a17c61f7aec67fd5448443efe542967c685a2fe0ffc435793dcbab"; };
Expand Down Expand Up @@ -1461,6 +1461,8 @@ cacheEntries = {
"fs.realpath@npm:1.0.0" = { filename = "fs.realpath-npm-1.0.0-c8f05d8126-99ddea01a7.zip"; sha512 = "99ddea01a7e75aa276c250a04eedeffe5662bce66c65c07164ad6264f9de18fb21be9433ead460e54cff20e31721c811f4fb5d70591799df5f85dce6d6746fd0"; };
"fsevents@npm:2.3.2" = { filename = "fsevents-npm-2.3.2-a881d6ac9f-97ade64e75.zip"; sha512 = "97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f"; };
"fsevents@npm:2.3.3" = { filename = "fsevents-npm-2.3.3-ce9fb0ffae-11e6ea6fea.zip"; sha512 = "11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317"; };
"fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7" = { filename = "fsevents-patch-3340e2eb10-8.zip"; sha512 = "edbd0fd80be379c14409605f77e52fdc78a119e17f875e8b90a220c3e5b29e54a1477c21d91fd30b957ea4866406dc3ff87b61432d2840ff8866b309e5866140"; };
"fsevents@patch:fsevents@npm%3A2.3.3#~builtin<compat/fsevents>::version=2.3.3&hash=18f3a7" = { filename = "fsevents-patch-7934e3c202-8.zip"; sha512 = "4639e24e2774cbd3669bd08521e0eeeb9d05bbabffdfdee418cc75a237660bc2fb30520a266ad5379199e2d657f430dd4236ad3642674ef32f20cc7258506725"; };
"ftp@npm:0.3.10" = { filename = "ftp-npm-0.3.10-348fb9ac23-ddd313c1d4.zip"; sha512 = "ddd313c1d44eb7429f3a7d77a0155dc8fe86a4c64dca58f395632333ce4b4e74c61413c6e0ef66ea3f3d32d905952fbb6d028c7117d522f793eb1fa282e17357"; };
"function-bind@npm:1.1.1" = { filename = "function-bind-npm-1.1.1-b56b322ae9-b32fbaebb3.zip"; sha512 = "b32fbaebb3f8ec4969f033073b43f5c8befbb58f1a79e12f1d7490358150359ebd92f49e72ff0144f65f2c48ea2a605bff2d07965f548f6474fd8efd95bf361a"; };
"function.prototype.name@npm:1.1.5" = { filename = "function.prototype.name-npm-1.1.5-e776a642bb-acd21d733a.zip"; sha512 = "acd21d733a9b649c2c442f067567743214af5fa248dbeee69d8278ce7df3329ea5abac572be9f7470b4ec1cd4d8f1040e3c5caccf98ebf2bf861a0deab735c27"; };
Expand Down
Loading