diff --git a/assets/wallets/wallet-connect-logo.svg b/assets/wallets/wallet-connect-logo.svg
new file mode 100644
index 00000000..1c1d9629
--- /dev/null
+++ b/assets/wallets/wallet-connect-logo.svg
@@ -0,0 +1,11 @@
+
diff --git a/examples/demo-inkv5/.eslintrc.cjs b/examples/demo-inkv5/.eslintrc.cjs
index c03c28c9..6ba48c34 100644
--- a/examples/demo-inkv5/.eslintrc.cjs
+++ b/examples/demo-inkv5/.eslintrc.cjs
@@ -1,19 +1,13 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:react-hooks/recommended',
- ],
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended'],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- "@typescript-eslint/no-explicit-any": "off"
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unused-expressions': 'off',
},
-}
+};
diff --git a/examples/demo-inkv5/src/main.tsx b/examples/demo-inkv5/src/main.tsx
index 740fd7ac..18d3ce3a 100644
--- a/examples/demo-inkv5/src/main.tsx
+++ b/examples/demo-inkv5/src/main.tsx
@@ -6,26 +6,25 @@ import App from '@/App';
import { theme } from '@/theme';
import { deployments } from '@/contracts/deployments';
import {
- alephZeroTestnet,
- development,
+ alephZero,
ExtensionWallet,
polkadotjs,
- popTestnet,
ReactToastifyAdapter,
setupTxToaster,
subwallet,
talisman,
TypinkProvider,
+ walletConnect,
} from 'typink';
import { toast } from 'react-toastify';
setupTxToaster({ adapter: new ReactToastifyAdapter(toast) });
const DEFAULT_CALLER = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; // Alice
-const SUPPORTED_NETWORK = [popTestnet, alephZeroTestnet];
-if (process.env.NODE_ENV === 'development') {
- SUPPORTED_NETWORK.push(development);
-}
+const SUPPORTED_NETWORK = [alephZero];
+// if (process.env.NODE_ENV === 'development') {
+// SUPPORTED_NETWORK.push(development);
+// }
const enkrypt = new ExtensionWallet({
name: 'Enkrypt',
@@ -43,9 +42,9 @@ function Root() {
deployments={deployments}
defaultCaller={DEFAULT_CALLER}
supportedNetworks={SUPPORTED_NETWORK}
- defaultNetworkId={popTestnet.id}
+ defaultNetworkId={alephZero.id}
cacheMetadata={true}
- wallets={[subwallet, talisman, polkadotjs, enkrypt]}>
+ wallets={[subwallet, talisman, polkadotjs, enkrypt, walletConnect]}>
{
+export const disconnectWalletAtom = atom(null, async (get, set, walletId?: string) => {
const walletIds = get(connectedWalletIdsAtom);
const connectedAccount = get(connectedAccountAtom);
@@ -100,6 +100,11 @@ export const disconnectWalletAtom = atom(null, (get, set, walletId?: string) =>
const connectionAtom = walletConnectionsAtomFamily(walletId);
const connection = get(connectionAtom);
+ // Clean up WalletConnect session if needed
+ if (connection?.wallet instanceof WalletConnect) {
+ await connection.wallet.disconnect();
+ }
+
// Clean up subscription
if (connection?.subscription) {
connection.subscription();
@@ -118,14 +123,20 @@ export const disconnectWalletAtom = atom(null, (get, set, walletId?: string) =>
}
} else {
// Disconnect all wallets
- walletIds.forEach((id) => {
+ for (const id of walletIds) {
const connectionAtom = walletConnectionsAtomFamily(id);
const connection = get(connectionAtom);
+
+ // Clean up WalletConnect session if needed
+ if (connection?.wallet instanceof WalletConnect) {
+ await connection.wallet.disconnect();
+ }
+
if (connection?.subscription) {
connection.subscription();
}
set(connectionAtom, null);
- });
+ }
set(connectedWalletIdsAtom, []);
set(connectedAccountAtom, undefined);
diff --git a/packages/typink/src/networks/mainnet.ts b/packages/typink/src/networks/mainnet.ts
index 7aaafd2d..4d6aaee7 100644
--- a/packages/typink/src/networks/mainnet.ts
+++ b/packages/typink/src/networks/mainnet.ts
@@ -11,6 +11,7 @@ export const alephZero: NetworkInfo = {
decimals: 12,
jsonRpcApi: JsonRpcApi.LEGACY,
subscanUrl: 'https://alephzero.subscan.io',
+ genesisHash: '0x70255b4d28de0fc4e1a193d7e175ad1ccef431598211c55538f1018651a0307e',
};
export const astar: NetworkInfo = {
@@ -22,6 +23,7 @@ export const astar: NetworkInfo = {
symbol: 'ASTR',
decimals: 18,
subscanUrl: 'https://astar.subscan.io',
+ genesisHash: '0x9eb76c5184c4ab8679d2d5d819fdf90b9c001403e9e17da2e14b6d8aec4029c6',
};
export const shiden: NetworkInfo = {
@@ -33,6 +35,7 @@ export const shiden: NetworkInfo = {
symbol: 'SDN',
decimals: 18,
subscanUrl: 'https://shiden.subscan.io',
+ genesisHash: '0xf1cf9022c7ebb34b162d5b5e34e705a5a740b2d0ecc1009fb89023e62a488108',
};
export const polkadot: NetworkInfo = {
@@ -60,6 +63,7 @@ export const polkadot: NetworkInfo = {
chainSpec: async () => {
return (await import('@substrate/connect-known-chains/polkadot')).chainSpec;
},
+ genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
};
export const polkadotAssetHub: NetworkInfo = {
@@ -85,6 +89,7 @@ export const polkadotAssetHub: NetworkInfo = {
return (await import('@substrate/connect-known-chains/polkadot_asset_hub')).chainSpec;
},
relayChain: polkadot,
+ genesisHash: '0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f',
};
export const polkadotPeople: NetworkInfo = {
@@ -106,6 +111,7 @@ export const polkadotPeople: NetworkInfo = {
return (await import('@substrate/connect-known-chains/polkadot_people')).chainSpec;
},
relayChain: polkadot,
+ genesisHash: '0x67fa177a097bfa18f77ea95ab56e9bcdfeb0e5b8a40e46298bb93e16b6fc5008',
};
export const kusama: NetworkInfo = {
@@ -130,6 +136,7 @@ export const kusama: NetworkInfo = {
chainSpec: async () => {
return (await import('@substrate/connect-known-chains/ksmcc3')).chainSpec;
},
+ genesisHash: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe',
};
export const kusamaAssetHub: NetworkInfo = {
@@ -154,6 +161,7 @@ export const kusamaAssetHub: NetworkInfo = {
return (await import('@substrate/connect-known-chains/ksmcc3_asset_hub')).chainSpec;
},
relayChain: kusama,
+ genesisHash: '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a',
};
export const kusamaPeople: NetworkInfo = {
@@ -178,6 +186,7 @@ export const kusamaPeople: NetworkInfo = {
return (await import('@substrate/connect-known-chains/ksmcc3_people')).chainSpec;
},
relayChain: kusama,
+ genesisHash: '0xc1af4cb4eb3918e5db15086c0cc5ec17fb334f728b7c65dd44bfe1e174ff8b3f',
};
export const hydration: NetworkInfo = {
@@ -195,6 +204,7 @@ export const hydration: NetworkInfo = {
symbol: 'HDX',
decimals: 12,
subscanUrl: 'https://hydration.subscan.io',
+ genesisHash: '0xafdc188f45c71dacbaa0b62e16a91f726c7b8699a9748cdf715459de6b7f366d',
};
export const basilisk: NetworkInfo = {
@@ -206,6 +216,7 @@ export const basilisk: NetworkInfo = {
symbol: 'BSX',
decimals: 12,
subscanUrl: 'https://basilisk.subscan.io',
+ genesisHash: '0xa85cfb9b9fd4d622a5b28289a02347af987d8f73fa3108450e2b4a11c1ce5755',
};
export const vara: NetworkInfo = {
@@ -217,4 +228,5 @@ export const vara: NetworkInfo = {
symbol: 'VARA',
decimals: 12,
subscanUrl: 'https://vara.subscan.io',
+ genesisHash: '0xfe1b4c55fd4d668101126434206571a7838a8b6b93a6d1b95d607e78e6c53763',
};
diff --git a/packages/typink/src/networks/testnet.ts b/packages/typink/src/networks/testnet.ts
index 5568c57f..266d7f82 100644
--- a/packages/typink/src/networks/testnet.ts
+++ b/packages/typink/src/networks/testnet.ts
@@ -11,6 +11,7 @@ export const popTestnet: NetworkInfo = {
decimals: 10,
faucetUrl: 'https://onboard.popnetwork.xyz',
pjsUrl: 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frpc1.paseo.popnetwork.xyz',
+ genesisHash: '0xe8b2d197b82a0da1fffca832c050894ebe343b289c61ef439aa694bdcef78aa1',
};
export const alephZeroTestnet: NetworkInfo = {
@@ -36,6 +37,7 @@ export const shibuyaTestnet: NetworkInfo = {
decimals: 18,
faucetUrl: 'https://docs.astar.network/docs/build/environment/faucet',
subscanUrl: 'https://shibuya.subscan.io',
+ genesisHash: '0xddb89973361a170839f80f152d2e9e38a376a5a7eccefcade763f46a8e567019',
};
export const westend: NetworkInfo = {
@@ -51,6 +53,7 @@ export const westend: NetworkInfo = {
chainSpec: async () => {
return (await import('@substrate/connect-known-chains/westend2')).chainSpec;
},
+ genesisHash: '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e',
};
export const westendAssetHub: NetworkInfo = {
@@ -67,6 +70,7 @@ export const westendAssetHub: NetworkInfo = {
return (await import('@substrate/connect-known-chains/westend2_asset_hub')).chainSpec;
},
relayChain: westend,
+ genesisHash: '0x67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9',
};
export const westendPeople: NetworkInfo = {
@@ -83,6 +87,7 @@ export const westendPeople: NetworkInfo = {
return (await import('@substrate/connect-known-chains/westend_people')).chainSpec;
},
relayChain: westend,
+ genesisHash: '0x1eb6fb0ba5187434de017a70cb84d4f47142df1d571d0ef9e7e1407f2b80b93c',
};
export const paseo: NetworkInfo = {
@@ -103,6 +108,7 @@ export const paseo: NetworkInfo = {
chainSpec: async () => {
return (await import('@substrate/connect-known-chains/paseo')).chainSpec;
},
+ genesisHash: '0x77afd6190f1554ad45fd0d31aee62aacc33c6db0ea801129acb813f913e0764f',
};
export const paseoPeople: NetworkInfo = {
@@ -118,6 +124,7 @@ export const paseoPeople: NetworkInfo = {
decimals: 10,
faucetUrl: 'https://faucet.polkadot.io',
subscanUrl: 'https://people-paseo.subscan.io',
+ genesisHash: '0xe6c30d6e148f250b887105237bcaa5cb9f16dd203bf7b5b9d4f1da7387cb86ec',
};
export const paseoAssetHub: NetworkInfo = {
@@ -136,6 +143,7 @@ export const paseoAssetHub: NetworkInfo = {
decimals: 10,
faucetUrl: 'https://faucet.polkadot.io',
subscanUrl: 'https://assethub-paseo.subscan.io',
+ genesisHash: '0xd6eec26135305a8ad257a20d003357284c8aa03d0bdb2b357ab0a22371e11ef2',
};
export const passetHub: NetworkInfo = {
@@ -150,6 +158,7 @@ export const passetHub: NetworkInfo = {
symbol: 'PAS',
decimals: 10,
faucetUrl: 'https://faucet.polkadot.io/?parachain=1111',
+ genesisHash: '0xfd974cf9eaf028f5e44b9fdd1949ab039c6cf9cc54449b0b60d71b042e79aeb6',
};
export const paseoHydration: NetworkInfo = {
@@ -161,4 +170,5 @@ export const paseoHydration: NetworkInfo = {
symbol: 'HDX',
decimals: 12,
faucetUrl: 'https://faucet.polkadot.io',
+ genesisHash: '0x05fab032a899566268235e3c89c847fb4644558a0d856282d47d17ff06cb2020',
};
diff --git a/packages/typink/src/providers/WalletSetupProvider.tsx b/packages/typink/src/providers/WalletSetupProvider.tsx
index 001bb3c5..e3c5bfd0 100644
--- a/packages/typink/src/providers/WalletSetupProvider.tsx
+++ b/packages/typink/src/providers/WalletSetupProvider.tsx
@@ -1,7 +1,7 @@
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { TypinkAccount } from '../types.js';
-import { polkadotjs, subwallet, talisman, Wallet } from '../wallets/index.js';
+import { polkadotjs, subwallet, talisman, Wallet, WalletConnect } from '../wallets/index.js';
import { noop } from '../utils/index.js';
import { WalletProvider, WalletProviderProps } from './WalletProvider.js';
import {
@@ -19,6 +19,7 @@ import {
initializeWalletsAtom,
setExternalSignerAtom,
} from '../atoms/walletActions.js';
+import { supportedNetworksAtom } from '../atoms/clientAtoms.js';
// Split these into 2 separate context (one for setup & one for signer & connected account)
export interface WalletSetupContextProps {
@@ -86,6 +87,7 @@ export function WalletSetupProvider({
const accounts = useAtomValue(allAccountsAtom);
const availableWallets = useAtomValue(availableWalletsAtom);
const finalEffectiveSigner = useAtomValue(finalEffectiveSignerAtom);
+ const supportedNetworks = useAtomValue(supportedNetworksAtom);
// Use atom actions
const connectWallet = useSetAtom(connectWalletAtom);
@@ -95,8 +97,16 @@ export function WalletSetupProvider({
// Initialize wallets and app name
useEffect(() => {
const walletsToUse = initialWallets || DEFAULT_WALLETS;
+
+ // Configure WalletConnect with supported networks
+ for (const wallet of walletsToUse) {
+ if (wallet instanceof WalletConnect && supportedNetworks.length > 0) {
+ wallet.setSupportedNetworks(supportedNetworks);
+ }
+ }
+
initializeWallets(walletsToUse);
- }, [initialWallets, initializeWallets]);
+ }, [initialWallets, supportedNetworks, initializeWallets]);
useEffect(() => {
initializeAppName(appName);
diff --git a/packages/typink/src/providers/__tests__/WalletSetupProvider.test.tsx b/packages/typink/src/providers/__tests__/WalletSetupProvider.test.tsx
index 42a143b0..1d43a81e 100644
--- a/packages/typink/src/providers/__tests__/WalletSetupProvider.test.tsx
+++ b/packages/typink/src/providers/__tests__/WalletSetupProvider.test.tsx
@@ -17,6 +17,7 @@ vi.mock('jotai', () => ({
useAtomValue: (atom: any) => mockUseAtomValue(atom),
useSetAtom: (atom: any) => mockUseSetAtom(atom),
createStore: vi.fn(() => ({})),
+ atom: (initialValue: any) => initialValue,
Provider: ({ children }: any) => children,
}));
diff --git a/packages/typink/src/types.ts b/packages/typink/src/types.ts
index 047e66d5..f78ada03 100644
--- a/packages/typink/src/types.ts
+++ b/packages/typink/src/types.ts
@@ -79,6 +79,7 @@ export interface NetworkInfo {
pjsUrl?: string;
faucetUrl?: string;
jsonRpcApi?: JsonRpcApi; // default to new
+ genesisHash?: string;
chainSpec?: () => Promise;
relayChain?: NetworkInfo;
}
diff --git a/packages/typink/src/utils/__tests__/chains.test.ts b/packages/typink/src/utils/__tests__/chains.test.ts
new file mode 100644
index 00000000..f99d465b
--- /dev/null
+++ b/packages/typink/src/utils/__tests__/chains.test.ts
@@ -0,0 +1,121 @@
+import { describe, expect, it } from 'vitest';
+import { genesisHashToCaipId, convertNetworkInfoToCaipId } from '../chains.js';
+import { NetworkInfo, NetworkType } from '../../types.js';
+
+describe('chains utilities', () => {
+ describe('genesisHashToCaipId', () => {
+ it('should convert genesis hash to CAIP-2 format', () => {
+ const genesisHash = '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3';
+ const result = genesisHashToCaipId(genesisHash);
+
+ expect(result).toBe('polkadot:91b171bb158e2d3848fa23a9f1c25182');
+ });
+
+ it('should remove 0x prefix and take first 32 characters', () => {
+ const genesisHash = '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890';
+ const result = genesisHashToCaipId(genesisHash);
+
+ expect(result).toBe('polkadot:abcdef1234567890abcdef1234567890');
+ expect(result.split(':')[1]).toHaveLength(32);
+ });
+
+ it('should handle genesis hash without 0x prefix', () => {
+ const genesisHash = '91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3';
+ const result = genesisHashToCaipId(genesisHash);
+
+ expect(result).toBe('polkadot:91b171bb158e2d3848fa23a9f1c25182');
+ });
+
+ it('should handle short genesis hash', () => {
+ const genesisHash = '0xabcd1234';
+ const result = genesisHashToCaipId(genesisHash);
+
+ expect(result).toBe('polkadot:abcd1234');
+ });
+ });
+
+ describe('convertNetworkInfoToCaipId', () => {
+ it('should convert multiple networks to CAIP-2 identifiers', () => {
+ const networks: NetworkInfo[] = [
+ {
+ id: 'polkadot',
+ type: NetworkType.MAINNET,
+ name: 'Polkadot',
+ logo: 'polkadot.png',
+ providers: ['wss://rpc.polkadot.io'],
+ symbol: 'DOT',
+ decimals: 10,
+ genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
+ },
+ {
+ id: 'kusama',
+ type: NetworkType.MAINNET,
+ name: 'Kusama',
+ logo: 'kusama.png',
+ providers: ['wss://kusama-rpc.polkadot.io'],
+ symbol: 'KSM',
+ decimals: 12,
+ genesisHash: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe',
+ },
+ ];
+
+ const result = convertNetworkInfoToCaipId(networks);
+
+ expect(result).toHaveLength(2);
+ expect(result[0]).toBe('polkadot:91b171bb158e2d3848fa23a9f1c25182');
+ expect(result[1]).toBe('polkadot:b0a8d493285c2df73290dfb7e61f870f');
+ });
+
+ it('should filter out networks without genesis hash', () => {
+ const networks: NetworkInfo[] = [
+ {
+ id: 'polkadot',
+ type: NetworkType.MAINNET,
+ name: 'Polkadot',
+ logo: 'polkadot.png',
+ providers: ['wss://rpc.polkadot.io'],
+ symbol: 'DOT',
+ decimals: 10,
+ genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
+ },
+ {
+ id: 'custom',
+ name: 'Custom Network',
+ logo: 'custom.png',
+ providers: ['wss://custom.io'],
+ symbol: 'CUST',
+ decimals: 10,
+ // No genesisHash
+ },
+ ];
+
+ const result = convertNetworkInfoToCaipId(networks);
+
+ expect(result).toHaveLength(1);
+ expect(result[0]).toBe('polkadot:91b171bb158e2d3848fa23a9f1c25182');
+ });
+
+ it('should return empty array for networks without genesis hash', () => {
+ const networks: NetworkInfo[] = [
+ {
+ id: 'custom',
+ name: 'Custom Network',
+ logo: 'custom.png',
+ providers: ['wss://custom.io'],
+ symbol: 'CUST',
+ decimals: 10,
+ },
+ ];
+
+ const result = convertNetworkInfoToCaipId(networks);
+
+ expect(result).toHaveLength(0);
+ });
+
+ it('should return empty array for empty input', () => {
+ const result = convertNetworkInfoToCaipId([]);
+
+ expect(result).toHaveLength(0);
+ });
+ });
+});
diff --git a/packages/typink/src/utils/chains.ts b/packages/typink/src/utils/chains.ts
new file mode 100644
index 00000000..01ead8da
--- /dev/null
+++ b/packages/typink/src/utils/chains.ts
@@ -0,0 +1,19 @@
+import { NetworkInfo } from '../types.js';
+
+/**
+ * Convert a genesis hash to a WalletConnect chain identifier
+ * @param genesisHash - The genesis hash of the network
+ * @returns WalletConnect chain identifier in format "polkadot:{genesisHash}"
+ */
+export function genesisHashToCaipId(genesisHash: string): string {
+ return `polkadot:${genesisHash.replace('0x', '').slice(0, 32)}`;
+}
+
+/**
+ * Convert an array of NetworkInfo to WalletConnect chain identifiers
+ * @param networks - Array of network info from typink
+ * @returns Array of WalletConnect chain identifiers
+ */
+export function convertNetworkInfoToCaipId(networks: NetworkInfo[]): string[] {
+ return networks.filter((o) => !!o.genesisHash).map((o) => genesisHashToCaipId(o.genesisHash!));
+}
diff --git a/packages/typink/src/utils/index.ts b/packages/typink/src/utils/index.ts
index 300f39fc..8ded758c 100644
--- a/packages/typink/src/utils/index.ts
+++ b/packages/typink/src/utils/index.ts
@@ -1,4 +1,5 @@
export * from './errors.js';
export * from './misc.js';
export * from './accounts.js';
+export * from './chains.js';
export { formatBalance } from '@dedot/utils';
diff --git a/packages/typink/src/wallets/Wallet.ts b/packages/typink/src/wallets/Wallet.ts
index 67fd5750..2d3d8889 100644
--- a/packages/typink/src/wallets/Wallet.ts
+++ b/packages/typink/src/wallets/Wallet.ts
@@ -1,4 +1,5 @@
import { InjectedWindow } from '../types.js';
+import { InjectedWindowProvider } from '../pjs-types.js';
export interface WalletOptions {
id: string;
@@ -35,7 +36,7 @@ export abstract class Wallet {
return injectedWindow.injectedWeb3;
}
- get injectedProvider() {
+ get injectedProvider(): InjectedWindowProvider | undefined {
return this.injectedWeb3[this.id];
}
diff --git a/packages/typink/src/wallets/WalletConnect.ts b/packages/typink/src/wallets/WalletConnect.ts
new file mode 100644
index 00000000..1e4208de
--- /dev/null
+++ b/packages/typink/src/wallets/WalletConnect.ts
@@ -0,0 +1,339 @@
+import { Wallet, WalletOptions } from './Wallet.js';
+import type { SignClientTypes } from '@walletconnect/types';
+import type { IUniversalProvider, Metadata } from '@walletconnect/universal-provider';
+import {
+ InjectedAccount,
+ InjectedAccounts,
+ InjectedSigner,
+ InjectedWindowProvider,
+ SignerPayloadJSON,
+ SignerPayloadRaw,
+ SignerResult,
+} from '../pjs-types.js';
+import { NetworkInfo } from '../types.js';
+import type { WalletConnectModal } from '@walletconnect/modal';
+import { assert, DedotError, HexString } from 'dedot/utils';
+import { genesisHashToCaipId, convertNetworkInfoToCaipId } from '../utils/chains.js';
+
+export interface WalletConnectOptions extends WalletOptions {
+ projectId: string;
+ metadata: Metadata;
+ relayUrl: string;
+}
+
+export class WalletConnect extends Wallet {
+ #provider?: IUniversalProvider;
+ #modal?: WalletConnectModal;
+
+ #injectedProvider?: InjectedWindowProvider;
+ #supportedNetworks: NetworkInfo[] = [];
+
+ #accountSubscribers: Set<(accounts: InjectedAccount[]) => void | Promise> = new Set();
+ #accounts: InjectedAccount[] = [];
+
+ constructor(public options: WalletConnectOptions) {
+ super(options);
+ }
+
+ get injectedProvider(): InjectedWindowProvider | undefined {
+ return this.#injectedProvider;
+ }
+
+ get ready(): boolean {
+ // WalletConnect is always "ready" since it's a protocol, not a browser extension
+ // The actual initialization happens lazily when connect() is called
+ return true;
+ }
+
+ get installed(): boolean {
+ // WalletConnect is always "installed" since it's a protocol, not a browser extension
+ return true;
+ }
+
+ get provider(): IUniversalProvider | undefined {
+ return this.#provider;
+ }
+
+ setSupportedNetworks(networks: NetworkInfo[]): void {
+ this.#supportedNetworks = networks;
+ }
+
+ async waitUntilReady(): Promise {
+ await this.#initialize();
+ await this.#setupModal();
+ await this.#connect();
+ }
+
+ async #connect(): Promise {
+ assert(this.#provider, 'WalletConnect provider not initialized');
+ assert(this.#modal, 'WalletConnect modal not initialized');
+
+ try {
+ if (this.#provider.session) {
+ this.#accounts = this.#getAccounts();
+ } else {
+ const chains = convertNetworkInfoToCaipId(this.#supportedNetworks);
+
+ const { uri, approval } = await this.#provider.client!.connect({
+ optionalNamespaces: {
+ polkadot: {
+ chains,
+ methods: [
+ 'polkadot_signMessage',
+ 'polkadot_sendTransaction',
+ 'polkadot_signTransaction',
+ 'polkadot_requestAccounts',
+ ],
+ events: ['accountsChanged', 'chainChanged', 'connect'],
+ },
+ },
+ });
+
+ if (uri) {
+ await this.#modal!.openModal({ uri });
+ }
+
+ const userCloseModal = new Promise((_, reject) => {
+ this.#modal!.subscribeModal((state: { open: boolean }) => {
+ !state.open && reject(new DedotError('User closed WalletConnect modal'));
+ });
+ });
+
+ this.#provider.session = await Promise.race([approval(), userCloseModal]);
+ this.#modal!.closeModal();
+ this.#accounts = this.#getAccounts();
+ }
+
+ this.#subscribeToSessionEvents();
+ this.#notifyAccountSubscribers(this.#accounts);
+ this.#injectedProvider = this.#getInjectedProvider();
+ } catch (e) {
+ throw new DedotError(`Failed to connect WalletConnect: ${(e as Error).message}`);
+ }
+ }
+
+ async #initialize(): Promise {
+ if (this.#provider) return;
+
+ const { projectId, relayUrl, metadata } = this.options;
+
+ assert(
+ this.options.projectId,
+ `${this.name} requires a projectId. Please visit https://cloud.walletconnect.com to get one.`,
+ );
+
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+
+ try {
+ this.#provider = await UniversalProvider.init({
+ projectId,
+ relayUrl,
+ metadata,
+ });
+ } catch (e) {
+ throw new DedotError(`Failed to initialize WalletConnect: ${(e as Error).message}`);
+ }
+ }
+
+ async #setupModal(): Promise {
+ if (this.#modal) return;
+
+ try {
+ const WalletConnectModalModule = await import('@walletconnect/modal');
+ const WalletConnectModal = WalletConnectModalModule.WalletConnectModal;
+
+ this.#modal = new WalletConnectModal({
+ projectId: this.options.projectId,
+ });
+
+ // Find out a way to do it properly
+ this.#modal!.setTheme({
+ themeVariables: {
+ '--wcm-z-index': '9999',
+ },
+ });
+ } catch (e) {
+ throw new DedotError(`Failed to initialize WalletConnect Modal: ${(e as Error).message}`);
+ }
+ }
+
+ async disconnect(): Promise {
+ if (!this.#provider) return;
+
+ try {
+ await Promise.race([
+ this.#provider.disconnect(),
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Disconnect timeout')), 5000)),
+ ]);
+ } catch (e) {}
+
+ this.#cleanUp();
+ }
+
+ #cleanUp() {
+ this.#provider = undefined;
+ this.#injectedProvider = undefined;
+ this.#accountSubscribers.clear();
+ }
+
+ #getAccounts(): InjectedAccount[] {
+ if (!this.#provider?.session) return [];
+
+ const accounts: InjectedAccount[] = [];
+ const polkadotNamespace = this.#provider.session.namespaces.polkadot;
+
+ if (polkadotNamespace && polkadotNamespace.accounts) {
+ // Get unique addresses across all chains
+ const uniqueAddresses = new Set();
+
+ for (const account of polkadotNamespace.accounts) {
+ // WalletConnect account format: "polkadot:{genesisHash}:{address}"
+ const parts = account.split(':');
+ if (parts.length >= 3) {
+ const address = parts.slice(2).join(':'); // Handle addresses with colons
+ uniqueAddresses.add(address);
+ }
+ }
+
+ uniqueAddresses.forEach((address) => {
+ accounts.push({ address });
+ });
+ }
+
+ return accounts;
+ }
+
+ #getSigner(): InjectedSigner {
+ return {
+ signPayload: async (payload: SignerPayloadJSON): Promise => {
+ assert(this.#provider?.client && this.#provider?.session, 'Provider client or session not initialized');
+
+ try {
+ const result = (await this.#provider.client.request({
+ topic: this.#provider.session.topic,
+ chainId: genesisHashToCaipId(payload.genesisHash),
+ request: {
+ method: 'polkadot_signTransaction',
+ params: {
+ address: payload.address,
+ transactionPayload: payload,
+ },
+ },
+ })) as { signature: string };
+
+ return {
+ id: 0,
+ signature: result.signature as HexString,
+ };
+ } catch (error) {
+ throw new DedotError(`Failed to sign payload with WalletConnect: ${(error as Error).message}`);
+ }
+ },
+
+ signRaw: async (raw: SignerPayloadRaw): Promise => {
+ assert(this.#provider?.client && this.#provider?.session, 'Provider client or session not initialized');
+
+ try {
+ // We choose the first one in the list because it is required to be one of the chain IDs
+ // that were requested during session creation, so we cannot make it to use a default one (e.g., polkadot mainnet)
+ const chainId = this.#provider.session.namespaces.polkadot.chains![0];
+
+ const result = (await this.#provider.client.request({
+ topic: this.#provider.session.topic,
+ chainId,
+ request: {
+ method: 'polkadot_signMessage',
+ params: {
+ address: raw.address,
+ message: raw.data,
+ type: raw.type,
+ },
+ },
+ })) as { signature: string };
+
+ return {
+ id: 0,
+ signature: result.signature as HexString,
+ };
+ } catch (error) {
+ throw new DedotError(`Failed to sign raw message with WalletConnect: ${(error as Error).message}`);
+ }
+ },
+ };
+ }
+
+ #getInjectedAccounts(): InjectedAccounts {
+ return {
+ get: async (): Promise => {
+ return this.#getAccounts();
+ },
+
+ subscribe: (callback: (accounts: InjectedAccount[]) => void | Promise): (() => void) => {
+ this.#accountSubscribers.add(callback);
+
+ // Immediately call with current accounts
+ callback(this.#getAccounts());
+
+ // Return unsubscribe function
+ return () => {
+ this.#accountSubscribers.delete(callback);
+ };
+ },
+ };
+ }
+
+ #getInjectedProvider(): InjectedWindowProvider {
+ const signer = this.#getSigner();
+ const accounts = this.#getInjectedAccounts();
+
+ return {
+ enable: async (_origin: string): Promise<{ accounts: InjectedAccounts; signer: InjectedSigner }> => {
+ if (!this.#provider?.session) {
+ throw new Error('WalletConnect session not established');
+ }
+
+ return {
+ accounts,
+ signer,
+ };
+ },
+
+ version: '1.0.0',
+ };
+ }
+
+ #subscribeToSessionEvents(): void {
+ assert(this.#provider?.client && this.#provider?.session, 'Provider client or session not initialized');
+
+ this.#provider.client.on(
+ 'session_update',
+ ({ topic, params }: SignClientTypes.EventArguments['session_update']) => {
+ const { namespaces } = params;
+ const session = this.#provider!.client!.session.get(topic);
+ this.#provider!.session! = { ...session, namespaces };
+
+ // Notify account subscribers
+ const accounts = this.#getAccounts();
+ this.#notifyAccountSubscribers(accounts);
+ },
+ );
+
+ this.#provider.client.on('session_delete', () => {
+ this.#provider!.session = undefined;
+ this.#injectedProvider = undefined;
+
+ // Notify subscribers that accounts are gone
+ this.#notifyAccountSubscribers([]);
+ });
+ }
+
+ #notifyAccountSubscribers(accounts: InjectedAccount[]): void {
+ this.#accountSubscribers.forEach((callback) => {
+ try {
+ callback(accounts);
+ } catch (error) {
+ console.error('Error in account subscriber callback:', error);
+ }
+ });
+ }
+}
diff --git a/packages/typink/src/wallets/__tests__/WalletConnect.test.ts b/packages/typink/src/wallets/__tests__/WalletConnect.test.ts
new file mode 100644
index 00000000..f51da389
--- /dev/null
+++ b/packages/typink/src/wallets/__tests__/WalletConnect.test.ts
@@ -0,0 +1,521 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { WalletConnect, WalletConnectOptions } from '../WalletConnect.js';
+import type { IUniversalProvider } from '@walletconnect/universal-provider';
+import type { WalletConnectModal } from '@walletconnect/modal';
+import type { SignClientTypes } from '@walletconnect/types';
+import { SignerPayloadJSON, SignerPayloadRaw } from '../../pjs-types.js';
+import { NetworkInfo, NetworkType } from '../../types.js';
+
+// Mock the dependencies
+vi.mock('@walletconnect/universal-provider', () => ({
+ UniversalProvider: {
+ init: vi.fn(),
+ },
+}));
+
+vi.mock('@walletconnect/modal', () => ({
+ WalletConnectModal: vi.fn(),
+}));
+
+describe('WalletConnect', () => {
+ let walletConnect: WalletConnect;
+ let mockOptions: WalletConnectOptions;
+ let mockProvider: Partial;
+ let mockModal: Partial;
+ let mockClient: any;
+
+ const mockNetworks: NetworkInfo[] = [
+ {
+ id: 'polkadot',
+ type: NetworkType.MAINNET,
+ name: 'Polkadot',
+ logo: 'polkadot.png',
+ providers: ['wss://rpc.polkadot.io'],
+ symbol: 'DOT',
+ decimals: 10,
+ genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
+ },
+ {
+ id: 'kusama',
+ type: NetworkType.MAINNET,
+ name: 'Kusama',
+ logo: 'kusama.png',
+ providers: ['wss://kusama-rpc.polkadot.io'],
+ symbol: 'KSM',
+ decimals: 12,
+ genesisHash: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe',
+ },
+ ];
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+
+ mockOptions = {
+ id: 'walletconnect',
+ name: 'WalletConnect',
+ logo: 'walletconnect.svg',
+ projectId: 'test-project-id',
+ metadata: {
+ name: 'Test DApp',
+ description: 'Test DApp Description',
+ url: 'https://test.com',
+ icons: ['https://test.com/icon.png'],
+ },
+ relayUrl: 'wss://relay.walletconnect.com',
+ };
+
+ // Mock client
+ mockClient = {
+ connect: vi.fn(),
+ request: vi.fn(),
+ on: vi.fn(),
+ session: {
+ get: vi.fn(),
+ },
+ };
+
+ // Mock provider
+ mockProvider = {
+ client: mockClient,
+ session: undefined,
+ disconnect: vi.fn().mockResolvedValue(undefined),
+ };
+
+ // Mock modal
+ mockModal = {
+ openModal: vi.fn().mockResolvedValue(undefined),
+ closeModal: vi.fn(),
+ subscribeModal: vi.fn(),
+ setTheme: vi.fn(),
+ };
+
+ walletConnect = new WalletConnect(mockOptions);
+ });
+
+ describe('Basic Properties', () => {
+ it('should always be ready and installed', () => {
+ expect(walletConnect.ready).toBe(true);
+ expect(walletConnect.installed).toBe(true);
+ });
+ });
+
+ describe('Initialization and Connection', () => {
+ it('should initialize and connect with existing session', async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ mockProvider.session = {
+ topic: 'test-topic',
+ namespaces: {
+ polkadot: {
+ accounts: ['polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
+ chains: ['polkadot:91b171bb158e2d3848fa23a9f1c25182'],
+ methods: ['polkadot_signTransaction'],
+ events: [],
+ },
+ },
+ } as any;
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+ await walletConnect.waitUntilReady();
+
+ expect(UniversalProvider.init).toHaveBeenCalledWith({
+ projectId: mockOptions.projectId,
+ relayUrl: mockOptions.relayUrl,
+ metadata: mockOptions.metadata,
+ });
+ expect(walletConnect.injectedProvider).toBeDefined();
+ });
+
+ it('should establish new connection when no existing session', async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ const mockSession = {
+ topic: 'test-topic',
+ namespaces: {
+ polkadot: {
+ accounts: ['polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
+ chains: ['polkadot:91b171bb158e2d3848fa23a9f1c25182'],
+ methods: ['polkadot_signTransaction'],
+ events: [],
+ },
+ },
+ };
+
+ mockClient.connect.mockResolvedValue({
+ uri: 'wc:test-uri',
+ approval: vi.fn().mockResolvedValue(mockSession),
+ });
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+ await walletConnect.waitUntilReady();
+
+ expect(mockClient.connect).toHaveBeenCalledWith({
+ optionalNamespaces: {
+ polkadot: {
+ chains: expect.any(Array),
+ methods: [
+ 'polkadot_signMessage',
+ 'polkadot_sendTransaction',
+ 'polkadot_signTransaction',
+ 'polkadot_requestAccounts',
+ ],
+ events: ['accountsChanged', 'chainChanged', 'connect'],
+ },
+ },
+ });
+ expect(mockModal.openModal).toHaveBeenCalledWith({ uri: 'wc:test-uri' });
+ expect(mockModal.closeModal).toHaveBeenCalled();
+ });
+
+ it('should throw error when projectId is missing', async () => {
+ const invalidOptions = { ...mockOptions, projectId: '' };
+ const invalidWallet = new WalletConnect(invalidOptions);
+
+ await expect(invalidWallet.waitUntilReady()).rejects.toThrow();
+ });
+
+ it('should throw error when provider initialization fails', async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ vi.mocked(UniversalProvider.init).mockRejectedValue(new Error('Init failed'));
+
+ await expect(walletConnect.waitUntilReady()).rejects.toThrow('Failed to initialize WalletConnect: Init failed');
+ });
+
+ it('should handle user closing modal', async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+
+ let modalCallback: (state: { open: boolean }) => void;
+ mockModal.subscribeModal = vi.fn().mockImplementation((cb) => {
+ modalCallback = cb;
+ });
+
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ mockClient.connect.mockResolvedValue({
+ uri: 'wc:test-uri',
+ approval: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ setTimeout(() => resolve({ topic: 'test' }), 1000);
+ }),
+ ),
+ });
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+
+ const connectPromise = walletConnect.waitUntilReady();
+
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ modalCallback!({ open: false });
+
+ await expect(connectPromise).rejects.toThrow('Failed to connect WalletConnect: User closed WalletConnect modal');
+ });
+ });
+
+ describe('Disconnect', () => {
+ it('should disconnect and cleanup state', async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ mockProvider.session = {
+ topic: 'test-topic',
+ namespaces: {
+ polkadot: {
+ accounts: ['polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
+ chains: [],
+ methods: [],
+ events: [],
+ },
+ },
+ } as any;
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+ await walletConnect.waitUntilReady();
+
+ await walletConnect.disconnect();
+
+ expect(mockProvider.disconnect).toHaveBeenCalled();
+ expect(walletConnect.provider).toBeUndefined();
+ expect(walletConnect.injectedProvider).toBeUndefined();
+ });
+
+ it('should handle disconnect errors gracefully', async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ mockProvider.disconnect = vi.fn().mockRejectedValue(new Error('Disconnect failed'));
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ mockProvider.session = {
+ topic: 'test-topic',
+ namespaces: {
+ polkadot: {
+ accounts: ['polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
+ chains: [],
+ methods: [],
+ events: [],
+ },
+ },
+ } as any;
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+ await walletConnect.waitUntilReady();
+
+ await expect(walletConnect.disconnect()).resolves.toBeUndefined();
+ expect(walletConnect.provider).toBeUndefined();
+ });
+ });
+
+ describe('InjectedProvider', () => {
+ beforeEach(async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ mockProvider.session = {
+ topic: 'test-topic',
+ namespaces: {
+ polkadot: {
+ accounts: [
+ 'polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
+ 'polkadot:91b171bb158e2d3848fa23a9f1c25182:5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
+ ],
+ chains: ['polkadot:91b171bb158e2d3848fa23a9f1c25182'],
+ methods: ['polkadot_signTransaction'],
+ events: [],
+ },
+ },
+ } as any;
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+ await walletConnect.waitUntilReady();
+ });
+
+ it('should provide enable method and version', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+
+ expect(injectedProvider.enable).toBeDefined();
+ expect(typeof injectedProvider.enable).toBe('function');
+ expect(injectedProvider.version).toBeDefined();
+ });
+
+ it('should enable and return accounts and signer', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const result = await injectedProvider.enable!('test-origin');
+
+ expect(result.accounts).toBeDefined();
+ expect(result.signer).toBeDefined();
+
+ const accountList = await result.accounts.get();
+ expect(accountList).toHaveLength(2);
+ expect(accountList[0].address).toBe('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
+ });
+
+ it('should support account subscription', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const { accounts } = await injectedProvider.enable!('test-origin');
+
+ const callback = vi.fn();
+ const unsubscribe = accounts.subscribe(callback);
+
+ expect(callback).toHaveBeenCalled();
+ expect(typeof unsubscribe).toBe('function');
+ });
+
+ it('should sign transaction payload', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const { signer } = await injectedProvider.enable!('test-origin');
+
+ const payload: SignerPayloadJSON = {
+ address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
+ blockHash: '0x1234',
+ blockNumber: '0x01',
+ era: '0x00',
+ genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
+ method: '0xabcd',
+ nonce: '0x00',
+ specVersion: '0x01',
+ tip: '0x00',
+ transactionVersion: '0x01',
+ signedExtensions: [],
+ version: 4,
+ };
+
+ mockClient.request.mockResolvedValue({ signature: '0xmocksignature' });
+
+ const result = await signer.signPayload!(payload);
+
+ expect(mockClient.request).toHaveBeenCalledWith({
+ topic: 'test-topic',
+ chainId: expect.stringContaining('polkadot:'),
+ request: {
+ method: 'polkadot_signTransaction',
+ params: {
+ address: payload.address,
+ transactionPayload: payload,
+ },
+ },
+ });
+ expect(result.signature).toBe('0xmocksignature');
+ });
+
+ it('should sign raw message', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const { signer } = await injectedProvider.enable!('test-origin');
+
+ const raw: SignerPayloadRaw = {
+ address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
+ data: '0x1234567890',
+ type: 'bytes',
+ };
+
+ mockClient.request.mockResolvedValue({ signature: '0xrawsignature' });
+
+ const result = await signer.signRaw!(raw);
+
+ expect(mockClient.request).toHaveBeenCalledWith({
+ topic: 'test-topic',
+ chainId: expect.stringContaining('polkadot:'),
+ request: {
+ method: 'polkadot_signMessage',
+ params: {
+ address: raw.address,
+ message: raw.data,
+ type: raw.type,
+ },
+ },
+ });
+ expect(result.signature).toBe('0xrawsignature');
+ });
+
+ it('should handle signing errors', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const { signer } = await injectedProvider.enable!('test-origin');
+
+ const payload: SignerPayloadJSON = {
+ address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
+ blockHash: '0x1234',
+ blockNumber: '0x01',
+ era: '0x00',
+ genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
+ method: '0xabcd',
+ nonce: '0x00',
+ specVersion: '0x01',
+ tip: '0x00',
+ transactionVersion: '0x01',
+ signedExtensions: [],
+ version: 4,
+ };
+
+ mockClient.request.mockRejectedValue(new Error('User rejected'));
+
+ await expect(signer.signPayload!(payload)).rejects.toThrow(
+ 'Failed to sign payload with WalletConnect: User rejected',
+ );
+ });
+ });
+
+ describe('Session Events', () => {
+ let sessionUpdateCallback: (args: SignClientTypes.EventArguments['session_update']) => void;
+ let sessionDeleteCallback: () => void;
+
+ beforeEach(async () => {
+ const { UniversalProvider } = await import('@walletconnect/universal-provider');
+ const { WalletConnectModal: WCModal } = await import('@walletconnect/modal');
+
+ mockClient.on = vi.fn().mockImplementation((event: string, callback: any) => {
+ if (event === 'session_update') {
+ sessionUpdateCallback = callback;
+ } else if (event === 'session_delete') {
+ sessionDeleteCallback = callback;
+ }
+ });
+
+ vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider as any);
+ vi.mocked(WCModal).mockImplementation(() => mockModal as WalletConnectModal);
+
+ mockProvider.session = {
+ topic: 'test-topic',
+ namespaces: {
+ polkadot: {
+ accounts: ['polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
+ chains: ['polkadot:91b171bb158e2d3848fa23a9f1c25182'],
+ methods: ['polkadot_signTransaction'],
+ events: [],
+ },
+ },
+ } as any;
+
+ walletConnect.setSupportedNetworks(mockNetworks);
+ await walletConnect.waitUntilReady();
+ });
+
+ it('should handle session_update event', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const { accounts } = await injectedProvider.enable!('test-origin');
+
+ const callback = vi.fn();
+ accounts.subscribe(callback);
+ callback.mockClear();
+
+ const newNamespaces = {
+ polkadot: {
+ accounts: [
+ 'polkadot:91b171bb158e2d3848fa23a9f1c25182:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
+ 'polkadot:91b171bb158e2d3848fa23a9f1c25182:5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
+ ],
+ chains: ['polkadot:91b171bb158e2d3848fa23a9f1c25182'],
+ methods: ['polkadot_signTransaction'],
+ events: [],
+ },
+ };
+
+ mockClient.session.get.mockReturnValue({
+ topic: 'test-topic',
+ namespaces: newNamespaces,
+ });
+
+ sessionUpdateCallback({
+ id: 1,
+ topic: 'test-topic',
+ params: { namespaces: newNamespaces },
+ });
+
+ expect(callback).toHaveBeenCalled();
+ const calledAccounts = callback.mock.calls[0][0];
+ expect(calledAccounts).toHaveLength(2);
+ });
+
+ it('should handle session_delete event', async () => {
+ const injectedProvider = walletConnect.injectedProvider!;
+ const { accounts } = await injectedProvider.enable!('test-origin');
+
+ const callback = vi.fn();
+ accounts.subscribe(callback);
+ callback.mockClear();
+
+ sessionDeleteCallback();
+
+ expect(callback).toHaveBeenCalledWith([]);
+ expect(walletConnect.injectedProvider).toBeUndefined();
+ });
+ });
+});
diff --git a/packages/typink/src/wallets/index.ts b/packages/typink/src/wallets/index.ts
index c88cb75b..9d948ec7 100644
--- a/packages/typink/src/wallets/index.ts
+++ b/packages/typink/src/wallets/index.ts
@@ -1,7 +1,9 @@
import { ExtensionWallet } from './ExtensionWallet.js';
+import { WalletConnect } from './WalletConnect.js';
export * from './Wallet.js';
export * from './ExtensionWallet.js';
+export * from './WalletConnect.js';
export const subwallet = new ExtensionWallet({
name: 'SubWallet',
@@ -26,3 +28,17 @@ export const polkadotjs = new ExtensionWallet({
installUrl: 'https://polkadot.js.org/extension',
websiteUrl: 'https://polkadot.js.org',
});
+
+export const walletConnect = new WalletConnect({
+ name: 'WalletConnect',
+ id: 'walletconnect',
+ logo: 'https://raw.githubusercontent.com/dedotdev/typink/feature/wallet-connect/assets/wallets/wallet-connect-logo.svg',
+ projectId: 'b56e18d47c72ab683b10814fe9495694', // Default project ID, please create your own at https://cloud.walletconnect.com
+ relayUrl: 'wss://relay.walletconnect.com',
+ metadata: {
+ name: 'Typink Dapp',
+ description: 'Typink powered dApp',
+ url: typeof window !== 'undefined' ? window.location.origin : 'https://typink.dev',
+ icons: ['https://raw.githubusercontent.com/dedotdev/typink/main/assets/typink/typink-pink-logo.png'],
+ },
+});
diff --git a/yarn.lock b/yarn.lock
index 15d3674a..4a2217d5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3206,6 +3206,13 @@ __metadata:
languageName: node
linkType: hard
+"@msgpack/msgpack@npm:3.1.2":
+ version: 3.1.2
+ resolution: "@msgpack/msgpack@npm:3.1.2"
+ checksum: 10/e04ff37d7c89ffdd6b4fbcd1770af60b16c98afdf1c3c16190170dfe34764048eb45e3654016ac62cc616c7e4b09e611f8863317ca5f18b3a72974fb131e562e
+ languageName: node
+ linkType: hard
+
"@napi-rs/wasm-runtime@npm:^0.2.11":
version: 0.2.12
resolution: "@napi-rs/wasm-runtime@npm:0.2.12"
@@ -3217,13 +3224,22 @@ __metadata:
languageName: node
linkType: hard
-"@noble/ciphers@npm:^1.3.0":
+"@noble/ciphers@npm:1.3.0, @noble/ciphers@npm:^1.3.0":
version: 1.3.0
resolution: "@noble/ciphers@npm:1.3.0"
checksum: 10/051660051e3e9e2ca5fb9dece2885532b56b7e62946f89afa7284a0fb8bc02e2bd1c06554dba68162ff42d295b54026456084198610f63c296873b2f1cd7a586
languageName: node
linkType: hard
+"@noble/curves@npm:1.8.0":
+ version: 1.8.0
+ resolution: "@noble/curves@npm:1.8.0"
+ dependencies:
+ "@noble/hashes": "npm:1.7.0"
+ checksum: 10/c54ce84cf54b8bda1a37a10dfae2e49e5b6cdf5dd98b399efa8b8a80a286b3f8f27bde53202cb308353bfd98719938991a78bed6e43f81f13b17f8181b7b82eb
+ languageName: node
+ linkType: hard
+
"@noble/curves@npm:1.9.1":
version: 1.9.1
resolution: "@noble/curves@npm:1.9.1"
@@ -3233,6 +3249,15 @@ __metadata:
languageName: node
linkType: hard
+"@noble/curves@npm:1.9.7, @noble/curves@npm:~1.9.0":
+ version: 1.9.7
+ resolution: "@noble/curves@npm:1.9.7"
+ dependencies:
+ "@noble/hashes": "npm:1.8.0"
+ checksum: 10/3cfe2735ea94972988ca9e217e0ebb2044372a7160b2079bf885da789492a6291fc8bf76ca3d8bf8dee477847ee2d6fac267d1e6c4f555054059f5e8c4865d44
+ languageName: node
+ linkType: hard
+
"@noble/curves@npm:^1.3.0":
version: 1.7.0
resolution: "@noble/curves@npm:1.7.0"
@@ -3242,15 +3267,6 @@ __metadata:
languageName: node
linkType: hard
-"@noble/curves@npm:~1.9.0":
- version: 1.9.7
- resolution: "@noble/curves@npm:1.9.7"
- dependencies:
- "@noble/hashes": "npm:1.8.0"
- checksum: 10/3cfe2735ea94972988ca9e217e0ebb2044372a7160b2079bf885da789492a6291fc8bf76ca3d8bf8dee477847ee2d6fac267d1e6c4f555054059f5e8c4865d44
- languageName: node
- linkType: hard
-
"@noble/hashes@npm:1.6.0":
version: 1.6.0
resolution: "@noble/hashes@npm:1.6.0"
@@ -3258,6 +3274,13 @@ __metadata:
languageName: node
linkType: hard
+"@noble/hashes@npm:1.7.0":
+ version: 1.7.0
+ resolution: "@noble/hashes@npm:1.7.0"
+ checksum: 10/ab038a816c8c9bb986e92797e3d9c5a5b37c020e0c3edc55bcae5061dbdd457f1f0a22787f83f4787c17415ba0282a20a1e455d36ed0cdcace4ce21ef1869f60
+ languageName: node
+ linkType: hard
+
"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.7.1, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0":
version: 1.8.0
resolution: "@noble/hashes@npm:1.8.0"
@@ -4768,6 +4791,13 @@ __metadata:
languageName: node
linkType: hard
+"@scure/base@npm:1.2.6, @scure/base@npm:^1.2.4, @scure/base@npm:^1.2.6, @scure/base@npm:~1.2.5":
+ version: 1.2.6
+ resolution: "@scure/base@npm:1.2.6"
+ checksum: 10/c1a7bd5e0b0c8f94c36fbc220f4a67cc832b00e2d2065c7d8a404ed81ab1c94c5443def6d361a70fc382db3496e9487fb9941728f0584782b274c18a4bed4187
+ languageName: node
+ linkType: hard
+
"@scure/base@npm:^1.1.1, @scure/base@npm:^1.1.5, @scure/base@npm:^1.1.7":
version: 1.2.1
resolution: "@scure/base@npm:1.2.1"
@@ -4775,13 +4805,6 @@ __metadata:
languageName: node
linkType: hard
-"@scure/base@npm:^1.2.4, @scure/base@npm:^1.2.6, @scure/base@npm:~1.2.5":
- version: 1.2.6
- resolution: "@scure/base@npm:1.2.6"
- checksum: 10/c1a7bd5e0b0c8f94c36fbc220f4a67cc832b00e2d2065c7d8a404ed81ab1c94c5443def6d361a70fc382db3496e9487fb9941728f0584782b274c18a4bed4187
- languageName: node
- linkType: hard
-
"@scure/bip32@npm:1.7.0, @scure/bip32@npm:^1.7.0":
version: 1.7.0
resolution: "@scure/bip32@npm:1.7.0"
@@ -5979,6 +6002,31 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/core@npm:2.22.4":
+ version: 2.22.4
+ resolution: "@walletconnect/core@npm:2.22.4"
+ dependencies:
+ "@walletconnect/heartbeat": "npm:1.2.2"
+ "@walletconnect/jsonrpc-provider": "npm:1.0.14"
+ "@walletconnect/jsonrpc-types": "npm:1.0.4"
+ "@walletconnect/jsonrpc-utils": "npm:1.0.8"
+ "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16"
+ "@walletconnect/keyvaluestorage": "npm:1.1.1"
+ "@walletconnect/logger": "npm:3.0.0"
+ "@walletconnect/relay-api": "npm:1.0.11"
+ "@walletconnect/relay-auth": "npm:1.1.0"
+ "@walletconnect/safe-json": "npm:1.0.2"
+ "@walletconnect/time": "npm:1.0.2"
+ "@walletconnect/types": "npm:2.22.4"
+ "@walletconnect/utils": "npm:2.22.4"
+ "@walletconnect/window-getters": "npm:1.0.1"
+ es-toolkit: "npm:1.39.3"
+ events: "npm:3.3.0"
+ uint8arrays: "npm:3.1.1"
+ checksum: 10/d34e617532843a6c18dc67b4c308f9f50aa259ec9357c35b63c0c481bdebe834da1a7e7b6fa96e2ad37a63d421e662086a71aae5d00208a6e469a060b926cd9b
+ languageName: node
+ linkType: hard
+
"@walletconnect/environment@npm:^1.0.1":
version: 1.0.1
resolution: "@walletconnect/environment@npm:1.0.1"
@@ -6065,6 +6113,18 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/jsonrpc-ws-connection@npm:1.0.16":
+ version: 1.0.16
+ resolution: "@walletconnect/jsonrpc-ws-connection@npm:1.0.16"
+ dependencies:
+ "@walletconnect/jsonrpc-utils": "npm:^1.0.6"
+ "@walletconnect/safe-json": "npm:^1.0.2"
+ events: "npm:^3.3.0"
+ ws: "npm:^7.5.1"
+ checksum: 10/98e06097588f895c4ba14b6feb64ed9b5c125d57a4ea3ad3fa6f52fd090fccce60808252c8cefaddc022cfa7fde7551a3aec3bb36e6b08c622207d7554d93e40
+ languageName: node
+ linkType: hard
+
"@walletconnect/keyvaluestorage@npm:1.1.1":
version: 1.1.1
resolution: "@walletconnect/keyvaluestorage@npm:1.1.1"
@@ -6091,6 +6151,16 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/logger@npm:3.0.0":
+ version: 3.0.0
+ resolution: "@walletconnect/logger@npm:3.0.0"
+ dependencies:
+ "@walletconnect/safe-json": "npm:^1.0.2"
+ pino: "npm:10.0.0"
+ checksum: 10/30c3c6029c6cf9669e9b10a4b6633b0b20402d4535eddc7604f707e1725cec057cbc2ee84fed3afba5f0d3cb457ec60f5afd5a6a529c3f3f808c15ea177a6451
+ languageName: node
+ linkType: hard
+
"@walletconnect/modal-core@npm:2.7.0":
version: 2.7.0
resolution: "@walletconnect/modal-core@npm:2.7.0"
@@ -6112,7 +6182,7 @@ __metadata:
languageName: node
linkType: hard
-"@walletconnect/modal@npm:^2.6.2":
+"@walletconnect/modal@npm:^2.6.2, @walletconnect/modal@npm:^2.7.0":
version: 2.7.0
resolution: "@walletconnect/modal@npm:2.7.0"
dependencies:
@@ -6145,6 +6215,19 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/relay-auth@npm:1.1.0":
+ version: 1.1.0
+ resolution: "@walletconnect/relay-auth@npm:1.1.0"
+ dependencies:
+ "@noble/curves": "npm:1.8.0"
+ "@noble/hashes": "npm:1.7.0"
+ "@walletconnect/safe-json": "npm:^1.0.1"
+ "@walletconnect/time": "npm:^1.0.2"
+ uint8arrays: "npm:^3.0.0"
+ checksum: 10/0fd6c2e05ced76fbc8e6a84c0a8e73458779662aea55568f51cd9066c337d8a12f2869f0bd717024bbe5955cc605241e68505ebac40406ed2a1bdacba42431b1
+ languageName: node
+ linkType: hard
+
"@walletconnect/safe-json@npm:1.0.2, @walletconnect/safe-json@npm:^1.0.1, @walletconnect/safe-json@npm:^1.0.2":
version: 1.0.2
resolution: "@walletconnect/safe-json@npm:1.0.2"
@@ -6171,6 +6254,23 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/sign-client@npm:2.22.4":
+ version: 2.22.4
+ resolution: "@walletconnect/sign-client@npm:2.22.4"
+ dependencies:
+ "@walletconnect/core": "npm:2.22.4"
+ "@walletconnect/events": "npm:1.0.1"
+ "@walletconnect/heartbeat": "npm:1.2.2"
+ "@walletconnect/jsonrpc-utils": "npm:1.0.8"
+ "@walletconnect/logger": "npm:3.0.0"
+ "@walletconnect/time": "npm:1.0.2"
+ "@walletconnect/types": "npm:2.22.4"
+ "@walletconnect/utils": "npm:2.22.4"
+ events: "npm:3.3.0"
+ checksum: 10/0361788319b983b90e79c4b86afe108eeece8dee9a053bab6e23727186041610ed173f3c8bab8127da6e2c1441d8ae3a90c002ddbbd256e0e0507a3a9783f1cc
+ languageName: node
+ linkType: hard
+
"@walletconnect/time@npm:1.0.2, @walletconnect/time@npm:^1.0.2":
version: 1.0.2
resolution: "@walletconnect/time@npm:1.0.2"
@@ -6194,6 +6294,20 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/types@npm:2.22.4, @walletconnect/types@npm:^2.22.4":
+ version: 2.22.4
+ resolution: "@walletconnect/types@npm:2.22.4"
+ dependencies:
+ "@walletconnect/events": "npm:1.0.1"
+ "@walletconnect/heartbeat": "npm:1.2.2"
+ "@walletconnect/jsonrpc-types": "npm:1.0.4"
+ "@walletconnect/keyvaluestorage": "npm:1.1.1"
+ "@walletconnect/logger": "npm:3.0.0"
+ events: "npm:3.3.0"
+ checksum: 10/d6f99583648726016ad273dc586bbdc123251b1b0bc2fa41b03e706598c5cf8a7ea7f07ac1b449cb6abe8626fc2f12957527cf55cdd8f3e5df2322496f2dedef
+ languageName: node
+ linkType: hard
+
"@walletconnect/universal-provider@npm:^2.11.2":
version: 2.17.2
resolution: "@walletconnect/universal-provider@npm:2.17.2"
@@ -6214,6 +6328,26 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/universal-provider@npm:^2.22.4":
+ version: 2.22.4
+ resolution: "@walletconnect/universal-provider@npm:2.22.4"
+ dependencies:
+ "@walletconnect/events": "npm:1.0.1"
+ "@walletconnect/jsonrpc-http-connection": "npm:1.0.8"
+ "@walletconnect/jsonrpc-provider": "npm:1.0.14"
+ "@walletconnect/jsonrpc-types": "npm:1.0.4"
+ "@walletconnect/jsonrpc-utils": "npm:1.0.8"
+ "@walletconnect/keyvaluestorage": "npm:1.1.1"
+ "@walletconnect/logger": "npm:3.0.0"
+ "@walletconnect/sign-client": "npm:2.22.4"
+ "@walletconnect/types": "npm:2.22.4"
+ "@walletconnect/utils": "npm:2.22.4"
+ es-toolkit: "npm:1.39.3"
+ events: "npm:3.3.0"
+ checksum: 10/5ed4a354015904289d8728f0cc189d57493d2af9221a8b06bbf20c6d644cecb08cab14060ce9d8129a3d4a38e4820a5e98ce8ae7736c6e051c65913222a2fbe8
+ languageName: node
+ linkType: hard
+
"@walletconnect/utils@npm:2.17.2":
version: 2.17.2
resolution: "@walletconnect/utils@npm:2.17.2"
@@ -6242,6 +6376,34 @@ __metadata:
languageName: node
linkType: hard
+"@walletconnect/utils@npm:2.22.4":
+ version: 2.22.4
+ resolution: "@walletconnect/utils@npm:2.22.4"
+ dependencies:
+ "@msgpack/msgpack": "npm:3.1.2"
+ "@noble/ciphers": "npm:1.3.0"
+ "@noble/curves": "npm:1.9.7"
+ "@noble/hashes": "npm:1.8.0"
+ "@scure/base": "npm:1.2.6"
+ "@walletconnect/jsonrpc-utils": "npm:1.0.8"
+ "@walletconnect/keyvaluestorage": "npm:1.1.1"
+ "@walletconnect/logger": "npm:3.0.0"
+ "@walletconnect/relay-api": "npm:1.0.11"
+ "@walletconnect/relay-auth": "npm:1.1.0"
+ "@walletconnect/safe-json": "npm:1.0.2"
+ "@walletconnect/time": "npm:1.0.2"
+ "@walletconnect/types": "npm:2.22.4"
+ "@walletconnect/window-getters": "npm:1.0.1"
+ "@walletconnect/window-metadata": "npm:1.0.1"
+ blakejs: "npm:1.2.1"
+ bs58: "npm:6.0.0"
+ detect-browser: "npm:5.3.0"
+ ox: "npm:0.9.3"
+ uint8arrays: "npm:3.1.1"
+ checksum: 10/8990552743ee23d2ff09e0ea0f19e70ae76e0833d2b2c74bcbe6c30b657dc4a800cb0affe07cb9000d51715f2e2617ee2b07883baffe468da3b1533fcb7939b3
+ languageName: node
+ linkType: hard
+
"@walletconnect/window-getters@npm:1.0.1, @walletconnect/window-getters@npm:^1.0.1":
version: 1.0.1
resolution: "@walletconnect/window-getters@npm:1.0.1"
@@ -6825,6 +6987,13 @@ __metadata:
languageName: node
linkType: hard
+"base-x@npm:^5.0.0":
+ version: 5.0.1
+ resolution: "base-x@npm:5.0.1"
+ checksum: 10/6e4f847ef842e0a71c6b6020a6ec482a2a5e727f5a98534dbfd5d5a4e8afbc0d1bdf1fd57174b3f0455d107f10a932c3c7710bec07e2878f80178607f8f605c8
+ languageName: node
+ linkType: hard
+
"base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
@@ -6871,6 +7040,13 @@ __metadata:
languageName: node
linkType: hard
+"blakejs@npm:1.2.1":
+ version: 1.2.1
+ resolution: "blakejs@npm:1.2.1"
+ checksum: 10/0638b1bd058b21892633929c43005aa6a4cc4b2ac5b338a146c3c076622f1b360795bd7a4d1f077c9b01863ed2df0c1504a81c5b520d164179120434847e6cd7
+ languageName: node
+ linkType: hard
+
"bn.js@npm:^4.11.9":
version: 4.12.1
resolution: "bn.js@npm:4.12.1"
@@ -6969,6 +7145,15 @@ __metadata:
languageName: node
linkType: hard
+"bs58@npm:6.0.0":
+ version: 6.0.0
+ resolution: "bs58@npm:6.0.0"
+ dependencies:
+ base-x: "npm:^5.0.0"
+ checksum: 10/7c9bb2b2d93d997a8c652de3510d89772007ac64ee913dc4e16ba7ff47624caad3128dcc7f360763eb6308760c300b3e9fd91b8bcbd489acd1a13278e7949c4e
+ languageName: node
+ linkType: hard
+
"buffer-from@npm:^1.0.0":
version: 1.1.2
resolution: "buffer-from@npm:1.1.2"
@@ -8827,6 +9012,18 @@ __metadata:
languageName: node
linkType: hard
+"es-toolkit@npm:1.39.3":
+ version: 1.39.3
+ resolution: "es-toolkit@npm:1.39.3"
+ dependenciesMeta:
+ "@trivago/prettier-plugin-sort-imports@4.3.0":
+ unplugged: true
+ prettier-plugin-sort-re-exports@0.0.1:
+ unplugged: true
+ checksum: 10/18cf6dee69170f802a50d447cac3af0026c199b501fe9a151633a402ea0523463b73aacbde53072cc610914d68031e2856fbb8a305b020e34bd7f6ac24d37e6d
+ languageName: node
+ linkType: hard
+
"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.46, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14, es5-ext@npm:~0.10.2":
version: 0.10.64
resolution: "es5-ext@npm:0.10.64"
@@ -13581,6 +13778,13 @@ __metadata:
languageName: node
linkType: hard
+"on-exit-leak-free@npm:^2.1.0":
+ version: 2.1.2
+ resolution: "on-exit-leak-free@npm:2.1.2"
+ checksum: 10/f7b4b7200026a08f6e4a17ba6d72e6c5cbb41789ed9cf7deaf9d9e322872c7dc5a7898549a894651ee0ee9ae635d34a678115bf8acdfba8ebd2ba2af688b563c
+ languageName: node
+ linkType: hard
+
"on-headers@npm:~1.1.0":
version: 1.1.0
resolution: "on-headers@npm:1.1.0"
@@ -13701,6 +13905,27 @@ __metadata:
languageName: node
linkType: hard
+"ox@npm:0.9.3":
+ version: 0.9.3
+ resolution: "ox@npm:0.9.3"
+ dependencies:
+ "@adraffy/ens-normalize": "npm:^1.11.0"
+ "@noble/ciphers": "npm:^1.3.0"
+ "@noble/curves": "npm:1.9.1"
+ "@noble/hashes": "npm:^1.8.0"
+ "@scure/bip32": "npm:^1.7.0"
+ "@scure/bip39": "npm:^1.6.0"
+ abitype: "npm:^1.0.9"
+ eventemitter3: "npm:5.0.1"
+ peerDependencies:
+ typescript: ">=5.4.0"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 10/c485c810d5b1e78fcb89d6f19dbf01ae90ad39d7746593620ccf4bf5ddb799b369dc3b56d625c80b9546ab2eb7231b841b46c4c6776f87926bde07999e0b82be
+ languageName: node
+ linkType: hard
+
"ox@npm:0.9.6":
version: 0.9.6
resolution: "ox@npm:0.9.6"
@@ -14107,6 +14332,15 @@ __metadata:
languageName: node
linkType: hard
+"pino-abstract-transport@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "pino-abstract-transport@npm:2.0.0"
+ dependencies:
+ split2: "npm:^4.0.0"
+ checksum: 10/e5699ecb06c7121055978e988e5cecea5b6892fc2589c64f1f86df5e7386bbbfd2ada268839e911b021c6b3123428aed7c6be3ac7940eee139556c75324c7e83
+ languageName: node
+ linkType: hard
+
"pino-abstract-transport@npm:v0.5.0":
version: 0.5.0
resolution: "pino-abstract-transport@npm:0.5.0"
@@ -14124,6 +14358,34 @@ __metadata:
languageName: node
linkType: hard
+"pino-std-serializers@npm:^7.0.0":
+ version: 7.0.0
+ resolution: "pino-std-serializers@npm:7.0.0"
+ checksum: 10/884e08f65aa5463d820521ead3779d4472c78fc434d8582afb66f9dcb8d8c7119c69524b68106cb8caf92c0487be7794cf50e5b9c0383ae65b24bf2a03480951
+ languageName: node
+ linkType: hard
+
+"pino@npm:10.0.0":
+ version: 10.0.0
+ resolution: "pino@npm:10.0.0"
+ dependencies:
+ atomic-sleep: "npm:^1.0.0"
+ on-exit-leak-free: "npm:^2.1.0"
+ pino-abstract-transport: "npm:^2.0.0"
+ pino-std-serializers: "npm:^7.0.0"
+ process-warning: "npm:^5.0.0"
+ quick-format-unescaped: "npm:^4.0.3"
+ real-require: "npm:^0.2.0"
+ safe-stable-stringify: "npm:^2.3.1"
+ slow-redact: "npm:^0.3.0"
+ sonic-boom: "npm:^4.0.1"
+ thread-stream: "npm:^3.0.0"
+ bin:
+ pino: bin.js
+ checksum: 10/32c7da274526706a2f1ad329bd44f2e2d2f0e0484e63dc290f8fb6986ed23e97ba5f30e6a80af3fa5f30a58e70a46d40a08b5731e1dfcb6f17a465f595fa361d
+ languageName: node
+ linkType: hard
+
"pino@npm:7.11.0":
version: 7.11.0
resolution: "pino@npm:7.11.0"
@@ -14290,6 +14552,13 @@ __metadata:
languageName: node
linkType: hard
+"process-warning@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "process-warning@npm:5.0.0"
+ checksum: 10/10f3e00ac9fc1943ec4566ff41fff2b964e660f853c283e622257719839d340b4616e707d62a02d6aa0038761bb1fa7c56bc7308d602d51bd96f05f9cd305dcd
+ languageName: node
+ linkType: hard
+
"promise-inflight@npm:^1.0.1":
version: 1.0.1
resolution: "promise-inflight@npm:1.0.1"
@@ -14791,6 +15060,13 @@ __metadata:
languageName: node
linkType: hard
+"real-require@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "real-require@npm:0.2.0"
+ checksum: 10/ddf44ee76301c774e9c9f2826da8a3c5c9f8fc87310f4a364e803ef003aa1a43c378b4323051ced212097fff1af459070f4499338b36a7469df1d4f7e8c0ba4c
+ languageName: node
+ linkType: hard
+
"redent@npm:^3.0.0":
version: 3.0.0
resolution: "redent@npm:3.0.0"
@@ -15238,7 +15514,7 @@ __metadata:
languageName: node
linkType: hard
-"safe-stable-stringify@npm:^2.1.0":
+"safe-stable-stringify@npm:^2.1.0, safe-stable-stringify@npm:^2.3.1":
version: 2.5.0
resolution: "safe-stable-stringify@npm:2.5.0"
checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c
@@ -15567,6 +15843,13 @@ __metadata:
languageName: node
linkType: hard
+"slow-redact@npm:^0.3.0":
+ version: 0.3.2
+ resolution: "slow-redact@npm:0.3.2"
+ checksum: 10/53889cc43128fd49dfa54dd39a858b215b075b0e2020f93fb0210bc419e5c0de9416300a885de8542238f253b622578ea8a58f9a55f548a9f3300119fd555064
+ languageName: node
+ linkType: hard
+
"smart-buffer@npm:^4.2.0":
version: 4.2.0
resolution: "smart-buffer@npm:4.2.0"
@@ -15633,6 +15916,15 @@ __metadata:
languageName: node
linkType: hard
+"sonic-boom@npm:^4.0.1":
+ version: 4.2.0
+ resolution: "sonic-boom@npm:4.2.0"
+ dependencies:
+ atomic-sleep: "npm:^1.0.0"
+ checksum: 10/385ef7fb5ea5976c1d2a1fef0b6df8df6b7caba8696d2d67f689d60c05e3ea2d536752ce7e1c69b9fad844635f1036d07c446f8e8149f5c6a80e0040a455b310
+ languageName: node
+ linkType: hard
+
"sonner@npm:^2.0.7":
version: 2.0.7
resolution: "sonner@npm:2.0.7"
@@ -16223,6 +16515,15 @@ __metadata:
languageName: node
linkType: hard
+"thread-stream@npm:^3.0.0":
+ version: 3.1.0
+ resolution: "thread-stream@npm:3.1.0"
+ dependencies:
+ real-require: "npm:^0.2.0"
+ checksum: 10/ea2d816c4f6077a7062fac5414a88e82977f807c82ee330938fb9691fe11883bb03f078551c0518bb649c239e47ba113d44014fcbb5db42c5abd5996f35e4213
+ languageName: node
+ linkType: hard
+
"throttle-debounce@npm:^3.0.1":
version: 3.0.1
resolution: "throttle-debounce@npm:3.0.1"
@@ -16718,6 +17019,9 @@ __metadata:
"@testing-library/react": "npm:^16.3.0"
"@types/react": "npm:^19.1.1"
"@vitest/ui": "npm:^3.2.4"
+ "@walletconnect/modal": "npm:^2.7.0"
+ "@walletconnect/types": "npm:^2.22.4"
+ "@walletconnect/universal-provider": "npm:^2.22.4"
fast-deep-equal: "npm:^3.1.3"
jotai: "npm:^2.15.0"
jsdom: "npm:^25.0.1"
@@ -16769,7 +17073,7 @@ __metadata:
languageName: node
linkType: hard
-"uint8arrays@npm:^3.0.0":
+"uint8arrays@npm:3.1.1, uint8arrays@npm:^3.0.0":
version: 3.1.1
resolution: "uint8arrays@npm:3.1.1"
dependencies: