walletpair-sdk 1.0.2 → 1.0.5
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/README.md +13 -0
- package/dist/ble/framing.d.ts.map +1 -1
- package/dist/ble/framing.js +2 -2
- package/dist/ble/framing.js.map +1 -1
- package/dist/ble/index.d.ts +2 -2
- package/dist/ble/index.d.ts.map +1 -1
- package/dist/ble/index.js +2 -2
- package/dist/ble/index.js.map +1 -1
- package/dist/ble/web-ble-transport.d.ts +1 -1
- package/dist/ble/web-ble-transport.d.ts.map +1 -1
- package/dist/ble/web-ble-transport.js +23 -12
- package/dist/ble/web-ble-transport.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +29 -12
- package/dist/crypto.js.map +1 -1
- package/dist/dapp-session.d.ts.map +1 -1
- package/dist/dapp-session.js +15 -5
- package/dist/dapp-session.js.map +1 -1
- package/dist/emitter.d.ts +1 -3
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +4 -2
- package/dist/emitter.js.map +1 -1
- package/dist/evm/eip1193.d.ts +2 -2
- package/dist/evm/eip1193.d.ts.map +1 -1
- package/dist/evm/eip1193.js +32 -18
- package/dist/evm/eip1193.js.map +1 -1
- package/dist/evm/index.d.ts +2 -2
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js.map +1 -1
- package/dist/wallet-session.d.ts.map +1 -1
- package/dist/wallet-session.js +4 -3
- package/dist/wallet-session.js.map +1 -1
- package/dist/ws-transport.d.ts +3 -2
- package/dist/ws-transport.d.ts.map +1 -1
- package/dist/ws-transport.js +13 -4
- package/dist/ws-transport.js.map +1 -1
- package/package.json +20 -1
- package/src/__tests__/adversarial/crypto-attacks.test.ts +240 -233
- package/src/__tests__/adversarial/malicious-dapp.test.ts +228 -194
- package/src/__tests__/adversarial/malicious-relay.test.ts +292 -220
- package/src/__tests__/adversarial/malicious-wallet.test.ts +246 -180
- package/src/__tests__/spec-compliance/canonical-json.test.ts +105 -105
- package/src/__tests__/spec-compliance/crypto-vectors.test.ts +149 -154
- package/src/__tests__/spec-compliance/message-format.test.ts +180 -151
- package/src/__tests__/spec-compliance/sequence-numbers.test.ts +142 -149
- package/src/__tests__/spec-compliance/state-machine.test.ts +203 -180
- package/src/ble/framing.test.ts +122 -114
- package/src/ble/framing.ts +48 -51
- package/src/ble/index.ts +7 -7
- package/src/ble/web-ble-transport.test.ts +93 -84
- package/src/ble/web-ble-transport.ts +70 -57
- package/src/ble/web-bluetooth.d.ts +19 -19
- package/src/canonical-json.test.ts +301 -285
- package/src/crypto-directional.test.ts +155 -129
- package/src/crypto-hardening.test.ts +292 -283
- package/src/crypto.test.ts +364 -346
- package/src/crypto.ts +185 -175
- package/src/dapp-session.test.ts +522 -385
- package/src/dapp-session.ts +17 -11
- package/src/emitter.test.ts +122 -122
- package/src/emitter.ts +20 -18
- package/src/evm/eip1193.test.ts +283 -205
- package/src/evm/eip1193.ts +162 -138
- package/src/evm/index.ts +5 -5
- package/src/evm/wagmi.test.ts +1 -1
- package/src/integration.test.ts +329 -201
- package/src/security.test.ts +331 -238
- package/src/sequence-validation.test.ts +6 -9
- package/src/test-helpers.ts +102 -78
- package/src/types.test.ts +45 -50
- package/src/wallet-session.test.ts +611 -383
- package/src/wallet-session.ts +7 -9
- package/src/ws-transport.test.ts +141 -139
- package/src/ws-transport.ts +52 -41
package/src/evm/eip1193.ts
CHANGED
|
@@ -9,32 +9,32 @@
|
|
|
9
9
|
* const provider = new WalletPairProvider({ session })
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { DAppSession } from '../dapp-session.js'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import type { DAppSession } from '../dapp-session.js'
|
|
13
|
+
import { Emitter } from '../emitter.js'
|
|
14
|
+
import { evmNumericChainId } from '../types.js'
|
|
15
15
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// EIP-1193 types
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
export interface EIP1193RequestArgs {
|
|
21
|
-
method: string
|
|
22
|
-
params?: unknown[] | Record<string, unknown
|
|
21
|
+
method: string
|
|
22
|
+
params?: unknown[] | Record<string, unknown>
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface EIP1193ProviderEvents {
|
|
26
|
-
[key: string]: unknown
|
|
27
|
-
connect: { chainId: string }
|
|
28
|
-
disconnect: { code: number; message: string }
|
|
29
|
-
chainChanged: string
|
|
30
|
-
accountsChanged: string[]
|
|
31
|
-
message: { type: string; data?: unknown | undefined }
|
|
26
|
+
[key: string]: unknown
|
|
27
|
+
connect: { chainId: string }
|
|
28
|
+
disconnect: { code: number; message: string }
|
|
29
|
+
chainChanged: string
|
|
30
|
+
accountsChanged: string[]
|
|
31
|
+
message: { type: string; data?: unknown | undefined }
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface EIP1193Provider {
|
|
35
|
-
request(args: EIP1193RequestArgs): Promise<unknown
|
|
36
|
-
on(event: string, handler: (...args:
|
|
37
|
-
removeListener(event: string, handler: (...args:
|
|
35
|
+
request(args: EIP1193RequestArgs): Promise<unknown>
|
|
36
|
+
on(event: string, handler: (...args: unknown[]) => void): void
|
|
37
|
+
removeListener(event: string, handler: (...args: unknown[]) => void): void
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// ---------------------------------------------------------------------------
|
|
@@ -42,24 +42,26 @@ export interface EIP1193Provider {
|
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
|
|
44
44
|
export interface MethodMapper {
|
|
45
|
-
mapRequest(
|
|
46
|
-
|
|
45
|
+
mapRequest(
|
|
46
|
+
method: string,
|
|
47
|
+
params?: unknown,
|
|
48
|
+
): { method: string; params?: unknown | undefined } | null
|
|
49
|
+
mapResponse(method: string, result: unknown): unknown
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/** Convert hex chainId "0x89" to CAIP-2 "eip155:137". */
|
|
50
53
|
function hexChainToCaip2(hex: string): string {
|
|
51
|
-
return `eip155:${Number.parseInt(hex, 16)}
|
|
54
|
+
return `eip155:${Number.parseInt(hex, 16)}`
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/** Validate that a transaction object contains all required fields. */
|
|
55
58
|
function validateTxFields(tx: Record<string, unknown> | undefined): void {
|
|
56
|
-
const required = ['value', 'data', 'type', 'chainId']
|
|
57
|
-
const missing = required.filter(f => tx?.[f] === undefined || tx?.[f] === null)
|
|
59
|
+
const required = ['value', 'data', 'type', 'chainId']
|
|
60
|
+
const missing = required.filter((f) => tx?.[f] === undefined || tx?.[f] === null)
|
|
58
61
|
if (missing.length > 0) {
|
|
59
|
-
throw Object.assign(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
62
|
+
throw Object.assign(new Error(`Missing required transaction fields: ${missing.join(', ')}`), {
|
|
63
|
+
code: -32602,
|
|
64
|
+
})
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -68,86 +70,92 @@ const defaultMapper: MethodMapper = {
|
|
|
68
70
|
switch (method) {
|
|
69
71
|
case 'eth_requestAccounts':
|
|
70
72
|
case 'eth_accounts':
|
|
71
|
-
return { method: 'wallet_getAccounts' }
|
|
73
|
+
return { method: 'wallet_getAccounts' }
|
|
72
74
|
case 'personal_sign': {
|
|
73
75
|
// personal_sign params: [message, address] where message is hex-encoded bytes
|
|
74
|
-
const p = params as [string, string] | undefined
|
|
75
|
-
const msg = p?.[0]
|
|
76
|
+
const p = params as [string, string] | undefined
|
|
77
|
+
const msg = p?.[0]
|
|
76
78
|
// EIP-1193 personal_sign: message is always hex-encoded bytes.
|
|
77
79
|
// Decode hex to UTF-8 text and route to wallet_signMessage.
|
|
78
|
-
let text = msg ?? ''
|
|
79
|
-
if (msg
|
|
80
|
+
let text = msg ?? ''
|
|
81
|
+
if (msg?.startsWith('0x')) {
|
|
80
82
|
try {
|
|
81
|
-
const hex = msg.slice(2)
|
|
82
|
-
const bytes = new Uint8Array(hex.length / 2)
|
|
83
|
+
const hex = msg.slice(2)
|
|
84
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
83
85
|
for (let i = 0; i < bytes.length; i++) {
|
|
84
|
-
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
86
|
+
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
85
87
|
}
|
|
86
|
-
text = new TextDecoder().decode(bytes)
|
|
88
|
+
text = new TextDecoder().decode(bytes)
|
|
87
89
|
} catch {
|
|
88
|
-
text = msg
|
|
90
|
+
text = msg
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
return { method: 'wallet_signMessage', params: { message: text, address: p?.[1] } }
|
|
93
|
+
return { method: 'wallet_signMessage', params: { message: text, address: p?.[1] } }
|
|
92
94
|
}
|
|
93
95
|
case 'eth_signTypedData_v4': {
|
|
94
96
|
// params: [address, typedDataJSON]
|
|
95
|
-
const p = params as [string, string] | undefined
|
|
96
|
-
let typedData: unknown
|
|
97
|
-
try {
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const p = params as [string, string] | undefined
|
|
98
|
+
let typedData: unknown
|
|
99
|
+
try {
|
|
100
|
+
typedData = typeof p?.[1] === 'string' ? JSON.parse(p[1]) : p?.[1]
|
|
101
|
+
} catch {
|
|
102
|
+
typedData = p?.[1]
|
|
103
|
+
}
|
|
104
|
+
return { method: 'wallet_signTypedData', params: { address: p?.[0], typedData } }
|
|
100
105
|
}
|
|
101
106
|
case 'eth_sendTransaction': {
|
|
102
107
|
// params: [txObject] — maps to wallet_sendTransaction (sign + broadcast)
|
|
103
|
-
const p = params as [Record<string, unknown>] | undefined
|
|
104
|
-
const tx = p?.[0]
|
|
105
|
-
validateTxFields(tx)
|
|
106
|
-
return { method: 'wallet_sendTransaction', params: { address: tx?.from, tx } }
|
|
108
|
+
const p = params as [Record<string, unknown>] | undefined
|
|
109
|
+
const tx = p?.[0]
|
|
110
|
+
validateTxFields(tx)
|
|
111
|
+
return { method: 'wallet_sendTransaction', params: { address: tx?.from, tx } }
|
|
107
112
|
}
|
|
108
113
|
case 'eth_signTransaction': {
|
|
109
114
|
// params: [txObject] — maps to wallet_signTransaction (sign only)
|
|
110
|
-
const p = params as [Record<string, unknown>] | undefined
|
|
111
|
-
const tx = p?.[0]
|
|
112
|
-
validateTxFields(tx)
|
|
113
|
-
return { method: 'wallet_signTransaction', params: { address: tx?.from, tx } }
|
|
115
|
+
const p = params as [Record<string, unknown>] | undefined
|
|
116
|
+
const tx = p?.[0]
|
|
117
|
+
validateTxFields(tx)
|
|
118
|
+
return { method: 'wallet_signTransaction', params: { address: tx?.from, tx } }
|
|
114
119
|
}
|
|
115
120
|
case 'wallet_switchEthereumChain': {
|
|
116
121
|
// params: [{ chainId: "0x89" }] — convert hex to CAIP-2
|
|
117
|
-
const p = params as [{ chainId: string }] | undefined
|
|
118
|
-
const hexId = p?.[0]?.chainId
|
|
119
|
-
return {
|
|
122
|
+
const p = params as [{ chainId: string }] | undefined
|
|
123
|
+
const hexId = p?.[0]?.chainId
|
|
124
|
+
return {
|
|
125
|
+
method: 'wallet_switchChain',
|
|
126
|
+
params: { chain: hexId ? hexChainToCaip2(hexId) : undefined },
|
|
127
|
+
}
|
|
120
128
|
}
|
|
121
129
|
case 'wallet_addEthereumChain':
|
|
122
|
-
return null
|
|
130
|
+
return null // unsupported — mapRequest returning null triggers unsupported_method error
|
|
123
131
|
default:
|
|
124
|
-
return null
|
|
132
|
+
return null // unknown method — routed to rpcProvider or rejected before reaching here
|
|
125
133
|
}
|
|
126
134
|
},
|
|
127
135
|
mapResponse(method, result) {
|
|
128
136
|
// Unwrap wallet_getAccounts result to EIP-1193 format (string[])
|
|
129
137
|
if (method === 'eth_requestAccounts' || method === 'eth_accounts') {
|
|
130
|
-
const r = result as { accounts?: { address: string }[] } | undefined
|
|
131
|
-
if (r?.accounts) return r.accounts.map((a) => a.address)
|
|
138
|
+
const r = result as { accounts?: { address: string }[] } | undefined
|
|
139
|
+
if (r?.accounts) return r.accounts.map((a) => a.address)
|
|
132
140
|
}
|
|
133
141
|
// Unwrap wallet_sendTransaction result
|
|
134
142
|
if (method === 'eth_sendTransaction') {
|
|
135
|
-
const r = result as { txHash?: string } | undefined
|
|
136
|
-
if (r?.txHash) return r.txHash
|
|
143
|
+
const r = result as { txHash?: string } | undefined
|
|
144
|
+
if (r?.txHash) return r.txHash
|
|
137
145
|
}
|
|
138
146
|
// Unwrap wallet_signTransaction result
|
|
139
147
|
if (method === 'eth_signTransaction') {
|
|
140
|
-
const r = result as { signedTx?: string } | undefined
|
|
141
|
-
if (r?.signedTx) return r.signedTx
|
|
148
|
+
const r = result as { signedTx?: string } | undefined
|
|
149
|
+
if (r?.signedTx) return r.signedTx
|
|
142
150
|
}
|
|
143
151
|
// Unwrap signature results
|
|
144
152
|
if (method === 'personal_sign' || method === 'eth_signTypedData_v4') {
|
|
145
|
-
const r = result as { signature?: string } | undefined
|
|
146
|
-
if (r?.signature) return r.signature
|
|
153
|
+
const r = result as { signature?: string } | undefined
|
|
154
|
+
if (r?.signature) return r.signature
|
|
147
155
|
}
|
|
148
|
-
return result
|
|
156
|
+
return result
|
|
149
157
|
},
|
|
150
|
-
}
|
|
158
|
+
}
|
|
151
159
|
|
|
152
160
|
// ---------------------------------------------------------------------------
|
|
153
161
|
// RPC routing: wallet methods vs read-only RPC
|
|
@@ -155,24 +163,26 @@ const defaultMapper: MethodMapper = {
|
|
|
155
163
|
|
|
156
164
|
/** Methods that MUST go through WalletPair (require wallet signing/authorization). */
|
|
157
165
|
const WALLET_METHODS = new Set([
|
|
158
|
-
'eth_requestAccounts',
|
|
166
|
+
'eth_requestAccounts',
|
|
167
|
+
'eth_accounts',
|
|
159
168
|
'personal_sign',
|
|
160
|
-
'eth_signTypedData_v4',
|
|
161
|
-
'
|
|
162
|
-
'
|
|
163
|
-
|
|
169
|
+
'eth_signTypedData_v4',
|
|
170
|
+
'eth_signTypedData_v3',
|
|
171
|
+
'eth_sendTransaction',
|
|
172
|
+
'eth_signTransaction',
|
|
173
|
+
'wallet_switchEthereumChain',
|
|
174
|
+
'wallet_addEthereumChain',
|
|
175
|
+
])
|
|
164
176
|
|
|
165
177
|
/** Methods handled locally by the provider (no RPC or WalletPair needed). */
|
|
166
|
-
const LOCAL_METHODS = new Set([
|
|
167
|
-
'eth_chainId', 'net_version',
|
|
168
|
-
]);
|
|
178
|
+
const LOCAL_METHODS = new Set(['eth_chainId', 'net_version'])
|
|
169
179
|
|
|
170
180
|
/**
|
|
171
181
|
* An RPC provider that handles read-only Ethereum JSON-RPC calls.
|
|
172
182
|
* Pass any EIP-1193-compatible provider, or a simple fetch-based JSON-RPC client.
|
|
173
183
|
*/
|
|
174
184
|
export interface RpcProvider {
|
|
175
|
-
request(args: EIP1193RequestArgs): Promise<unknown
|
|
185
|
+
request(args: EIP1193RequestArgs): Promise<unknown>
|
|
176
186
|
}
|
|
177
187
|
|
|
178
188
|
// ---------------------------------------------------------------------------
|
|
@@ -180,167 +190,181 @@ export interface RpcProvider {
|
|
|
180
190
|
// ---------------------------------------------------------------------------
|
|
181
191
|
|
|
182
192
|
export interface WalletPairProviderOptions {
|
|
183
|
-
session: DAppSession
|
|
193
|
+
session: DAppSession
|
|
184
194
|
/** Initial EVM chain ID (numeric). Default 1 (mainnet). */
|
|
185
|
-
chainId?: number | undefined
|
|
195
|
+
chainId?: number | undefined
|
|
186
196
|
/** Custom method mapper. */
|
|
187
|
-
mapper?: MethodMapper | undefined
|
|
197
|
+
mapper?: MethodMapper | undefined
|
|
188
198
|
/**
|
|
189
199
|
* Optional RPC provider for read-only methods (eth_call, eth_getBalance,
|
|
190
200
|
* eth_blockNumber, etc.). If provided, any method not handled by WalletPair
|
|
191
201
|
* is routed here instead of being sent to the wallet. If omitted, unknown
|
|
192
202
|
* methods throw unsupported_method (4200).
|
|
193
203
|
*/
|
|
194
|
-
rpcProvider?: RpcProvider | undefined
|
|
204
|
+
rpcProvider?: RpcProvider | undefined
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
export class WalletPairProvider implements EIP1193Provider {
|
|
198
|
-
private session: DAppSession
|
|
199
|
-
private mapper: MethodMapper
|
|
200
|
-
private rpcProvider: RpcProvider | undefined
|
|
201
|
-
private emitter = new Emitter<EIP1193ProviderEvents>()
|
|
202
|
-
private chainId: number
|
|
203
|
-
private accounts: string[] = []
|
|
204
|
-
private connected = false
|
|
205
|
-
private disconnected = false
|
|
208
|
+
private session: DAppSession
|
|
209
|
+
private mapper: MethodMapper
|
|
210
|
+
private rpcProvider: RpcProvider | undefined
|
|
211
|
+
private emitter = new Emitter<EIP1193ProviderEvents>()
|
|
212
|
+
private chainId: number
|
|
213
|
+
private accounts: string[] = []
|
|
214
|
+
private connected = false
|
|
215
|
+
private disconnected = false
|
|
206
216
|
|
|
207
217
|
constructor(options: WalletPairProviderOptions) {
|
|
208
|
-
this.session = options.session
|
|
209
|
-
this.mapper = options.mapper ?? defaultMapper
|
|
210
|
-
this.rpcProvider = options.rpcProvider
|
|
211
|
-
this.chainId = options.chainId ?? 1
|
|
218
|
+
this.session = options.session
|
|
219
|
+
this.mapper = options.mapper ?? defaultMapper
|
|
220
|
+
this.rpcProvider = options.rpcProvider
|
|
221
|
+
this.chainId = options.chainId ?? 1
|
|
212
222
|
|
|
213
223
|
this.session.on('phase', (phase) => {
|
|
214
224
|
if (phase === 'connected' && !this.connected) {
|
|
215
|
-
this.connected = true
|
|
216
|
-
this.emitter.emit('connect', { chainId: `0x${this.chainId.toString(16)}` })
|
|
225
|
+
this.connected = true
|
|
226
|
+
this.emitter.emit('connect', { chainId: `0x${this.chainId.toString(16)}` })
|
|
217
227
|
} else if ((phase === 'closed' || phase === 'disconnected') && this.connected) {
|
|
218
|
-
this.connected = false
|
|
219
|
-
this.emitter.emit('disconnect', { code: 4900, message: 'Disconnected' })
|
|
228
|
+
this.connected = false
|
|
229
|
+
this.emitter.emit('disconnect', { code: 4900, message: 'Disconnected' })
|
|
220
230
|
}
|
|
221
|
-
})
|
|
231
|
+
})
|
|
222
232
|
|
|
223
233
|
this.session.on('event', ({ event, data }) => {
|
|
224
234
|
if (event === 'disconnect') {
|
|
225
|
-
this.connected = false
|
|
226
|
-
this.disconnected = true
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
this.connected = false
|
|
236
|
+
this.disconnected = true
|
|
237
|
+
const d = data as Record<string, unknown>
|
|
238
|
+
const reason = String(d?.reason ?? 'unknown')
|
|
239
|
+
const msg =
|
|
240
|
+
typeof d?.message === 'string' ? d.message : `Disconnected by wallet (${reason})`
|
|
241
|
+
this.emitter.emit('disconnect', { code: 4900, message: msg })
|
|
242
|
+
this.session.close('normal')
|
|
243
|
+
return
|
|
232
244
|
}
|
|
233
245
|
if (event === 'accountsChanged') {
|
|
234
246
|
// Handle both formats:
|
|
235
247
|
// - Simple: { accounts: ['0x...'] } or just ['0x...']
|
|
236
248
|
// - Sub-protocol: { accounts: [{ address: '0x...', chains?: [...] }] }
|
|
237
|
-
const payload = data as { accounts?: (string | { address: string })[] } | string[]
|
|
238
|
-
const rawAccounts = Array.isArray(payload) ? payload :
|
|
249
|
+
const payload = data as { accounts?: (string | { address: string })[] } | string[]
|
|
250
|
+
const rawAccounts = Array.isArray(payload) ? payload : payload?.accounts
|
|
239
251
|
if (Array.isArray(rawAccounts)) {
|
|
240
252
|
this.accounts = rawAccounts.map((a: string | { address: string }) =>
|
|
241
253
|
typeof a === 'string' ? a : a.address,
|
|
242
|
-
)
|
|
243
|
-
this.emitter.emit('accountsChanged', this.accounts)
|
|
254
|
+
)
|
|
255
|
+
this.emitter.emit('accountsChanged', this.accounts)
|
|
244
256
|
}
|
|
245
257
|
} else if (event === 'chainChanged') {
|
|
246
258
|
// Handle multiple formats:
|
|
247
259
|
// - { chainId: 'eip155:137' } or { chainId: '0x89' } or { chainId: 137 }
|
|
248
260
|
// - { chain: 'eip155:137' }
|
|
249
261
|
// - raw string 'eip155:137' or '0x89'
|
|
250
|
-
const raw =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
262
|
+
const raw =
|
|
263
|
+
typeof data === 'object' && data !== null
|
|
264
|
+
? ((data as Record<string, unknown>).chainId ?? (data as Record<string, unknown>).chain)
|
|
265
|
+
: data
|
|
266
|
+
let newChainId: number | null = null
|
|
254
267
|
if (typeof raw === 'string') {
|
|
255
268
|
if (raw.startsWith('eip155:')) {
|
|
256
|
-
newChainId = evmNumericChainId(raw)
|
|
269
|
+
newChainId = evmNumericChainId(raw)
|
|
257
270
|
} else if (raw.startsWith('0x')) {
|
|
258
|
-
newChainId = Number.parseInt(raw, 16)
|
|
271
|
+
newChainId = Number.parseInt(raw, 16)
|
|
259
272
|
} else {
|
|
260
|
-
newChainId = Number.parseInt(raw, 10) || null
|
|
273
|
+
newChainId = Number.parseInt(raw, 10) || null
|
|
261
274
|
}
|
|
262
275
|
} else if (typeof raw === 'number') {
|
|
263
|
-
newChainId = raw
|
|
276
|
+
newChainId = raw
|
|
264
277
|
}
|
|
265
278
|
if (newChainId != null && newChainId !== this.chainId) {
|
|
266
|
-
this.chainId = newChainId
|
|
267
|
-
this.emitter.emit('chainChanged', `0x${newChainId.toString(16)}`)
|
|
279
|
+
this.chainId = newChainId
|
|
280
|
+
this.emitter.emit('chainChanged', `0x${newChainId.toString(16)}`)
|
|
268
281
|
}
|
|
269
282
|
}
|
|
270
|
-
})
|
|
283
|
+
})
|
|
271
284
|
}
|
|
272
285
|
|
|
273
286
|
async request(args: EIP1193RequestArgs): Promise<unknown> {
|
|
274
287
|
if (this.disconnected) {
|
|
275
|
-
throw Object.assign(new Error('Provider is disconnected'), { code: 4900 })
|
|
288
|
+
throw Object.assign(new Error('Provider is disconnected'), { code: 4900 })
|
|
276
289
|
}
|
|
277
290
|
|
|
278
|
-
const { method, params } = args
|
|
291
|
+
const { method, params } = args
|
|
279
292
|
|
|
280
293
|
if (method === 'eth_chainId') {
|
|
281
|
-
return `0x${this.chainId.toString(16)}
|
|
294
|
+
return `0x${this.chainId.toString(16)}`
|
|
282
295
|
}
|
|
283
296
|
if (method === 'net_version') {
|
|
284
|
-
return String(this.chainId)
|
|
297
|
+
return String(this.chainId)
|
|
285
298
|
}
|
|
286
299
|
|
|
287
300
|
// Route non-wallet methods to RPC provider if available
|
|
288
301
|
if (!WALLET_METHODS.has(method) && !LOCAL_METHODS.has(method)) {
|
|
289
302
|
if (this.rpcProvider) {
|
|
290
|
-
return this.rpcProvider.request(args)
|
|
303
|
+
return this.rpcProvider.request(args)
|
|
291
304
|
}
|
|
292
|
-
throw Object.assign(
|
|
305
|
+
throw Object.assign(
|
|
306
|
+
new Error(`Unsupported method: ${method}. Pass rpcProvider to handle read-only RPC calls.`),
|
|
307
|
+
{ code: 4200 },
|
|
308
|
+
)
|
|
293
309
|
}
|
|
294
310
|
|
|
295
|
-
const mapped = this.mapper.mapRequest(method, params)
|
|
311
|
+
const mapped = this.mapper.mapRequest(method, params)
|
|
296
312
|
if (!mapped) {
|
|
297
|
-
throw Object.assign(new Error(`Unsupported method: ${method}`), { code: 4200 })
|
|
313
|
+
throw Object.assign(new Error(`Unsupported method: ${method}`), { code: 4200 })
|
|
298
314
|
}
|
|
299
315
|
|
|
300
316
|
// Inject chain for methods that require it per EVM sub-protocol
|
|
301
317
|
const chainRequiredMethods = [
|
|
302
|
-
'wallet_signMessage',
|
|
303
|
-
'
|
|
318
|
+
'wallet_signMessage',
|
|
319
|
+
'wallet_signTypedData',
|
|
320
|
+
'wallet_signTransaction',
|
|
321
|
+
'wallet_sendTransaction',
|
|
304
322
|
'wallet_getAccounts',
|
|
305
|
-
]
|
|
306
|
-
if (
|
|
307
|
-
|
|
323
|
+
]
|
|
324
|
+
if (
|
|
325
|
+
mapped.params &&
|
|
326
|
+
typeof mapped.params === 'object' &&
|
|
327
|
+
chainRequiredMethods.includes(mapped.method)
|
|
328
|
+
) {
|
|
329
|
+
const p = mapped.params as Record<string, unknown>
|
|
308
330
|
if (!p.chain) {
|
|
309
|
-
p.chain = `eip155:${this.chainId}
|
|
331
|
+
p.chain = `eip155:${this.chainId}`
|
|
310
332
|
}
|
|
311
333
|
}
|
|
312
334
|
|
|
313
|
-
const result = await this.session.request(mapped.method, mapped.params)
|
|
314
|
-
const mappedResult = this.mapper.mapResponse(method, result)
|
|
335
|
+
const result = await this.session.request(mapped.method, mapped.params)
|
|
336
|
+
const mappedResult = this.mapper.mapResponse(method, result)
|
|
315
337
|
|
|
316
338
|
if (method === 'eth_requestAccounts' || method === 'eth_accounts') {
|
|
317
|
-
if (Array.isArray(mappedResult)) this.accounts = mappedResult
|
|
339
|
+
if (Array.isArray(mappedResult)) this.accounts = mappedResult
|
|
318
340
|
}
|
|
319
341
|
|
|
320
|
-
return mappedResult
|
|
342
|
+
return mappedResult
|
|
321
343
|
}
|
|
322
344
|
|
|
345
|
+
// biome-ignore lint/suspicious/noExplicitAny: EIP-1193 interface requires generic handler signature
|
|
323
346
|
on(event: string, handler: (...args: any[]) => void): void {
|
|
324
|
-
this.emitter.on(event as keyof EIP1193ProviderEvents, handler as
|
|
347
|
+
this.emitter.on(event as keyof EIP1193ProviderEvents, handler as (data: unknown) => void)
|
|
325
348
|
}
|
|
326
349
|
|
|
350
|
+
// biome-ignore lint/suspicious/noExplicitAny: EIP-1193 interface requires generic handler signature
|
|
327
351
|
removeListener(event: string, handler: (...args: any[]) => void): void {
|
|
328
|
-
this.emitter.off(event as keyof EIP1193ProviderEvents, handler as
|
|
352
|
+
this.emitter.off(event as keyof EIP1193ProviderEvents, handler as (data: unknown) => void)
|
|
329
353
|
}
|
|
330
354
|
|
|
331
355
|
getChainId(): string {
|
|
332
|
-
return `0x${this.chainId.toString(16)}
|
|
356
|
+
return `0x${this.chainId.toString(16)}`
|
|
333
357
|
}
|
|
334
358
|
|
|
335
359
|
getAccounts(): string[] {
|
|
336
|
-
return this.accounts
|
|
360
|
+
return this.accounts
|
|
337
361
|
}
|
|
338
362
|
|
|
339
363
|
isConnected(): boolean {
|
|
340
|
-
return this.connected
|
|
364
|
+
return this.connected
|
|
341
365
|
}
|
|
342
366
|
|
|
343
367
|
getSession(): DAppSession {
|
|
344
|
-
return this.session
|
|
368
|
+
return this.session
|
|
345
369
|
}
|
|
346
370
|
}
|
package/src/evm/index.ts
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export {
|
|
8
|
-
WalletPairProvider,
|
|
9
|
-
type WalletPairProviderOptions,
|
|
10
8
|
type EIP1193Provider,
|
|
11
9
|
type EIP1193ProviderEvents,
|
|
12
10
|
type EIP1193RequestArgs,
|
|
13
11
|
type MethodMapper,
|
|
14
|
-
|
|
12
|
+
WalletPairProvider,
|
|
13
|
+
type WalletPairProviderOptions,
|
|
14
|
+
} from './eip1193.js'
|
|
15
15
|
|
|
16
16
|
export {
|
|
17
|
-
walletPair,
|
|
18
17
|
type WalletPairConnectorOptions,
|
|
19
|
-
|
|
18
|
+
walletPair,
|
|
19
|
+
} from './wagmi.js'
|
package/src/evm/wagmi.test.ts
CHANGED
|
@@ -391,6 +391,6 @@ describe('walletPair connector factory', () => {
|
|
|
391
391
|
|
|
392
392
|
// switchChain will fail because session isn't connected, but the chain validation
|
|
393
393
|
// happens after the request. We test the error path.
|
|
394
|
-
await expect(connector.switchChain
|
|
394
|
+
await expect(connector.switchChain?.({ chainId: 999 })).rejects.toThrow()
|
|
395
395
|
})
|
|
396
396
|
})
|