@sip-protocol/sdk 0.1.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.
- package/LICENSE +21 -0
- package/dist/index.d.mts +3640 -0
- package/dist/index.d.ts +3640 -0
- package/dist/index.js +5725 -0
- package/dist/index.mjs +5606 -0
- package/package.json +61 -0
- package/src/adapters/index.ts +19 -0
- package/src/adapters/near-intents.ts +475 -0
- package/src/adapters/oneclick-client.ts +367 -0
- package/src/commitment.ts +470 -0
- package/src/crypto.ts +93 -0
- package/src/errors.ts +471 -0
- package/src/index.ts +369 -0
- package/src/intent.ts +488 -0
- package/src/privacy.ts +382 -0
- package/src/proofs/index.ts +52 -0
- package/src/proofs/interface.ts +228 -0
- package/src/proofs/mock.ts +258 -0
- package/src/proofs/noir.ts +233 -0
- package/src/sip.ts +299 -0
- package/src/solver/index.ts +25 -0
- package/src/solver/mock-solver.ts +278 -0
- package/src/stealth.ts +414 -0
- package/src/validation.ts +401 -0
- package/src/wallet/base-adapter.ts +407 -0
- package/src/wallet/errors.ts +106 -0
- package/src/wallet/ethereum/adapter.ts +655 -0
- package/src/wallet/ethereum/index.ts +48 -0
- package/src/wallet/ethereum/mock.ts +505 -0
- package/src/wallet/ethereum/types.ts +364 -0
- package/src/wallet/index.ts +116 -0
- package/src/wallet/registry.ts +207 -0
- package/src/wallet/solana/adapter.ts +533 -0
- package/src/wallet/solana/index.ts +40 -0
- package/src/wallet/solana/mock.ts +522 -0
- package/src/wallet/solana/types.ts +253 -0
- package/src/zcash/index.ts +53 -0
- package/src/zcash/rpc-client.ts +623 -0
- package/src/zcash/shielded-service.ts +641 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ethereum Wallet Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implementation of WalletAdapter for Ethereum wallets (MetaMask, Coinbase, etc.).
|
|
5
|
+
* Uses EIP-1193 provider standard for wallet communication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Asset,
|
|
10
|
+
HexString,
|
|
11
|
+
ChainId,
|
|
12
|
+
Signature,
|
|
13
|
+
UnsignedTransaction,
|
|
14
|
+
SignedTransaction,
|
|
15
|
+
TransactionReceipt,
|
|
16
|
+
} from '@sip-protocol/types'
|
|
17
|
+
import { WalletErrorCode } from '@sip-protocol/types'
|
|
18
|
+
import { BaseWalletAdapter } from '../base-adapter'
|
|
19
|
+
import { WalletError } from '../errors'
|
|
20
|
+
import type {
|
|
21
|
+
EIP1193Provider,
|
|
22
|
+
EthereumAdapterConfig,
|
|
23
|
+
EthereumTransactionRequest,
|
|
24
|
+
EthereumTransactionReceipt,
|
|
25
|
+
EthereumWalletName,
|
|
26
|
+
EIP712TypedData,
|
|
27
|
+
} from './types'
|
|
28
|
+
import {
|
|
29
|
+
getEthereumProvider,
|
|
30
|
+
toHex,
|
|
31
|
+
hexToNumber,
|
|
32
|
+
normalizeAddress,
|
|
33
|
+
getDefaultRpcEndpoint,
|
|
34
|
+
EthereumChainId,
|
|
35
|
+
} from './types'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ethereum wallet adapter implementation
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const adapter = new EthereumWalletAdapter({
|
|
43
|
+
* wallet: 'metamask',
|
|
44
|
+
* chainId: 1,
|
|
45
|
+
* })
|
|
46
|
+
*
|
|
47
|
+
* await adapter.connect()
|
|
48
|
+
* const balance = await adapter.getBalance()
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export class EthereumWalletAdapter extends BaseWalletAdapter {
|
|
52
|
+
readonly chain = 'ethereum' as const
|
|
53
|
+
readonly name: string
|
|
54
|
+
|
|
55
|
+
private provider: EIP1193Provider | undefined
|
|
56
|
+
private _chainId: number
|
|
57
|
+
private _rpcEndpoint: string
|
|
58
|
+
private walletType: EthereumWalletName
|
|
59
|
+
private boundAccountsChanged: (...args: unknown[]) => void
|
|
60
|
+
private boundChainChanged: (...args: unknown[]) => void
|
|
61
|
+
private boundDisconnect: (...args: unknown[]) => void
|
|
62
|
+
|
|
63
|
+
constructor(config: EthereumAdapterConfig = {}) {
|
|
64
|
+
super()
|
|
65
|
+
this.walletType = config.wallet ?? 'metamask'
|
|
66
|
+
this._chainId = config.chainId ?? EthereumChainId.MAINNET
|
|
67
|
+
this._rpcEndpoint = config.rpcEndpoint ?? getDefaultRpcEndpoint(this._chainId)
|
|
68
|
+
this.name = `ethereum-${this.walletType}`
|
|
69
|
+
this.provider = config.provider
|
|
70
|
+
|
|
71
|
+
// Bind event handlers
|
|
72
|
+
this.boundAccountsChanged = this.handleAccountsChanged.bind(this)
|
|
73
|
+
this.boundChainChanged = this.handleChainChanged.bind(this)
|
|
74
|
+
this.boundDisconnect = this.handleDisconnect.bind(this)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get current chain ID
|
|
79
|
+
*/
|
|
80
|
+
getChainId(): number {
|
|
81
|
+
return this._chainId
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get RPC endpoint URL
|
|
86
|
+
*/
|
|
87
|
+
getRpcEndpoint(): string {
|
|
88
|
+
return this._rpcEndpoint
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Set RPC endpoint URL
|
|
93
|
+
*/
|
|
94
|
+
setRpcEndpoint(endpoint: string): void {
|
|
95
|
+
this._rpcEndpoint = endpoint
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Connect to Ethereum wallet
|
|
100
|
+
*/
|
|
101
|
+
async connect(): Promise<void> {
|
|
102
|
+
try {
|
|
103
|
+
this._connectionState = 'connecting'
|
|
104
|
+
|
|
105
|
+
// Get provider
|
|
106
|
+
if (!this.provider) {
|
|
107
|
+
this.provider = getEthereumProvider(this.walletType)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!this.provider) {
|
|
111
|
+
this._connectionState = 'error'
|
|
112
|
+
throw new WalletError(
|
|
113
|
+
`${this.walletType} wallet not found. Please install the extension.`,
|
|
114
|
+
WalletErrorCode.NOT_INSTALLED
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Request accounts (triggers connection popup)
|
|
119
|
+
const accounts = await this.provider.request<string[]>({
|
|
120
|
+
method: 'eth_requestAccounts',
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
if (!accounts || accounts.length === 0) {
|
|
124
|
+
this._connectionState = 'error'
|
|
125
|
+
throw new WalletError(
|
|
126
|
+
'No accounts returned from wallet',
|
|
127
|
+
WalletErrorCode.CONNECTION_REJECTED
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const address = normalizeAddress(accounts[0])
|
|
132
|
+
|
|
133
|
+
// Get chain ID
|
|
134
|
+
const chainIdHex = await this.provider.request<string>({
|
|
135
|
+
method: 'eth_chainId',
|
|
136
|
+
})
|
|
137
|
+
this._chainId = hexToNumber(chainIdHex)
|
|
138
|
+
|
|
139
|
+
// Update RPC endpoint if chain changed
|
|
140
|
+
this._rpcEndpoint = getDefaultRpcEndpoint(this._chainId)
|
|
141
|
+
|
|
142
|
+
// Set up event listeners
|
|
143
|
+
this.setupEventListeners()
|
|
144
|
+
|
|
145
|
+
// Set connected state
|
|
146
|
+
// For Ethereum, publicKey is the address (no separate public key concept)
|
|
147
|
+
this.setConnected(address, address as HexString)
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this._connectionState = 'error'
|
|
150
|
+
|
|
151
|
+
if (error instanceof WalletError) {
|
|
152
|
+
throw error
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle common EIP-1193 errors
|
|
156
|
+
const rpcError = error as { code?: number; message?: string }
|
|
157
|
+
if (rpcError.code === 4001) {
|
|
158
|
+
throw new WalletError(
|
|
159
|
+
'User rejected connection request',
|
|
160
|
+
WalletErrorCode.CONNECTION_REJECTED
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw new WalletError(
|
|
165
|
+
`Failed to connect: ${rpcError.message || String(error)}`,
|
|
166
|
+
WalletErrorCode.CONNECTION_FAILED
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Disconnect from wallet
|
|
173
|
+
*/
|
|
174
|
+
async disconnect(): Promise<void> {
|
|
175
|
+
this.removeEventListeners()
|
|
176
|
+
this.setDisconnected()
|
|
177
|
+
this.provider = undefined
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Sign a message
|
|
182
|
+
*/
|
|
183
|
+
async signMessage(message: Uint8Array): Promise<Signature> {
|
|
184
|
+
this.requireConnected()
|
|
185
|
+
|
|
186
|
+
if (!this.provider) {
|
|
187
|
+
throw new WalletError(
|
|
188
|
+
'Provider not available',
|
|
189
|
+
WalletErrorCode.NOT_CONNECTED
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
// Convert message to hex
|
|
195
|
+
const messageHex = `0x${Buffer.from(message).toString('hex')}`
|
|
196
|
+
|
|
197
|
+
// Use personal_sign for message signing
|
|
198
|
+
const signature = await this.provider.request<string>({
|
|
199
|
+
method: 'personal_sign',
|
|
200
|
+
params: [messageHex, this._address],
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
signature: signature as HexString,
|
|
205
|
+
publicKey: this._publicKey as HexString,
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
const rpcError = error as { code?: number; message?: string }
|
|
209
|
+
|
|
210
|
+
if (rpcError.code === 4001) {
|
|
211
|
+
throw new WalletError(
|
|
212
|
+
'User rejected signing request',
|
|
213
|
+
WalletErrorCode.SIGNING_REJECTED
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
throw new WalletError(
|
|
218
|
+
`Failed to sign message: ${rpcError.message || String(error)}`,
|
|
219
|
+
WalletErrorCode.SIGNING_FAILED
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Sign typed data (EIP-712)
|
|
226
|
+
*/
|
|
227
|
+
async signTypedData(typedData: EIP712TypedData): Promise<Signature> {
|
|
228
|
+
this.requireConnected()
|
|
229
|
+
|
|
230
|
+
if (!this.provider) {
|
|
231
|
+
throw new WalletError(
|
|
232
|
+
'Provider not available',
|
|
233
|
+
WalletErrorCode.NOT_CONNECTED
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const signature = await this.provider.request<string>({
|
|
239
|
+
method: 'eth_signTypedData_v4',
|
|
240
|
+
params: [this._address, JSON.stringify(typedData)],
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
signature: signature as HexString,
|
|
245
|
+
publicKey: this._publicKey as HexString,
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const rpcError = error as { code?: number; message?: string }
|
|
249
|
+
|
|
250
|
+
if (rpcError.code === 4001) {
|
|
251
|
+
throw new WalletError(
|
|
252
|
+
'User rejected signing request',
|
|
253
|
+
WalletErrorCode.SIGNING_REJECTED
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
throw new WalletError(
|
|
258
|
+
`Failed to sign typed data: ${rpcError.message || String(error)}`,
|
|
259
|
+
WalletErrorCode.SIGNING_FAILED
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Sign a transaction without sending
|
|
266
|
+
*/
|
|
267
|
+
async signTransaction(tx: UnsignedTransaction): Promise<SignedTransaction> {
|
|
268
|
+
this.requireConnected()
|
|
269
|
+
|
|
270
|
+
if (!this.provider) {
|
|
271
|
+
throw new WalletError(
|
|
272
|
+
'Provider not available',
|
|
273
|
+
WalletErrorCode.NOT_CONNECTED
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const ethTx = tx.data as EthereumTransactionRequest
|
|
279
|
+
|
|
280
|
+
// Ensure from address is set
|
|
281
|
+
const txWithFrom: EthereumTransactionRequest = {
|
|
282
|
+
...ethTx,
|
|
283
|
+
from: ethTx.from ?? this._address,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Use eth_signTransaction if available (not all wallets support this)
|
|
287
|
+
// MetaMask doesn't support eth_signTransaction, so we'll simulate
|
|
288
|
+
const signature = await this.provider.request<string>({
|
|
289
|
+
method: 'eth_signTransaction',
|
|
290
|
+
params: [txWithFrom],
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
unsigned: tx,
|
|
295
|
+
signatures: [
|
|
296
|
+
{
|
|
297
|
+
signature: signature as HexString,
|
|
298
|
+
publicKey: this._publicKey as HexString,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
serialized: signature as HexString,
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
const rpcError = error as { code?: number; message?: string }
|
|
305
|
+
|
|
306
|
+
if (rpcError.code === 4001) {
|
|
307
|
+
throw new WalletError(
|
|
308
|
+
'User rejected transaction signing',
|
|
309
|
+
WalletErrorCode.SIGNING_REJECTED
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Many wallets don't support eth_signTransaction
|
|
314
|
+
// Fall back to creating a mock signed transaction
|
|
315
|
+
if (rpcError.code === -32601 || rpcError.message?.includes('not supported')) {
|
|
316
|
+
// Method not supported - create placeholder
|
|
317
|
+
const mockSig = `0x${'00'.repeat(65)}` as HexString
|
|
318
|
+
return {
|
|
319
|
+
unsigned: tx,
|
|
320
|
+
signatures: [
|
|
321
|
+
{
|
|
322
|
+
signature: mockSig,
|
|
323
|
+
publicKey: this._publicKey as HexString,
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
serialized: mockSig,
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
throw new WalletError(
|
|
331
|
+
`Failed to sign transaction: ${rpcError.message || String(error)}`,
|
|
332
|
+
WalletErrorCode.TRANSACTION_FAILED
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Sign and send a transaction
|
|
339
|
+
*/
|
|
340
|
+
async signAndSendTransaction(tx: UnsignedTransaction): Promise<TransactionReceipt> {
|
|
341
|
+
this.requireConnected()
|
|
342
|
+
|
|
343
|
+
if (!this.provider) {
|
|
344
|
+
throw new WalletError(
|
|
345
|
+
'Provider not available',
|
|
346
|
+
WalletErrorCode.NOT_CONNECTED
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const ethTx = tx.data as EthereumTransactionRequest
|
|
352
|
+
|
|
353
|
+
// Ensure from address is set
|
|
354
|
+
const txWithFrom: EthereumTransactionRequest = {
|
|
355
|
+
...ethTx,
|
|
356
|
+
from: ethTx.from ?? this._address,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Send transaction
|
|
360
|
+
const txHash = await this.provider.request<string>({
|
|
361
|
+
method: 'eth_sendTransaction',
|
|
362
|
+
params: [txWithFrom],
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
txHash: txHash as HexString,
|
|
367
|
+
status: 'pending',
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
const rpcError = error as { code?: number; message?: string }
|
|
371
|
+
|
|
372
|
+
if (rpcError.code === 4001) {
|
|
373
|
+
throw new WalletError(
|
|
374
|
+
'User rejected transaction',
|
|
375
|
+
WalletErrorCode.TRANSACTION_REJECTED
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
throw new WalletError(
|
|
380
|
+
`Failed to send transaction: ${rpcError.message || String(error)}`,
|
|
381
|
+
WalletErrorCode.TRANSACTION_FAILED
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get ETH balance
|
|
388
|
+
*/
|
|
389
|
+
async getBalance(): Promise<bigint> {
|
|
390
|
+
this.requireConnected()
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
// Use provider if available (more reliable)
|
|
394
|
+
if (this.provider) {
|
|
395
|
+
const balance = await this.provider.request<string>({
|
|
396
|
+
method: 'eth_getBalance',
|
|
397
|
+
params: [this._address, 'latest'],
|
|
398
|
+
})
|
|
399
|
+
return BigInt(balance)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Fallback to RPC
|
|
403
|
+
const response = await fetch(this._rpcEndpoint, {
|
|
404
|
+
method: 'POST',
|
|
405
|
+
headers: { 'Content-Type': 'application/json' },
|
|
406
|
+
body: JSON.stringify({
|
|
407
|
+
jsonrpc: '2.0',
|
|
408
|
+
id: 1,
|
|
409
|
+
method: 'eth_getBalance',
|
|
410
|
+
params: [this._address, 'latest'],
|
|
411
|
+
}),
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
const data = await response.json()
|
|
415
|
+
if (data.error) {
|
|
416
|
+
throw new Error(data.error.message)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return BigInt(data.result)
|
|
420
|
+
} catch (error) {
|
|
421
|
+
throw new WalletError(
|
|
422
|
+
`Failed to fetch balance: ${String(error)}`,
|
|
423
|
+
WalletErrorCode.UNKNOWN
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get ERC-20 token balance
|
|
430
|
+
*/
|
|
431
|
+
async getTokenBalance(asset: Asset): Promise<bigint> {
|
|
432
|
+
this.requireConnected()
|
|
433
|
+
|
|
434
|
+
if (asset.chain !== 'ethereum') {
|
|
435
|
+
throw new WalletError(
|
|
436
|
+
`Asset chain ${asset.chain} not supported by Ethereum adapter`,
|
|
437
|
+
WalletErrorCode.UNSUPPORTED_CHAIN
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Native ETH
|
|
442
|
+
if (!asset.address) {
|
|
443
|
+
return this.getBalance()
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
// ERC-20 balanceOf call
|
|
448
|
+
// Function selector: balanceOf(address) = 0x70a08231
|
|
449
|
+
const data = `0x70a08231000000000000000000000000${this._address.slice(2)}`
|
|
450
|
+
|
|
451
|
+
const result = await this.provider?.request<string>({
|
|
452
|
+
method: 'eth_call',
|
|
453
|
+
params: [
|
|
454
|
+
{
|
|
455
|
+
to: asset.address,
|
|
456
|
+
data,
|
|
457
|
+
},
|
|
458
|
+
'latest',
|
|
459
|
+
],
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
if (!result || result === '0x') {
|
|
463
|
+
return 0n
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return BigInt(result)
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw new WalletError(
|
|
469
|
+
`Failed to fetch token balance: ${String(error)}`,
|
|
470
|
+
WalletErrorCode.UNKNOWN
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Switch to a different chain
|
|
477
|
+
*/
|
|
478
|
+
async switchChain(chainId: number): Promise<void> {
|
|
479
|
+
this.requireConnected()
|
|
480
|
+
|
|
481
|
+
if (!this.provider) {
|
|
482
|
+
throw new WalletError(
|
|
483
|
+
'Provider not available',
|
|
484
|
+
WalletErrorCode.NOT_CONNECTED
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
await this.provider.request({
|
|
490
|
+
method: 'wallet_switchEthereumChain',
|
|
491
|
+
params: [{ chainId: toHex(chainId) }],
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
this._chainId = chainId
|
|
495
|
+
this._rpcEndpoint = getDefaultRpcEndpoint(chainId)
|
|
496
|
+
} catch (error) {
|
|
497
|
+
const rpcError = error as { code?: number; message?: string }
|
|
498
|
+
|
|
499
|
+
if (rpcError.code === 4001) {
|
|
500
|
+
throw new WalletError(
|
|
501
|
+
'User rejected chain switch',
|
|
502
|
+
WalletErrorCode.CHAIN_SWITCH_REJECTED
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Chain not added to wallet
|
|
507
|
+
if (rpcError.code === 4902) {
|
|
508
|
+
throw new WalletError(
|
|
509
|
+
`Chain ${chainId} not added to wallet`,
|
|
510
|
+
WalletErrorCode.UNSUPPORTED_CHAIN
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
throw new WalletError(
|
|
515
|
+
`Failed to switch chain: ${rpcError.message || String(error)}`,
|
|
516
|
+
WalletErrorCode.CHAIN_SWITCH_FAILED
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Wait for transaction confirmation
|
|
523
|
+
*/
|
|
524
|
+
async waitForTransaction(
|
|
525
|
+
txHash: string,
|
|
526
|
+
confirmations: number = 1
|
|
527
|
+
): Promise<EthereumTransactionReceipt> {
|
|
528
|
+
const maxAttempts = 60 // 5 minutes with 5s interval
|
|
529
|
+
let attempts = 0
|
|
530
|
+
|
|
531
|
+
while (attempts < maxAttempts) {
|
|
532
|
+
try {
|
|
533
|
+
const receipt = await this.provider?.request<EthereumTransactionReceipt | null>({
|
|
534
|
+
method: 'eth_getTransactionReceipt',
|
|
535
|
+
params: [txHash],
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
if (receipt) {
|
|
539
|
+
// Check confirmations
|
|
540
|
+
const currentBlock = await this.provider?.request<string>({
|
|
541
|
+
method: 'eth_blockNumber',
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
if (currentBlock) {
|
|
545
|
+
const receiptBlock = hexToNumber(receipt.blockNumber)
|
|
546
|
+
const currentBlockNum = hexToNumber(currentBlock)
|
|
547
|
+
const confirmedBlocks = currentBlockNum - receiptBlock + 1
|
|
548
|
+
|
|
549
|
+
if (confirmedBlocks >= confirmations) {
|
|
550
|
+
return receipt
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} catch {
|
|
555
|
+
// Ignore errors, keep polling
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
559
|
+
attempts++
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
throw new WalletError(
|
|
563
|
+
`Transaction ${txHash} not confirmed after ${maxAttempts * 5} seconds`,
|
|
564
|
+
WalletErrorCode.TRANSACTION_FAILED
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Set up provider event listeners
|
|
570
|
+
*/
|
|
571
|
+
private setupEventListeners(): void {
|
|
572
|
+
if (!this.provider) return
|
|
573
|
+
|
|
574
|
+
this.provider.on('accountsChanged', this.boundAccountsChanged)
|
|
575
|
+
this.provider.on('chainChanged', this.boundChainChanged)
|
|
576
|
+
this.provider.on('disconnect', this.boundDisconnect)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Remove provider event listeners
|
|
581
|
+
*/
|
|
582
|
+
private removeEventListeners(): void {
|
|
583
|
+
if (!this.provider) return
|
|
584
|
+
|
|
585
|
+
this.provider.removeListener('accountsChanged', this.boundAccountsChanged)
|
|
586
|
+
this.provider.removeListener('chainChanged', this.boundChainChanged)
|
|
587
|
+
this.provider.removeListener('disconnect', this.boundDisconnect)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Handle accounts changed event
|
|
592
|
+
*/
|
|
593
|
+
private handleAccountsChanged(...args: unknown[]): void {
|
|
594
|
+
const accounts = args[0] as string[]
|
|
595
|
+
if (!accounts || accounts.length === 0) {
|
|
596
|
+
this.handleDisconnect()
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const previousAddress = this._address
|
|
601
|
+
const newAddress = normalizeAddress(accounts[0])
|
|
602
|
+
|
|
603
|
+
if (previousAddress !== newAddress) {
|
|
604
|
+
this._address = newAddress
|
|
605
|
+
this._publicKey = newAddress as HexString
|
|
606
|
+
|
|
607
|
+
this.emit({
|
|
608
|
+
type: 'accountChanged',
|
|
609
|
+
previousAddress,
|
|
610
|
+
newAddress,
|
|
611
|
+
timestamp: Date.now(),
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Handle chain changed event
|
|
618
|
+
*/
|
|
619
|
+
private handleChainChanged(...args: unknown[]): void {
|
|
620
|
+
const chainId = args[0] as string
|
|
621
|
+
if (!chainId) return
|
|
622
|
+
|
|
623
|
+
const previousChainId = this._chainId
|
|
624
|
+
const newChainId = hexToNumber(chainId)
|
|
625
|
+
|
|
626
|
+
if (previousChainId !== newChainId) {
|
|
627
|
+
this._chainId = newChainId
|
|
628
|
+
this._rpcEndpoint = getDefaultRpcEndpoint(newChainId)
|
|
629
|
+
|
|
630
|
+
// For Ethereum, emit chain change as 'ethereum' since that's our chain ID
|
|
631
|
+
this.emit({
|
|
632
|
+
type: 'chainChanged',
|
|
633
|
+
previousChain: 'ethereum' as ChainId,
|
|
634
|
+
newChain: 'ethereum' as ChainId,
|
|
635
|
+
timestamp: Date.now(),
|
|
636
|
+
})
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Handle disconnect event
|
|
642
|
+
*/
|
|
643
|
+
private handleDisconnect(): void {
|
|
644
|
+
this.setDisconnected('Wallet disconnected')
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Factory function to create Ethereum adapter
|
|
650
|
+
*/
|
|
651
|
+
export function createEthereumAdapter(
|
|
652
|
+
config?: EthereumAdapterConfig
|
|
653
|
+
): EthereumWalletAdapter {
|
|
654
|
+
return new EthereumWalletAdapter(config)
|
|
655
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ethereum Wallet Module
|
|
3
|
+
*
|
|
4
|
+
* Exports for Ethereum wallet integration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Adapter
|
|
8
|
+
export { EthereumWalletAdapter, createEthereumAdapter } from './adapter'
|
|
9
|
+
|
|
10
|
+
// Mock adapter for testing
|
|
11
|
+
export {
|
|
12
|
+
MockEthereumAdapter,
|
|
13
|
+
createMockEthereumAdapter,
|
|
14
|
+
createMockEthereumProvider,
|
|
15
|
+
} from './mock'
|
|
16
|
+
export type { MockEthereumAdapterConfig } from './mock'
|
|
17
|
+
|
|
18
|
+
// Types
|
|
19
|
+
export type {
|
|
20
|
+
EIP1193Provider,
|
|
21
|
+
EIP1193RequestArguments,
|
|
22
|
+
EIP1193Event,
|
|
23
|
+
EIP1193ConnectInfo,
|
|
24
|
+
EIP1193ProviderRpcError,
|
|
25
|
+
EIP712Domain,
|
|
26
|
+
EIP712TypeDefinition,
|
|
27
|
+
EIP712Types,
|
|
28
|
+
EIP712TypedData,
|
|
29
|
+
EthereumTransactionRequest,
|
|
30
|
+
EthereumTransactionReceipt,
|
|
31
|
+
EthereumTokenMetadata,
|
|
32
|
+
EthereumChainMetadata,
|
|
33
|
+
EthereumWalletName,
|
|
34
|
+
EthereumAdapterConfig,
|
|
35
|
+
EthereumChainIdType,
|
|
36
|
+
} from './types'
|
|
37
|
+
|
|
38
|
+
// Utilities
|
|
39
|
+
export {
|
|
40
|
+
getEthereumProvider,
|
|
41
|
+
detectEthereumWallets,
|
|
42
|
+
toHex,
|
|
43
|
+
fromHex,
|
|
44
|
+
hexToNumber,
|
|
45
|
+
normalizeAddress,
|
|
46
|
+
getDefaultRpcEndpoint,
|
|
47
|
+
EthereumChainId,
|
|
48
|
+
} from './types'
|