@tomo-inc/wallet-adaptor-base 0.0.1

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 (171) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +60 -0
  3. package/dist/index.cjs +2485 -0
  4. package/dist/index.d.cts +184 -0
  5. package/dist/index.d.ts +184 -0
  6. package/dist/index.js +2471 -0
  7. package/package.json +31 -0
  8. package/project.json +67 -0
  9. package/src/chains/type.ts +3 -0
  10. package/src/chains/utils.ts +20 -0
  11. package/src/index.ts +93 -0
  12. package/src/svg.d.ts +4 -0
  13. package/src/type.ts +99 -0
  14. package/src/utils/browsers.ts +39 -0
  15. package/src/utils/chainId.ts +27 -0
  16. package/src/utils/isMobile.ts +22 -0
  17. package/src/utils/utils.ts +14 -0
  18. package/src/wallet-api/balance.ts +96 -0
  19. package/src/wallet-api/chain.ts +59 -0
  20. package/src/wallet-api/connect.ts +65 -0
  21. package/src/wallet-api/index.ts +5 -0
  22. package/src/wallet-api/sign-in.ts +103 -0
  23. package/src/wallet-api/sign-message.ts +60 -0
  24. package/src/wallets/Wallet.ts +37 -0
  25. package/src/wallets/default/backpackWallet/backpackWallet.svg +1 -0
  26. package/src/wallets/default/backpackWallet/backpackWallet.ts +23 -0
  27. package/src/wallets/default/berasigWallet/berasigWallet.svg +1 -0
  28. package/src/wallets/default/berasigWallet/berasigWallet.ts +22 -0
  29. package/src/wallets/default/binanceWallet/binanceWallet.svg +1 -0
  30. package/src/wallets/default/binanceWallet/binanceWallet.ts +23 -0
  31. package/src/wallets/default/bitgetWallet/bitgetWallet.svg +4 -0
  32. package/src/wallets/default/bitgetWallet/bitgetWallet.ts +30 -0
  33. package/src/wallets/default/bitskiWallet/bitskiWallet.svg +1 -0
  34. package/src/wallets/default/bitskiWallet/bitskiWallet.ts +17 -0
  35. package/src/wallets/default/bitverseWallet/bitverseWallet.svg +1 -0
  36. package/src/wallets/default/bitverseWallet/bitverseWallet.ts +17 -0
  37. package/src/wallets/default/bloomWallet/bloomWallet.svg +1 -0
  38. package/src/wallets/default/bloomWallet/bloomWallet.ts +15 -0
  39. package/src/wallets/default/braveWallet/braveWallet.svg +1 -0
  40. package/src/wallets/default/braveWallet/braveWallet.ts +20 -0
  41. package/src/wallets/default/bybitWallet/bybitWallet.svg +1 -0
  42. package/src/wallets/default/bybitWallet/bybitWallet.ts +23 -0
  43. package/src/wallets/default/clvWallet/clvWallet.svg +1 -0
  44. package/src/wallets/default/clvWallet/clvWallet.ts +21 -0
  45. package/src/wallets/default/coin98Wallet/coin98Wallet.svg +1 -0
  46. package/src/wallets/default/coin98Wallet/coin98Wallet.ts +25 -0
  47. package/src/wallets/default/coinbaseWallet/coinbaseWallet.svg +4 -0
  48. package/src/wallets/default/coinbaseWallet/coinbaseWallet.ts +25 -0
  49. package/src/wallets/default/compassWallet/compassWallet.svg +1 -0
  50. package/src/wallets/default/compassWallet/compassWallet.ts +19 -0
  51. package/src/wallets/default/coreWallet/coreWallet.svg +7 -0
  52. package/src/wallets/default/coreWallet/coreWallet.ts +24 -0
  53. package/src/wallets/default/ctrlWallet/ctrlWallet.svg +13 -0
  54. package/src/wallets/default/ctrlWallet/ctrlWallet.ts +24 -0
  55. package/src/wallets/default/dawnWallet/dawnWallet.svg +23 -0
  56. package/src/wallets/default/dawnWallet/dawnWallet.ts +16 -0
  57. package/src/wallets/default/desigWallet/desigWallet.svg +1 -0
  58. package/src/wallets/default/desigWallet/desigWallet.ts +21 -0
  59. package/src/wallets/default/enkryptWallet/enkryptWallet.svg +10 -0
  60. package/src/wallets/default/enkryptWallet/enkryptWallet.ts +24 -0
  61. package/src/wallets/default/foxWallet/foxWallet.svg +6 -0
  62. package/src/wallets/default/foxWallet/foxWallet.ts +19 -0
  63. package/src/wallets/default/frameWallet/frameWallet.svg +1 -0
  64. package/src/wallets/default/frameWallet/frameWallet.ts +17 -0
  65. package/src/wallets/default/gateWallet/gateWallet.svg +1 -0
  66. package/src/wallets/default/gateWallet/gateWallet.ts +24 -0
  67. package/src/wallets/default/imTokenWallet/imTokenWallet.svg +10 -0
  68. package/src/wallets/default/imTokenWallet/imTokenWallet.ts +18 -0
  69. package/src/wallets/default/injectedWallet/injectedWallet.svg +1 -0
  70. package/src/wallets/default/injectedWallet/injectedWallet.ts +12 -0
  71. package/src/wallets/default/iopayWallet/iopayWallet.svg +1 -0
  72. package/src/wallets/default/iopayWallet/iopayWallet.ts +18 -0
  73. package/src/wallets/default/kaiaWallet/kaiaWallet.svg +16 -0
  74. package/src/wallets/default/kaiaWallet/kaiaWallet.ts +22 -0
  75. package/src/wallets/default/kaikasWallet/kaikasWallet.svg +1 -0
  76. package/src/wallets/default/kaikasWallet/kaikasWallet.ts +22 -0
  77. package/src/wallets/default/krakenWallet/krakenWallet.svg +1 -0
  78. package/src/wallets/default/krakenWallet/krakenWallet.ts +17 -0
  79. package/src/wallets/default/kresusWallet/kresusWallet.svg +1 -0
  80. package/src/wallets/default/kresusWallet/kresusWallet.ts +17 -0
  81. package/src/wallets/default/ledgerWallet/ledgerWallet.svg +1 -0
  82. package/src/wallets/default/ledgerWallet/ledgerWallet.ts +23 -0
  83. package/src/wallets/default/magicEdenWallet/magicEden.svg +1 -0
  84. package/src/wallets/default/magicEdenWallet/magicEdenWallet.ts +25 -0
  85. package/src/wallets/default/mathWallet/icon.ts +2 -0
  86. package/src/wallets/default/mathWallet/mathWallet.ts +24 -0
  87. package/src/wallets/default/metaMaskWallet/icon.ts +2 -0
  88. package/src/wallets/default/metaMaskWallet/metaMaskWallet.svg +32 -0
  89. package/src/wallets/default/metaMaskWallet/metaMaskWallet.ts +88 -0
  90. package/src/wallets/default/myDogeWallet/icon.ts +2 -0
  91. package/src/wallets/default/myDogeWallet/mydogeWallet.svg +190 -0
  92. package/src/wallets/default/myDogeWallet/mydogeWallet.ts +20 -0
  93. package/src/wallets/default/nestWallet/nestWallet.svg +1 -0
  94. package/src/wallets/default/nestWallet/nestWallet.ts +17 -0
  95. package/src/wallets/default/novaWallet/novaWallet.svg +19 -0
  96. package/src/wallets/default/novaWallet/novaWallet.ts +30 -0
  97. package/src/wallets/default/oktoWallet/oktoWallet.svg +1 -0
  98. package/src/wallets/default/oktoWallet/oktoWallet.ts +18 -0
  99. package/src/wallets/default/okxWallet/okxWallet.svg +1 -0
  100. package/src/wallets/default/okxWallet/okxWallet.ts +32 -0
  101. package/src/wallets/default/omniWallet/omniWallet.svg +5 -0
  102. package/src/wallets/default/omniWallet/omniWallet.ts +18 -0
  103. package/src/wallets/default/oneInchWallet/oneInchWallet.svg +1 -0
  104. package/src/wallets/default/oneInchWallet/oneInchWallet.ts +18 -0
  105. package/src/wallets/default/oneKeyWallet/oneKeyWallet.svg +1 -0
  106. package/src/wallets/default/oneKeyWallet/oneKeyWallet.ts +25 -0
  107. package/src/wallets/default/paraSwapWallet/paraSwapWallet.svg +1 -0
  108. package/src/wallets/default/paraSwapWallet/paraswapWallet.ts +17 -0
  109. package/src/wallets/default/phantomWallet/phantomWallet.svg +1 -0
  110. package/src/wallets/default/phantomWallet/phantomWallet.ts +30 -0
  111. package/src/wallets/default/rabbyWallet/rabbyWallet.svg +1 -0
  112. package/src/wallets/default/rabbyWallet/rabbyWallet.ts +18 -0
  113. package/src/wallets/default/rainbowWallet/rainbowWallet.svg +54 -0
  114. package/src/wallets/default/rainbowWallet/rainbowWallet.ts +24 -0
  115. package/src/wallets/default/ramperWallet/ramperWallet.svg +1 -0
  116. package/src/wallets/default/ramperWallet/ramperWallet.ts +19 -0
  117. package/src/wallets/default/readyWallet/readyWallet.svg +4 -0
  118. package/src/wallets/default/readyWallet/readyWallet.ts +18 -0
  119. package/src/wallets/default/roninWallet/roninWallet.svg +1 -0
  120. package/src/wallets/default/roninWallet/roninWallet.ts +25 -0
  121. package/src/wallets/default/safeWallet/safeWallet.svg +6 -0
  122. package/src/wallets/default/safeWallet/safeWallet.ts +18 -0
  123. package/src/wallets/default/safeheronWallet/safeheronWallet.svg +1 -0
  124. package/src/wallets/default/safeheronWallet/safeheronWallet.ts +17 -0
  125. package/src/wallets/default/safepalWallet/safepalWallet.svg +1 -0
  126. package/src/wallets/default/safepalWallet/safepalWallet.ts +24 -0
  127. package/src/wallets/default/seifWallet/seifWallet.svg +12 -0
  128. package/src/wallets/default/seifWallet/seifWallet.ts +18 -0
  129. package/src/wallets/default/solflareWallet/icon.ts +2 -0
  130. package/src/wallets/default/solflareWallet/solflareWallet.ts +23 -0
  131. package/src/wallets/default/subWallet/subWallet.svg +1 -0
  132. package/src/wallets/default/subWallet/subWallet.ts +25 -0
  133. package/src/wallets/default/tahoWallet/tahoWallet.svg +1 -0
  134. package/src/wallets/default/tahoWallet/tahoWallet.ts +19 -0
  135. package/src/wallets/default/talismanWallet/talismanWallet.svg +1 -0
  136. package/src/wallets/default/talismanWallet/talismanWallet.ts +19 -0
  137. package/src/wallets/default/tokenPocketWallet/tokenPocketWallet.svg +1 -0
  138. package/src/wallets/default/tokenPocketWallet/tokenPocketWallet.ts +26 -0
  139. package/src/wallets/default/tokenaryWallet/tokenaryWallet.svg +1 -0
  140. package/src/wallets/default/tokenaryWallet/tokenaryWallet.ts +19 -0
  141. package/src/wallets/default/trezorWallet/icon.ts +2 -0
  142. package/src/wallets/default/trezorWallet/trezorWallet.ts +19 -0
  143. package/src/wallets/default/trustWallet/trustWallet.svg +1 -0
  144. package/src/wallets/default/trustWallet/trustWallet.ts +25 -0
  145. package/src/wallets/default/uniswapWallet/uniswapWallet.svg +1 -0
  146. package/src/wallets/default/uniswapWallet/uniswapWallet.ts +17 -0
  147. package/src/wallets/default/universalProfilesWallet/icon.ts +2 -0
  148. package/src/wallets/default/universalProfilesWallet/universalProfilesWallet.svg +1353 -0
  149. package/src/wallets/default/universalProfilesWallet/universalProfilesWallet.ts +22 -0
  150. package/src/wallets/default/valoraWallet/valoraWallet.svg +1 -0
  151. package/src/wallets/default/valoraWallet/valoraWallet.ts +18 -0
  152. package/src/wallets/default/walletConnectWallet/walletConnectWallet.svg +4 -0
  153. package/src/wallets/default/walletConnectWallet/walletConnectWallet.ts +18 -0
  154. package/src/wallets/default/wigwamWallet/wigwamWallet.svg +9 -0
  155. package/src/wallets/default/wigwamWallet/wigwamWallet.ts +20 -0
  156. package/src/wallets/default/xPortalWallet/xPortalWallet.svg +6 -0
  157. package/src/wallets/default/xPortalWallet/xPortalWallet.ts +22 -0
  158. package/src/wallets/default/zealWallet/zealWallet.svg +1 -0
  159. package/src/wallets/default/zealWallet/zealWallet.ts +25 -0
  160. package/src/wallets/default/zerionWallet/zerionWallet.svg +1 -0
  161. package/src/wallets/default/zerionWallet/zerionWallet.ts +24 -0
  162. package/src/wallets/default/zilPayWallet/zilPayWallet.svg +17 -0
  163. package/src/wallets/default/zilPayWallet/zilPayWallet.ts +21 -0
  164. package/src/wallets/detector.ts +136 -0
  165. package/src/wallets/index.ts +210 -0
  166. package/src/wallets/providers/WalletConnectProvider.ts +346 -0
  167. package/src/wallets/providers/WalletConnectSolanaProvider.ts +351 -0
  168. package/src/wallets/wallet-eip6963.ts +70 -0
  169. package/src/wallets/wallet-standard.ts +77 -0
  170. package/src/wallets/wallet-walletconnect.ts +202 -0
  171. package/tsconfig.json +7 -0
@@ -0,0 +1,346 @@
1
+ /**
2
+ * WalletConnect Provider
3
+ *
4
+ * Implements standard Ethereum provider interface using WalletConnect protocol
5
+ * Compatible with EIP-1193 provider interface
6
+ */
7
+
8
+ import type { NamespaceConfig, SessionInfo } from "@tomo-inc/wallet-connect-protocol";
9
+ import { WalletConnectClient } from "@tomo-inc/wallet-connect-protocol";
10
+
11
+ export interface WalletConnectProviderConfig {
12
+ projectId: string;
13
+ metadata: {
14
+ name: string;
15
+ description: string;
16
+ url: string;
17
+ icons: string[];
18
+ };
19
+ chains?: string[];
20
+ optionalChains?: string[];
21
+ methods?: string[];
22
+ events?: string[];
23
+ }
24
+
25
+ const defaultNamespaces: Record<string, NamespaceConfig> = {
26
+ eip155: {
27
+ chains: ["eip155:1"],
28
+ methods: [
29
+ "eth_sendTransaction",
30
+ "eth_signTransaction",
31
+ "eth_sign",
32
+ "personal_sign",
33
+ "eth_signTypedData",
34
+ "eth_signTypedData_v4",
35
+ "eth_chainId",
36
+ ],
37
+ events: ["chainChanged", "accountsChanged"],
38
+ },
39
+ };
40
+
41
+ export class WalletConnectProvider {
42
+ private client: WalletConnectClient;
43
+ private session: SessionInfo | null = null;
44
+ private accounts: string[] = [];
45
+ private chainId: string = "0x1"; // Default to Ethereum mainnet
46
+ private eventListeners: Map<string, Set<(...args: any[]) => void>> = new Map();
47
+ private sessionMap: Map<string, SessionInfo> = new Map();
48
+ public uri: string | null = null;
49
+ public qrCode: string | null = null;
50
+
51
+ constructor(config: WalletConnectProviderConfig) {
52
+ this.client = new WalletConnectClient({
53
+ projectId: config.projectId,
54
+ metadata: config.metadata,
55
+ });
56
+
57
+ this.setupClientListeners();
58
+ }
59
+
60
+ /**
61
+ * Setup WalletConnect client event listeners
62
+ */
63
+ private setupClientListeners() {
64
+ this.client.on("session_proposal", () => {
65
+ // Session connected
66
+ const sessions = this.client.getActiveSessions();
67
+ if (sessions.length > 0) {
68
+ this.session = sessions[0];
69
+ this.updateAccountsFromSession();
70
+ this.emit("connect", { chainId: this.chainId });
71
+ }
72
+ });
73
+
74
+ this.client.on("session_delete", () => {
75
+ this.session = null;
76
+ this.accounts = [];
77
+ this.emit("disconnect");
78
+ });
79
+
80
+ this.client.on("session_update", () => {
81
+ this.updateAccountsFromSession();
82
+ this.emit("accountsChanged", this.accounts);
83
+ });
84
+
85
+ this.client.on("display_uri", (e: { uri: string }) => {
86
+ this.uri = e.uri;
87
+ this.emit("uri_changed", e);
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Update accounts from current session
93
+ */
94
+ private updateAccountsFromSession() {
95
+ if (!this.session) return;
96
+
97
+ const allAccounts = Object.values(this.session.namespaces).flatMap((ns: any) => ns.accounts || []);
98
+
99
+ this.accounts = allAccounts.map((account: string) => {
100
+ const parts = account.split(":");
101
+ return parts[2] || account;
102
+ });
103
+
104
+ if (allAccounts.length > 0) {
105
+ const chainIdPart = allAccounts[0].split(":")[1];
106
+ this.chainId = "0x" + parseInt(chainIdPart).toString(16);
107
+ }
108
+ }
109
+
110
+ public async getUri(config: { requiredNamespaces: Record<string, NamespaceConfig> }): Promise<string> {
111
+ // Create connection and get URI
112
+ const uri = await this.client.connect({
113
+ requiredNamespaces: config.requiredNamespaces || defaultNamespaces,
114
+ });
115
+
116
+ this.uri = uri;
117
+ return uri;
118
+ }
119
+ /**
120
+ * Initialize the provider
121
+ */
122
+ async initialize(): Promise<void> {
123
+ await this.client.initialize();
124
+ // get all active sessions and store in sessionMap
125
+ const sessions = this.client.getActiveSessions();
126
+ sessions.forEach((session: SessionInfo) => {
127
+ this.sessionMap.set(session.topic, session);
128
+ });
129
+ this.uri = await this.getUri({
130
+ requiredNamespaces: defaultNamespaces,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Connect to wallet via WalletConnect
136
+ * Returns URI and QR code immediately for display
137
+ * Connection completion is notified via 'connect' event
138
+ */
139
+ async connect(): Promise<string[] | undefined> {
140
+ // Start waiting for connection in background (don't await)
141
+ // Connection will emit 'connect' event when established
142
+ const connected = await this.waitForConnection().catch((error) => {
143
+ console.error("WalletConnect connection failed:", error);
144
+ this.emit("error", error);
145
+ return false;
146
+ });
147
+
148
+ if (connected) {
149
+ return this.accounts;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Wait for WalletConnect session to be established
155
+ */
156
+ private waitForConnection(): Promise<boolean | undefined> {
157
+ return new Promise((resolve, reject) => {
158
+ const timeout = setTimeout(() => {
159
+ reject(new Error("Connection timeout"));
160
+ }, 300000); // 5 minutes
161
+
162
+ const checkConnection = () => {
163
+ const sessions = this.client.getActiveSessions();
164
+ const newSessions = sessions.filter((session: SessionInfo) => !this.sessionMap.has(session.topic));
165
+ if (newSessions.length > 0) {
166
+ clearTimeout(timeout);
167
+ this.session = newSessions[0];
168
+ this.updateAccountsFromSession();
169
+ this.emit("connect", { chainId: this.chainId });
170
+ resolve(true);
171
+ } else {
172
+ setTimeout(checkConnection, 500);
173
+ }
174
+ };
175
+
176
+ checkConnection();
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Disconnect from wallet
182
+ */
183
+ async disconnect(): Promise<void> {
184
+ if (this.session) {
185
+ await this.client.disconnectSession(this.session.topic);
186
+ this.session = null;
187
+ this.accounts = [];
188
+ this.emit("disconnect");
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Send JSON-RPC request (EIP-1193)
194
+ */
195
+ async request(args: { method: string; params?: any[] }): Promise<any> {
196
+ // eth_requestAccounts
197
+ if (args.method === "eth_requestAccounts") {
198
+ if (this.accounts.length > 0) {
199
+ return this.accounts;
200
+ }
201
+ if (this.uri === null) {
202
+ this.uri = await this.getUri({
203
+ requiredNamespaces: defaultNamespaces,
204
+ });
205
+ }
206
+ return this.connect();
207
+ }
208
+
209
+ if (!this.session) {
210
+ throw new Error("Not connected. Please call connect() first.");
211
+ }
212
+
213
+ if (args.method === "eth_chainId") {
214
+ return this.chainId || "0x1"; // default to Ethereum mainnet
215
+ }
216
+
217
+ try {
218
+ const result = await this.client.sendRequest({
219
+ topic: this.session.topic,
220
+ chainId: `eip155:${parseInt(this.chainId, 16)}`,
221
+ request: {
222
+ method: args.method,
223
+ params: args.params || [],
224
+ },
225
+ });
226
+
227
+ return result;
228
+ } catch (error: any) {
229
+ throw new Error(error.message || "Request failed");
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get current accounts
235
+ */
236
+ async getAccounts(): Promise<string[]> {
237
+ if (!this.session) {
238
+ return [];
239
+ }
240
+ return this.accounts;
241
+ }
242
+
243
+ /**
244
+ * Get current chain ID
245
+ */
246
+ async getChainId(): Promise<string> {
247
+ return this.chainId;
248
+ }
249
+
250
+ /**
251
+ * Check if connected
252
+ */
253
+ isConnected(): boolean {
254
+ return this.session !== null && this.accounts.length > 0;
255
+ }
256
+
257
+ /**
258
+ * Get active session
259
+ */
260
+ getSession(): SessionInfo | null {
261
+ return this.session;
262
+ }
263
+
264
+ /**
265
+ * Event emitter - on
266
+ */
267
+ on(event: string, listener: (...args: any[]) => void): void {
268
+ if (!this.eventListeners.has(event)) {
269
+ this.eventListeners.set(event, new Set());
270
+ }
271
+ this.eventListeners.get(event)!.add(listener);
272
+ }
273
+
274
+ /**
275
+ * Event emitter - off
276
+ */
277
+ off(event: string, listener: (...args: any[]) => void): void {
278
+ const listeners = this.eventListeners.get(event);
279
+ if (listeners) {
280
+ listeners.delete(listener);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Event emitter - emit
286
+ */
287
+ private emit(event: string, data?: any): void {
288
+ const listeners = this.eventListeners.get(event);
289
+ if (listeners) {
290
+ listeners.forEach((listener) => {
291
+ try {
292
+ listener(data);
293
+ } catch (error) {
294
+ console.error(`Error in ${event} listener:`, error);
295
+ }
296
+ });
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Helper: Sign message
302
+ */
303
+ async signMessage(message: string): Promise<string> {
304
+ if (!this.accounts[0]) {
305
+ throw new Error("No account connected");
306
+ }
307
+
308
+ return this.request({
309
+ method: "personal_sign",
310
+ params: [message, this.accounts[0]],
311
+ });
312
+ }
313
+
314
+ /**
315
+ * Helper: Sign typed data
316
+ */
317
+ async signTypedData(typedData: any): Promise<string> {
318
+ if (!this.accounts[0]) {
319
+ throw new Error("No account connected");
320
+ }
321
+
322
+ return this.request({
323
+ method: "eth_signTypedData_v4",
324
+ params: [this.accounts[0], JSON.stringify(typedData)],
325
+ });
326
+ }
327
+
328
+ /**
329
+ * Helper: Send transaction
330
+ */
331
+ async sendTransaction(transaction: any): Promise<string> {
332
+ return this.request({
333
+ method: "eth_sendTransaction",
334
+ params: [transaction],
335
+ });
336
+ }
337
+
338
+ /**
339
+ * Helper: Switch chain
340
+ */
341
+ async switchChain(_chainId: string): Promise<void> {
342
+ // WalletConnect doesn't support switching chains dynamically
343
+ // User needs to reconnect with different chain
344
+ throw new Error("Please reconnect to switch chains");
345
+ }
346
+ }
@@ -0,0 +1,351 @@
1
+ /**
2
+ * WalletConnect Solana Provider
3
+ *
4
+ * Implements Solana provider interface using WalletConnect protocol
5
+ * Compatible with WalletConnect Solana JSON-RPC methods
6
+ */
7
+
8
+ import type { NamespaceConfig, SessionInfo } from "@tomo-inc/wallet-connect-protocol";
9
+ import { WalletConnectClient } from "@tomo-inc/wallet-connect-protocol";
10
+
11
+ export interface WalletConnectSolanaProviderConfig {
12
+ projectId: string;
13
+ metadata: {
14
+ name: string;
15
+ description: string;
16
+ url: string;
17
+ icons: string[];
18
+ };
19
+ chains?: string[];
20
+ methods?: string[];
21
+ events?: string[];
22
+ }
23
+
24
+ const defaultSolanaNamespaces: Record<string, NamespaceConfig> = {
25
+ solana: {
26
+ chains: ["solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"], // Solana mainnet
27
+ methods: [
28
+ "solana_getAccounts",
29
+ "solana_requestAccounts",
30
+ "solana_signMessage",
31
+ "solana_signTransaction",
32
+ "solana_signAllTransactions",
33
+ "solana_signAndSendTransaction",
34
+ ],
35
+ events: ["accountsChanged"],
36
+ },
37
+ };
38
+
39
+ export class WalletConnectSolanaProvider {
40
+ private client: WalletConnectClient;
41
+ private session: SessionInfo | null = null;
42
+ private accounts: string[] = [];
43
+ private chainId: string = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"; // Default to Solana mainnet
44
+ private eventListeners: Map<string, Set<(...args: any[]) => void>> = new Map();
45
+ private sessionMap: Map<string, SessionInfo> = new Map();
46
+ public uri: string | null = null;
47
+ public qrCode: string | null = null;
48
+
49
+ constructor(config: WalletConnectSolanaProviderConfig) {
50
+ this.client = new WalletConnectClient({
51
+ projectId: config.projectId,
52
+ metadata: config.metadata,
53
+ });
54
+
55
+ this.setupClientListeners();
56
+ }
57
+
58
+ /**
59
+ * Setup WalletConnect client event listeners
60
+ */
61
+ private setupClientListeners() {
62
+ this.client.on("session_proposal", () => {
63
+ // Session connected
64
+ const sessions = this.client.getActiveSessions();
65
+ if (sessions.length > 0) {
66
+ this.session = sessions[0];
67
+ this.updateAccountsFromSession();
68
+ this.emit("connect", { chainId: this.chainId });
69
+ }
70
+ });
71
+
72
+ this.client.on("session_delete", () => {
73
+ this.session = null;
74
+ this.accounts = [];
75
+ this.emit("disconnect");
76
+ });
77
+
78
+ this.client.on("session_update", () => {
79
+ this.updateAccountsFromSession();
80
+ this.emit("accountsChanged", this.accounts);
81
+ });
82
+
83
+ this.client.on("display_uri", (e: { uri: string }) => {
84
+ this.uri = e.uri;
85
+ this.emit("uri_changed", e);
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Update accounts from current session
91
+ */
92
+ private updateAccountsFromSession() {
93
+ if (!this.session) return;
94
+
95
+ const allAccounts = Object.values(this.session.namespaces).flatMap((ns: any) => ns.accounts || []);
96
+
97
+ this.accounts = allAccounts.map((account: string) => {
98
+ const parts = account.split(":");
99
+ // Solana account format: solana:chainId:pubkey
100
+ return parts[2] || account;
101
+ });
102
+
103
+ if (allAccounts.length > 0) {
104
+ const chainIdPart = allAccounts[0].split(":")[1];
105
+ this.chainId = `solana:${chainIdPart}`;
106
+ }
107
+ }
108
+
109
+ public async getUri(config: { requiredNamespaces: Record<string, NamespaceConfig> }): Promise<string> {
110
+ // Create connection and get URI
111
+ const uri = await this.client.connect({
112
+ requiredNamespaces: config.requiredNamespaces || defaultSolanaNamespaces,
113
+ });
114
+
115
+ this.uri = uri;
116
+ return uri;
117
+ }
118
+
119
+ /**
120
+ * Initialize the provider
121
+ */
122
+ async initialize(): Promise<void> {
123
+ await this.client.initialize();
124
+ // get all active sessions and store in sessionMap
125
+ const sessions = this.client.getActiveSessions();
126
+ sessions.forEach((session: SessionInfo) => {
127
+ this.sessionMap.set(session.topic, session);
128
+ });
129
+ this.uri = await this.getUri({
130
+ requiredNamespaces: defaultSolanaNamespaces,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Connect to wallet via WalletConnect
136
+ * Returns accounts when connected
137
+ */
138
+ async connect(): Promise<string[] | undefined> {
139
+ // Start waiting for connection in background (don't await)
140
+ // Connection will emit 'connect' event when established
141
+ const connected = await this.waitForConnection().catch((error) => {
142
+ console.error("WalletConnect Solana connection failed:", error);
143
+ this.emit("error", error);
144
+ return false;
145
+ });
146
+
147
+ if (connected) {
148
+ return this.accounts;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Wait for WalletConnect session to be established
154
+ */
155
+ private waitForConnection(): Promise<boolean | undefined> {
156
+ return new Promise((resolve, reject) => {
157
+ const timeout = setTimeout(() => {
158
+ reject(new Error("Connection timeout"));
159
+ }, 300000); // 5 minutes
160
+
161
+ const checkConnection = () => {
162
+ const sessions = this.client.getActiveSessions();
163
+ const newSessions = sessions.filter((session: SessionInfo) => !this.sessionMap.has(session.topic));
164
+ if (newSessions.length > 0) {
165
+ clearTimeout(timeout);
166
+ this.session = newSessions[0];
167
+ this.updateAccountsFromSession();
168
+ this.emit("connect", { chainId: this.chainId });
169
+ resolve(true);
170
+ } else {
171
+ setTimeout(checkConnection, 500);
172
+ }
173
+ };
174
+
175
+ checkConnection();
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Disconnect from wallet
181
+ */
182
+ async disconnect(): Promise<void> {
183
+ if (this.session) {
184
+ await this.client.disconnectSession(this.session.topic);
185
+ this.session = null;
186
+ this.accounts = [];
187
+ this.emit("disconnect");
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Send JSON-RPC request (Solana WalletConnect methods)
193
+ */
194
+ async request(args: { method: string; params?: any }): Promise<any> {
195
+ // solana_requestAccounts
196
+ if (args.method === "solana_requestAccounts") {
197
+ if (this.accounts.length > 0) {
198
+ return this.accounts.map((account) => ({ pubkey: account }));
199
+ }
200
+ if (this.uri === null) {
201
+ this.uri = await this.getUri({
202
+ requiredNamespaces: defaultSolanaNamespaces,
203
+ });
204
+ }
205
+ const accounts = await this.connect();
206
+ return accounts?.map((account) => ({ pubkey: account })) || [];
207
+ }
208
+
209
+ // solana_getAccounts
210
+ if (args.method === "solana_getAccounts") {
211
+ return this.accounts.map((account) => ({ pubkey: account }));
212
+ }
213
+
214
+ if (!this.session) {
215
+ throw new Error("Not connected. Please call connect() first.");
216
+ }
217
+
218
+ try {
219
+ const result = await this.client.sendRequest({
220
+ topic: this.session.topic,
221
+ chainId: this.chainId,
222
+ request: {
223
+ method: args.method,
224
+ params: args.params || [],
225
+ },
226
+ });
227
+
228
+ return result;
229
+ } catch (error: any) {
230
+ throw new Error(error.message || "Request failed");
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get current accounts
236
+ */
237
+ async getAccounts(): Promise<string[]> {
238
+ if (!this.session) {
239
+ return [];
240
+ }
241
+ return this.accounts;
242
+ }
243
+
244
+ /**
245
+ * Get current chain ID
246
+ */
247
+ async getChainId(): Promise<string> {
248
+ return this.chainId;
249
+ }
250
+
251
+ /**
252
+ * Check if connected
253
+ */
254
+ isConnected(): boolean {
255
+ return this.session !== null && this.accounts.length > 0;
256
+ }
257
+
258
+ /**
259
+ * Get active session
260
+ */
261
+ getSession(): SessionInfo | null {
262
+ return this.session;
263
+ }
264
+
265
+ /**
266
+ * Event emitter - on
267
+ */
268
+ on(event: string, listener: (...args: any[]) => void): void {
269
+ if (!this.eventListeners.has(event)) {
270
+ this.eventListeners.set(event, new Set());
271
+ }
272
+ this.eventListeners.get(event)!.add(listener);
273
+ }
274
+
275
+ /**
276
+ * Event emitter - off
277
+ */
278
+ off(event: string, listener: (...args: any[]) => void): void {
279
+ const listeners = this.eventListeners.get(event);
280
+ if (listeners) {
281
+ listeners.delete(listener);
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Event emitter - emit
287
+ */
288
+ private emit(event: string, data?: any): void {
289
+ const listeners = this.eventListeners.get(event);
290
+ if (listeners) {
291
+ listeners.forEach((listener) => {
292
+ try {
293
+ listener(data);
294
+ } catch (error) {
295
+ console.error(`Error in ${event} listener:`, error);
296
+ }
297
+ });
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Helper: Sign message
303
+ */
304
+ async signMessage(message: string, pubkey: string): Promise<string> {
305
+ const result = await this.request({
306
+ method: "solana_signMessage",
307
+ params: {
308
+ message,
309
+ pubkey,
310
+ },
311
+ });
312
+ return result.signature;
313
+ }
314
+
315
+ /**
316
+ * Helper: Sign transaction
317
+ */
318
+ async signTransaction(transaction: string): Promise<{ signature: string; transaction?: string }> {
319
+ return this.request({
320
+ method: "solana_signTransaction",
321
+ params: {
322
+ transaction,
323
+ },
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Helper: Sign all transactions
329
+ */
330
+ async signAllTransactions(transactions: string[]): Promise<{ transactions: string[] }> {
331
+ return this.request({
332
+ method: "solana_signAllTransactions",
333
+ params: {
334
+ transactions,
335
+ },
336
+ });
337
+ }
338
+
339
+ /**
340
+ * Helper: Sign and send transaction
341
+ */
342
+ async signAndSendTransaction(transaction: string, sendOptions?: any): Promise<{ signature: string }> {
343
+ return this.request({
344
+ method: "solana_signAndSendTransaction",
345
+ params: {
346
+ transaction,
347
+ sendOptions,
348
+ },
349
+ });
350
+ }
351
+ }