@rango-dev/provider-walletconnect-2 0.57.0 → 0.57.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/adapter/adapter.d.ts +39 -0
  3. package/dist/adapter/adapter.d.ts.map +1 -0
  4. package/dist/adapter/index.d.ts +3 -0
  5. package/dist/adapter/index.d.ts.map +1 -0
  6. package/dist/adapter/modal-cache.d.ts +14 -0
  7. package/dist/adapter/modal-cache.d.ts.map +1 -0
  8. package/dist/adapter/modal.d.ts +25 -0
  9. package/dist/adapter/modal.d.ts.map +1 -0
  10. package/dist/adapter/networks.d.ts +60 -0
  11. package/dist/adapter/networks.d.ts.map +1 -0
  12. package/dist/adapter/provider-client.d.ts +3 -0
  13. package/dist/adapter/provider-client.d.ts.map +1 -0
  14. package/dist/adapter/registry.d.ts +4 -0
  15. package/dist/adapter/registry.d.ts.map +1 -0
  16. package/dist/adapter/session-cache.d.ts +17 -0
  17. package/dist/adapter/session-cache.d.ts.map +1 -0
  18. package/dist/adapter/task-queue.d.ts +11 -0
  19. package/dist/adapter/task-queue.d.ts.map +1 -0
  20. package/dist/chunk-GEAT645O.js +2 -0
  21. package/dist/chunk-GEAT645O.js.map +7 -0
  22. package/dist/constants.d.ts +4 -48
  23. package/dist/constants.d.ts.map +1 -0
  24. package/dist/evm-3KFL5N77.js +2 -0
  25. package/dist/evm-3KFL5N77.js.map +7 -0
  26. package/dist/mod.d.ts +4 -0
  27. package/dist/mod.d.ts.map +1 -0
  28. package/dist/mod.js +2 -0
  29. package/dist/mod.js.map +7 -0
  30. package/dist/namespaces/evm/hooks.d.ts +18 -0
  31. package/dist/namespaces/evm/hooks.d.ts.map +1 -0
  32. package/dist/namespaces/evm/namespace.d.ts +4 -0
  33. package/dist/namespaces/evm/namespace.d.ts.map +1 -0
  34. package/dist/namespaces/subscriber.d.ts +24 -0
  35. package/dist/namespaces/subscriber.d.ts.map +1 -0
  36. package/dist/provider.d.ts +3 -0
  37. package/dist/provider.d.ts.map +1 -0
  38. package/dist/session/accounts.d.ts +17 -0
  39. package/dist/session/accounts.d.ts.map +1 -0
  40. package/dist/session/bip122.d.ts +23 -0
  41. package/dist/session/bip122.d.ts.map +1 -0
  42. package/dist/session/chain-state.d.ts +6 -0
  43. package/dist/session/chain-state.d.ts.map +1 -0
  44. package/dist/session/evm-chain.d.ts +29 -0
  45. package/dist/session/evm-chain.d.ts.map +1 -0
  46. package/dist/session/evm.d.ts +25 -0
  47. package/dist/session/evm.d.ts.map +1 -0
  48. package/dist/session/lifecycle.d.ts +30 -0
  49. package/dist/session/lifecycle.d.ts.map +1 -0
  50. package/dist/session/lookup.d.ts +6 -0
  51. package/dist/session/lookup.d.ts.map +1 -0
  52. package/dist/session/proposals.d.ts +27 -0
  53. package/dist/session/proposals.d.ts.map +1 -0
  54. package/dist/session/restore-cache.d.ts +6 -0
  55. package/dist/session/restore-cache.d.ts.map +1 -0
  56. package/dist/session/teardown.d.ts +24 -0
  57. package/dist/session/teardown.d.ts.map +1 -0
  58. package/dist/signer.d.ts +2 -2
  59. package/dist/signer.d.ts.map +1 -0
  60. package/dist/signers/evm.d.ts +6 -4
  61. package/dist/signers/evm.d.ts.map +1 -0
  62. package/dist/types.d.ts +28 -11
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/utils.d.ts +17 -0
  65. package/dist/utils.d.ts.map +1 -0
  66. package/dist/wcConstants.d.ts +61 -0
  67. package/dist/wcConstants.d.ts.map +1 -0
  68. package/package.json +18 -13
  69. package/readme.md +14 -2
  70. package/src/adapter/adapter.ts +289 -0
  71. package/src/adapter/index.ts +2 -0
  72. package/src/adapter/modal-cache.ts +23 -0
  73. package/src/adapter/modal.ts +121 -0
  74. package/src/adapter/networks.ts +85 -0
  75. package/src/adapter/provider-client.ts +13 -0
  76. package/src/adapter/registry.ts +17 -0
  77. package/src/adapter/session-cache.ts +38 -0
  78. package/src/adapter/task-queue.ts +32 -0
  79. package/src/constants.ts +41 -76
  80. package/src/mod.ts +10 -0
  81. package/src/namespaces/evm/hooks.ts +148 -0
  82. package/src/namespaces/evm/namespace.ts +102 -0
  83. package/src/namespaces/subscriber.ts +106 -0
  84. package/src/provider.ts +35 -0
  85. package/src/session/accounts.ts +44 -0
  86. package/src/session/bip122.ts +225 -0
  87. package/src/session/chain-state.ts +38 -0
  88. package/src/session/evm-chain.ts +185 -0
  89. package/src/session/evm.ts +198 -0
  90. package/src/session/lifecycle.ts +241 -0
  91. package/src/session/lookup.ts +54 -0
  92. package/src/session/proposals.ts +96 -0
  93. package/src/session/restore-cache.ts +38 -0
  94. package/src/session/teardown.ts +181 -0
  95. package/src/signer.ts +9 -17
  96. package/src/signers/evm.ts +19 -17
  97. package/src/types.ts +31 -12
  98. package/src/utils.ts +68 -0
  99. package/src/wcConstants.ts +94 -0
  100. package/dist/chunk-6V3WUFOU.js +0 -2
  101. package/dist/chunk-6V3WUFOU.js.map +0 -7
  102. package/dist/chunk-BPSEZJUF.js +0 -2
  103. package/dist/chunk-BPSEZJUF.js.map +0 -7
  104. package/dist/chunk-DXVBX5XQ.js +0 -2
  105. package/dist/chunk-DXVBX5XQ.js.map +0 -7
  106. package/dist/dist-6GI3ECE5.js +0 -139
  107. package/dist/dist-6GI3ECE5.js.map +0 -7
  108. package/dist/evm-3ID4XY4Y.js +0 -2
  109. package/dist/evm-3ID4XY4Y.js.map +0 -7
  110. package/dist/helpers.d.ts +0 -31
  111. package/dist/index.d.ts +0 -21
  112. package/dist/index.js +0 -2
  113. package/dist/index.js.map +0 -7
  114. package/dist/session.d.ts +0 -63
  115. package/dist/signers/solana.d.ts +0 -14
  116. package/dist/solana-MFA7C5XI.js +0 -2
  117. package/dist/solana-MFA7C5XI.js.map +0 -7
  118. package/src/helpers.ts +0 -238
  119. package/src/index.ts +0 -234
  120. package/src/session.ts +0 -376
  121. package/src/signers/solana.ts +0 -160
  122. package/src/wc-types.d.ts +0 -31
@@ -0,0 +1,121 @@
1
+ import type { WalletConnectNamespace } from '../types.js';
2
+ import type { AppKitNetwork } from '@reown/appkit-common';
3
+ import type UniversalProvider from '@walletconnect/universal-provider';
4
+
5
+ import { createAppKit } from '@reown/appkit/core';
6
+
7
+ import { bitcoin, mainnet } from './networks.js';
8
+
9
+ export type AppKitModal = ReturnType<typeof createAppKitModal>;
10
+
11
+ const DEFAULT_MODAL_Z_INDEX = 999_999_999;
12
+
13
+ export type ModalParams = {
14
+ projectId: string;
15
+ universalProvider: UniversalProvider;
16
+ themeMode?: 'light' | 'dark';
17
+ zIndex?: number;
18
+ };
19
+
20
+ /*
21
+ * Every WalletConnect namespace shares one AppKit instance with all networks
22
+ * registered up front. A per-namespace instance had to be rebuilt on each
23
+ * switch, and every rebuild re-ran AppKit init against its module-level
24
+ * singleton controllers - leaving router history from the previous open mixed
25
+ * into the next (a stray back button in the modal header). The active network
26
+ * is chosen per connect with `switchNetwork` instead.
27
+ *
28
+ * The active network is now shared mutable state, so this is only safe while
29
+ * connects are serialized (the adapter's `#sessionQueue`): the switch/open/close
30
+ * for one namespace never interleaves with another's. A connect path that
31
+ * bypassed that queue would race this switch.
32
+ */
33
+ const NETWORKS: [AppKitNetwork, ...AppKitNetwork[]] = [mainnet, bitcoin];
34
+
35
+ /**
36
+ * The network to make active before opening the modal for a namespace. Keyed by
37
+ * `WalletConnectNamespace` so a future namespace is a compile error until mapped.
38
+ */
39
+ const NAMESPACE_NETWORK: Record<WalletConnectNamespace, AppKitNetwork> = {
40
+ evm: mainnet,
41
+ utxo: bitcoin,
42
+ };
43
+
44
+ /**
45
+ * Make a namespace's network active before opening the shared modal.
46
+ *
47
+ * `open()` compares the requested namespace against AppKit's active chain: if
48
+ * they differ it treats the open as a network switch and pushes an extra step
49
+ * onto the router, which leaves a stray back button in the modal header. One
50
+ * shared instance keeps whatever chain the previous connect left active, so we
51
+ * switch it to the target here first - then `open()` sees a matching chain and
52
+ * takes its clean, non-switching path.
53
+ *
54
+ * `ready()` first so AppKit init has registered the networks `switchNetwork`
55
+ * looks up.
56
+ */
57
+ export async function prepareModalForNamespace(
58
+ web3Modal: AppKitModal,
59
+ namespace: WalletConnectNamespace
60
+ ): Promise<void> {
61
+ await web3Modal.ready();
62
+ await web3Modal.switchNetwork(NAMESPACE_NETWORK[namespace]);
63
+ }
64
+
65
+ export function createAppKitModal({
66
+ projectId,
67
+ universalProvider,
68
+ themeMode = 'light',
69
+ zIndex = DEFAULT_MODAL_Z_INDEX,
70
+ }: ModalParams) {
71
+ return createAppKit({
72
+ projectId,
73
+ networks: NETWORKS,
74
+ universalProvider,
75
+ /*
76
+ * AppKit is intentionally kept out of the WalletConnect session lifecycle -
77
+ * it renders the modal (QR + wallet list), nothing more. We drive the
78
+ * handshake ourselves with `client.connect()` (see `session/lifecycle.ts`).
79
+ *
80
+ * Why: AppKit models one unified, multi-chain session with a single active
81
+ * chain and its own reconnect/sync. The hub needs the opposite - EVM and
82
+ * UTXO as independent sessions that connect, disconnect and eager-restore
83
+ * separately (the adapter keeps one cache entry per namespace). Letting
84
+ * AppKit drive via `provider.connect()` accumulates namespaces into a single
85
+ * proposal, so connecting BTC after EVM asks the wallet for Ethereum - it was
86
+ * tried and reverted. `manualWCControl: true` is AppKit's supported flag for
87
+ * exactly this "you render, I connect" split.
88
+ *
89
+ * The visible cost: because we own the lifecycle, `provider.session` is never
90
+ * set. With `enableReconnect: false`, AppKit init runs
91
+ * `unSyncExistingConnection()`, whose `provider.disconnect()` hits a
92
+ * `if (!this.session) throw` guard and AppKit logs the caught error as
93
+ * `UniversalAdapter:disconnect - error: Please call connect() before
94
+ * enable()`. It is expected and harmless - AppKit looking for a session to
95
+ * clean up, finding none.
96
+ *
97
+ * Do NOT "fix" the log. Enabling reconnect makes AppKit actively restore/sync
98
+ * connections it doesn't own, racing our own restore path and serialize
99
+ * queue. Assigning `provider.session` removes the guard so that disconnect
100
+ * (and every other AppKit-initiated disconnect) succeeds for real, tearing
101
+ * down a live session on AppKit's schedule instead of ours. Both trade a
102
+ * cosmetic log for real state conflicts.
103
+ */
104
+ enableReconnect: false,
105
+ defaultNetwork: mainnet,
106
+ defaultAccountTypes: { bip122: 'payment' },
107
+ manualWCControl: true,
108
+ /*
109
+ * We register only one EVM network (`mainnet`) but the session approves every
110
+ * EVM chain, so a network switch can land on a chain AppKit considers
111
+ * "unsupported" and it auto-opens its UnsupportedChain modal
112
+ * (ChainController.showUnsupportedChainUI). Allow it so AppKit doesn't police
113
+ * the network we manage ourselves.
114
+ */
115
+ allowUnsupportedChain: true,
116
+ themeMode,
117
+ themeVariables: {
118
+ '--apkt-z-index': zIndex,
119
+ },
120
+ });
121
+ }
@@ -0,0 +1,85 @@
1
+ import type { AppKitNetwork, CaipNetwork } from '@reown/appkit-common';
2
+
3
+ /*
4
+ * The two networks AppKit renders the modal for, defined here instead of
5
+ * imported from `@reown/appkit/networks`.
6
+ *
7
+ * That entry point is `export * from 'viem/chains'` plus AppKit's own non-EVM
8
+ * chains, so importing a single name off it pulls every viem chain definition
9
+ * into the consumer's bundle. One of them (`tempo`) re-exports viem's tempo
10
+ * chainConfig, which reaches `ox`'s salt-mining worker pool - and that module
11
+ * routes `await import(id)` through a variable so bundlers skip
12
+ * `node:worker_threads` in the browser. webpack can't resolve the expression
13
+ * and warns "Critical dependency: the request of a dependency is an
14
+ * expression"; react-scripts promotes warnings to errors on CI, which fails
15
+ * the build for anyone consuming the widget through Create React App.
16
+ *
17
+ * viem publishes no per-chain subpath (`./chains` is the only export), so
18
+ * there is no narrower import to reach for - the definitions are plain data,
19
+ * so we carry the two we need.
20
+ *
21
+ * Keep these in sync with upstream if AppKit's `bitcoin` or viem's `mainnet`
22
+ * changes: `@reown/appkit/dist/esm/src/networks/bitcoin.js` and
23
+ * `viem/_esm/chains/definitions/mainnet.js`.
24
+ */
25
+
26
+ /**
27
+ * Mirrors `defineChain` from both `viem/chains` and AppKit's networks module -
28
+ * they are the same three-key spread. Declaring the viem-only formatter fields
29
+ * keeps the objects structurally identical to the ones AppKit used to receive.
30
+ */
31
+ function defineChain<const chain extends AppKitNetwork>(chain: chain) {
32
+ return {
33
+ formatters: undefined,
34
+ fees: undefined,
35
+ serializers: undefined,
36
+ ...chain,
37
+ };
38
+ }
39
+
40
+ /** viem's `mainnet`, the only EVM network we register. */
41
+ export const mainnet = defineChain({
42
+ id: 1,
43
+ caipNetworkId: 'eip155:1',
44
+ chainNamespace: 'eip155',
45
+ name: 'Ethereum',
46
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
47
+ rpcUrls: {
48
+ default: {
49
+ http: ['https://eth.merkle.io'],
50
+ },
51
+ },
52
+ blockExplorers: {
53
+ default: {
54
+ name: 'Etherscan',
55
+ url: 'https://etherscan.io',
56
+ apiUrl: 'https://api.etherscan.io/api',
57
+ },
58
+ },
59
+ contracts: {
60
+ ensUniversalResolver: {
61
+ address: '0xeeeeeeee14d718c2b47d9923deab1335e144eeee',
62
+ blockCreated: 23_085_558,
63
+ },
64
+ multicall3: {
65
+ address: '0xca11bde05977b3631167028862be2a173976ca11',
66
+ blockCreated: 14_353_601,
67
+ },
68
+ },
69
+ } satisfies CaipNetwork);
70
+
71
+ /** AppKit's `bitcoin`, the only UTXO network we register. */
72
+ export const bitcoin = defineChain({
73
+ id: '000000000019d6689c085ae165831e93',
74
+ caipNetworkId: 'bip122:000000000019d6689c085ae165831e93',
75
+ chainNamespace: 'bip122',
76
+ name: 'Bitcoin',
77
+ nativeCurrency: {
78
+ name: 'Bitcoin',
79
+ symbol: 'BTC',
80
+ decimals: 8,
81
+ },
82
+ rpcUrls: {
83
+ default: { http: ['https://rpc.walletconnect.org/v1'] },
84
+ },
85
+ } satisfies CaipNetwork);
@@ -0,0 +1,13 @@
1
+ import UniversalProvider from '@walletconnect/universal-provider';
2
+
3
+ import { DEFAULT_APP_METADATA, RELAY_URL } from '../wcConstants.js';
4
+
5
+ export async function createUniversalProvider(
6
+ projectId: string
7
+ ): Promise<UniversalProvider> {
8
+ return UniversalProvider.init({
9
+ relayUrl: RELAY_URL,
10
+ projectId,
11
+ metadata: DEFAULT_APP_METADATA,
12
+ });
13
+ }
@@ -0,0 +1,17 @@
1
+ import type { WalletConnectAdapter } from './adapter.js';
2
+
3
+ /**
4
+ * Holds the singleton adapter instance created in the provider's `init`.
5
+ *
6
+ * This lives in its own leaf module (importing nothing from `provider.ts` or the
7
+ * namespaces) to break the `provider -> namespace -> provider` import cycle that
8
+ * otherwise throws a TDZ error ("Cannot access 'getAdapter' before initialization")
9
+ * when the namespace module evaluates its top-level subscriber factories.
10
+ */
11
+ let adapter: WalletConnectAdapter;
12
+
13
+ export const getAdapter = () => adapter;
14
+
15
+ export const setAdapter = (instance: WalletConnectAdapter) => {
16
+ adapter = instance;
17
+ };
@@ -0,0 +1,38 @@
1
+ import type { WalletConnectNamespace } from '../types.js';
2
+ import type { SessionTypes } from '@walletconnect/types';
3
+
4
+ export type SessionCache = {
5
+ get(namespace: WalletConnectNamespace): SessionTypes.Struct | null;
6
+ set(namespace: WalletConnectNamespace, session: SessionTypes.Struct): void;
7
+ clear(namespace: WalletConnectNamespace): void;
8
+ /**
9
+ * Drops every namespace holding `topic`. A wallet may approve more than was
10
+ * proposed, so one settled session can back both namespaces - tearing its topic
11
+ * down under one of them would otherwise leave the other pointing at a topic
12
+ * the relay has already dropped.
13
+ */
14
+ clearTopic(topic: string): void;
15
+ clearAll(): void;
16
+ };
17
+
18
+ export function createSessionCache(): SessionCache {
19
+ const sessions = new Map<WalletConnectNamespace, SessionTypes.Struct>();
20
+
21
+ return {
22
+ get: (namespace) => sessions.get(namespace) ?? null,
23
+
24
+ set: (namespace, session) => void sessions.set(namespace, session),
25
+
26
+ clear: (namespace) => void sessions.delete(namespace),
27
+
28
+ clearTopic: (topic) => {
29
+ for (const [namespace, session] of sessions) {
30
+ if (session.topic === topic) {
31
+ sessions.delete(namespace);
32
+ }
33
+ }
34
+ },
35
+
36
+ clearAll: () => sessions.clear(),
37
+ };
38
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Runs tasks one at a time, in the order `run` was called.
3
+ *
4
+ * A task that rejects passes its rejection to its own caller and does not block
5
+ * the tasks behind it.
6
+ */
7
+ export class TaskQueue {
8
+ /** Resolves once the task currently at the back of the queue has settled. */
9
+ #tail: Promise<void> = Promise.resolve();
10
+
11
+ async run<T>(task: () => Promise<T>): Promise<T> {
12
+ const ourTurn = this.#tail;
13
+
14
+ /*
15
+ * Take our place at the back of the queue before awaiting anything, so
16
+ * callers arriving in the same tick line up behind us rather than all
17
+ * reading the same `#tail`. `releaseOurTurn` never rejects, which is what
18
+ * lets the next task run even when this one throws.
19
+ */
20
+ let releaseOurTurn!: () => void;
21
+ this.#tail = new Promise<void>((resolve) => {
22
+ releaseOurTurn = resolve;
23
+ });
24
+
25
+ await ourTurn;
26
+ try {
27
+ return await task();
28
+ } finally {
29
+ releaseOurTurn();
30
+ }
31
+ }
32
+ }
package/src/constants.ts CHANGED
@@ -1,77 +1,42 @@
1
- import { Networks } from '@rango-dev/wallets-shared';
2
-
3
- export const DEFAULT_NETWORK = Networks.ETHEREUM;
4
- export const PING_TIMEOUT = 10_000;
5
-
6
- export enum NAMESPACES {
7
- ETHEREUM = 'eip155',
8
- SOLANA = 'solana',
9
- POLKADOT = 'polkadot',
10
- CARDANO = 'cip34',
11
- ERLOND = 'elrond',
12
- MULTIVERSX = 'multiversx',
13
- }
14
-
15
- export const CHAIN_ID_STORAGE = 'wc@2:client//namespaces';
16
-
17
- // Refrence: https://docs.walletconnect.com/2.0/advanced/rpc-reference/solana-rpc
18
- export enum SolanaRPCMethods {
19
- GET_ACCOUNTS = 'solana_getAccounts',
20
- REQUEST_ACCOUNTS = 'solana_requestAccounts',
21
- SIGN_TRANSACTION = 'solana_signTransaction',
22
- SIGN_MESSAGE = 'solana_signMessage',
23
- }
24
-
25
- // Refrence: https://docs.walletconnect.com/2.0/advanced/rpc-reference/ethereum-rpc
26
- export enum EthereumRPCMethods {
27
- PERSONAL_SIGN = 'personal_sign',
28
- SIGN = 'eth_sign',
29
- SIGN_TYPED_DATA = 'eth_signTypedData',
30
- SIGN_TRANSACTION = 'eth_signTransaction',
31
- SEND_TRANSACTION = 'eth_sendTransaction',
32
- SEND_RAW_TRANSACTION = 'eth_sendRawTransaction',
33
- SWITCH_CHAIN = 'wallet_switchEthereumChain',
34
- ADD_CHAIN = 'wallet_addEthereumChain',
35
- GET_CHAIN = 'eth_chainId',
36
- }
37
-
38
- export enum StarknetRPCMethods {
39
- REQUEST_ADD_INVOKE_TRANSACTION = 'starknet_requestAddInvokeTransaction',
40
- SIGN_TYPED_DATA = 'starknet_signTypedData',
41
- }
42
-
43
- export enum EthereumEvents {
44
- CHAIN_CHANGED = 'chainChanged',
45
- ACCOUNTS_CHANGED = 'accountsChanged',
46
- }
47
-
48
- export const DEFAULT_ETHEREUM_EVENTS: EthereumEvents[] = [
49
- EthereumEvents.CHAIN_CHANGED,
50
- EthereumEvents.ACCOUNTS_CHANGED,
51
- ];
52
-
53
- export const DEFAULT_ETHEREUM_METHODS = [
54
- EthereumRPCMethods.PERSONAL_SIGN,
55
- EthereumRPCMethods.SEND_TRANSACTION,
56
- EthereumRPCMethods.SIGN_TRANSACTION,
57
- EthereumRPCMethods.SWITCH_CHAIN,
58
- EthereumRPCMethods.ADD_CHAIN,
59
- EthereumRPCMethods.GET_CHAIN,
60
- ];
61
-
62
- export const DEFAULT_SOLANA_METHODS = [
63
- SolanaRPCMethods.SIGN_TRANSACTION,
64
- SolanaRPCMethods.SIGN_MESSAGE,
65
- ];
66
-
67
- // refrence: https://github.com/ChainAgnostic/namespaces/blob/main/solana/caip2.md
68
- export const DEFAULT_SOLANA_CHAIN_ID = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
69
-
70
- export const DEFAULT_APP_METADATA = {
71
- name: 'Rango Exchange',
72
- description: 'The Ultimate Cross-Chain Solution',
73
- url: 'https://app.rango.exchange/',
74
- icons: ['https://app.rango.exchange/logo-rounded.png'],
1
+ import type { ProviderMetadata } from '@hub3js/core';
2
+
3
+ import { isEvmNamespace } from '@hub3js/evm';
4
+
5
+ import getSigners from './signer.js';
6
+
7
+ export const WALLET_ID = 'wallet-connect-2';
8
+
9
+ export const metadata: ProviderMetadata = {
10
+ name: 'WalletConnect',
11
+ icon: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/walletconnect/icon.svg',
12
+ extensions: {
13
+ homepage: 'https://walletconnect.com/',
14
+ },
15
+ properties: [
16
+ {
17
+ name: 'namespaces',
18
+ value: {
19
+ selection: 'single',
20
+ data: [
21
+ {
22
+ label: 'EVM',
23
+ value: 'EVM',
24
+ id: 'ETH',
25
+ isChainSupported: isEvmNamespace,
26
+ },
27
+ ],
28
+ },
29
+ },
30
+ {
31
+ name: 'signers',
32
+ value: { getSigners: async () => getSigners() },
33
+ },
34
+ {
35
+ name: 'details',
36
+ value: {
37
+ mobileWallet: true,
38
+ showOnMobile: true,
39
+ },
40
+ },
41
+ ],
75
42
  };
76
-
77
- export const RELAY_URL = 'wss://relay.walletconnect.com';
package/src/mod.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineVersions } from '@hub3js/core/utils';
2
+
3
+ import { buildProvider } from './provider.js';
4
+
5
+ export type { Environments } from './types.js';
6
+
7
+ const versions = () =>
8
+ defineVersions().version('1.0.0', buildProvider()).build();
9
+
10
+ export { versions };
@@ -0,0 +1,148 @@
1
+ import type { EvmActions } from '@hub3js/evm';
2
+
3
+ import { utils } from '@hub3js/evm';
4
+ import { ChainId } from 'caip';
5
+
6
+ import {
7
+ extractAddress,
8
+ getAccountsFromEvent,
9
+ } from '../../session/accounts.js';
10
+ import {
11
+ getPersistedChainId,
12
+ persistCurrentChainId,
13
+ } from '../../session/chain-state.js';
14
+ import { filterEvmAccounts } from '../../session/evm.js';
15
+ import { expireWalletConnectTopic } from '../../session/teardown.js';
16
+ import { chainReferenceToHex, parseChainReference } from '../../utils.js';
17
+ import { EthereumEvents, NAMESPACES } from '../../wcConstants.js';
18
+ import { createSessionSubscriber } from '../subscriber.js';
19
+
20
+ /**
21
+ * WC signals an account switch via `session_update` (re-shared namespaces with the
22
+ * newly-selected account first), so the active account is published from this event.
23
+ */
24
+ export function sessionUpdateSubscriber() {
25
+ return createSessionSubscriber<'session_update', EvmActions>(
26
+ 'evm',
27
+ 'session_update',
28
+ async ({ args, session, adapter, context }) => {
29
+ /*
30
+ * Keep the cached struct in step with what the wallet just re-shared.
31
+ * `switchEvmNetwork` reads the authorized chains and methods back out
32
+ * of it, so a stale copy rejects a chain the wallet has since added
33
+ * (`wallet_addEthereumChain` lands here as a `session_update`) with
34
+ * "Chain X is not configured".
35
+ */
36
+ if (args.params.namespaces[NAMESPACES.ETHEREUM]) {
37
+ adapter.cacheSession('evm', {
38
+ ...session,
39
+ namespaces: args.params.namespaces,
40
+ });
41
+ }
42
+
43
+ const allAccounts = getAccountsFromEvent(args);
44
+ if (!allAccounts.length) {
45
+ return;
46
+ }
47
+
48
+ const entries = allAccounts.map((accountsWithChain) => ({
49
+ address: accountsWithChain.accounts[0],
50
+ chainId: accountsWithChain.chainId,
51
+ }));
52
+
53
+ const [getState, setState] = context.state();
54
+ const hubNetwork = getState().network;
55
+ const persistedChain = await getPersistedChainId(
56
+ await adapter.getClient()
57
+ );
58
+ const activeReference =
59
+ parseChainReference(hubNetwork ?? undefined) ?? persistedChain;
60
+
61
+ let evmAccounts = filterEvmAccounts(entries, activeReference);
62
+ if (!evmAccounts.length) {
63
+ evmAccounts = filterEvmAccounts(entries);
64
+ }
65
+ if (!evmAccounts.length) {
66
+ return;
67
+ }
68
+
69
+ const chainIdHex = chainReferenceToHex(evmAccounts[0].chainId);
70
+ setState(
71
+ 'accounts',
72
+ utils.formatAccountsToCAIP([evmAccounts[0].accounts[0]], chainIdHex)
73
+ );
74
+ setState('network', chainIdHex);
75
+ }
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Addresses out of an `accountsChanged` payload. Spec says `string[]`, but wallets
81
+ * forwarding the EIP-1193 event as-is send a bare value, so the shape is coerced
82
+ * before anything reads it.
83
+ */
84
+ function eventAccounts(data: unknown): string[] {
85
+ return (Array.isArray(data) ? data : [data])
86
+ .filter((entry): entry is string => typeof entry === 'string' && !!entry)
87
+ .map(extractAddress);
88
+ }
89
+
90
+ /**
91
+ * Handles WC `session_event` payloads: EIP-1193 `accountsChanged` (publish the new
92
+ * accounts, or disconnect when the wallet revokes them all) and `chainChanged`
93
+ * (update and persist the active chain). Any other event is ignored.
94
+ */
95
+ export function sessionEventSubscriber() {
96
+ return createSessionSubscriber<'session_event', EvmActions>(
97
+ 'evm',
98
+ 'session_event',
99
+ ({ args, adapter, context }) => {
100
+ const [, setState] = context.state();
101
+
102
+ if (args.params.event.name === EthereumEvents.ACCOUNTS_CHANGED) {
103
+ const accounts = eventAccounts(args.params.event.data);
104
+ /*
105
+ * An empty payload is the wallet revoking access (locked, or the account
106
+ * unlinked from this dApp), not a switch - there is nothing to publish.
107
+ */
108
+ if (!accounts.length) {
109
+ void context.action('disconnect');
110
+ return;
111
+ }
112
+
113
+ const chainIdHex = chainReferenceToHex(
114
+ ChainId.parse(args.params.chainId).reference
115
+ );
116
+ setState('network', chainIdHex);
117
+ setState('accounts', utils.formatAccountsToCAIP(accounts, chainIdHex));
118
+ return;
119
+ }
120
+
121
+ if (args.params.event.name === EthereumEvents.CHAIN_CHANGED) {
122
+ const chainIdHex = chainReferenceToHex(args.params.event.data);
123
+ setState('network', chainIdHex);
124
+ void adapter
125
+ .getClient()
126
+ .then(async (client) => persistCurrentChainId(client, chainIdHex));
127
+ }
128
+ }
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Reflects a wallet-initiated `session_delete` into the hub: clears the cached
134
+ * session, expires the WC topic, and runs the hub `disconnect` action.
135
+ */
136
+ export function sessionDeleteSubscriber() {
137
+ return createSessionSubscriber<'session_delete', EvmActions>(
138
+ 'evm',
139
+ 'session_delete',
140
+ ({ args, adapter, context }) => {
141
+ adapter.clearSession('evm');
142
+ void adapter
143
+ .getClient()
144
+ .then(async (client) => expireWalletConnectTopic(client, args.topic));
145
+ context.action('disconnect');
146
+ }
147
+ );
148
+ }