accounts 0.3.0 → 0.4.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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/core/AccessKey.d.ts +55 -0
- package/dist/core/AccessKey.d.ts.map +1 -0
- package/dist/core/AccessKey.js +69 -0
- package/dist/core/AccessKey.js.map +1 -0
- package/dist/core/Account.d.ts +91 -0
- package/dist/core/Account.d.ts.map +1 -0
- package/dist/core/Account.js +64 -0
- package/dist/core/Account.js.map +1 -0
- package/dist/core/Adapter.d.ts +187 -0
- package/dist/core/Adapter.d.ts.map +1 -0
- package/dist/core/Adapter.js +7 -0
- package/dist/core/Adapter.js.map +1 -0
- package/dist/core/Ceremony.d.ts +109 -0
- package/dist/core/Ceremony.d.ts.map +1 -0
- package/dist/core/Ceremony.js +104 -0
- package/dist/core/Ceremony.js.map +1 -0
- package/dist/core/Client.d.ts +16 -0
- package/dist/core/Client.d.ts.map +1 -0
- package/dist/core/Client.js +18 -0
- package/dist/core/Client.js.map +1 -0
- package/dist/core/Dialog.d.ts +52 -0
- package/dist/core/Dialog.d.ts.map +1 -0
- package/dist/core/Dialog.js +342 -0
- package/dist/core/Dialog.js.map +1 -0
- package/dist/core/Expiry.d.ts +15 -0
- package/dist/core/Expiry.d.ts.map +1 -0
- package/dist/core/Expiry.js +29 -0
- package/dist/core/Expiry.js.map +1 -0
- package/dist/core/Messenger.d.ts +86 -0
- package/dist/core/Messenger.d.ts.map +1 -0
- package/dist/core/Messenger.js +127 -0
- package/dist/core/Messenger.js.map +1 -0
- package/dist/core/Provider.d.ts +69 -0
- package/dist/core/Provider.d.ts.map +1 -0
- package/dist/core/Provider.js +401 -0
- package/dist/core/Provider.js.map +1 -0
- package/dist/core/Remote.d.ts +114 -0
- package/dist/core/Remote.d.ts.map +1 -0
- package/dist/core/Remote.js +116 -0
- package/dist/core/Remote.js.map +1 -0
- package/dist/core/Schema.d.ts +805 -0
- package/dist/core/Schema.d.ts.map +1 -0
- package/dist/core/Schema.js +43 -0
- package/dist/core/Schema.js.map +1 -0
- package/dist/core/Storage.d.ts +42 -0
- package/dist/core/Storage.d.ts.map +1 -0
- package/dist/core/Storage.js +173 -0
- package/dist/core/Storage.js.map +1 -0
- package/dist/core/Store.d.ts +58 -0
- package/dist/core/Store.d.ts.map +1 -0
- package/dist/core/Store.js +58 -0
- package/dist/core/Store.js.map +1 -0
- package/dist/core/adapters/dangerous_secp256k1.d.ts +30 -0
- package/dist/core/adapters/dangerous_secp256k1.d.ts.map +1 -0
- package/dist/core/adapters/dangerous_secp256k1.js +39 -0
- package/dist/core/adapters/dangerous_secp256k1.js.map +1 -0
- package/dist/core/adapters/dialog.d.ts +31 -0
- package/dist/core/adapters/dialog.d.ts.map +1 -0
- package/dist/core/adapters/dialog.js +306 -0
- package/dist/core/adapters/dialog.js.map +1 -0
- package/dist/core/adapters/local.d.ts +33 -0
- package/dist/core/adapters/local.d.ts.map +1 -0
- package/dist/core/adapters/local.js +227 -0
- package/dist/core/adapters/local.js.map +1 -0
- package/dist/core/adapters/webAuthn.d.ts +36 -0
- package/dist/core/adapters/webAuthn.d.ts.map +1 -0
- package/dist/core/adapters/webAuthn.js +93 -0
- package/dist/core/adapters/webAuthn.js.map +1 -0
- package/dist/core/internal/withDedupe.d.ts +12 -0
- package/dist/core/internal/withDedupe.d.ts.map +1 -0
- package/dist/core/internal/withDedupe.js +12 -0
- package/dist/core/internal/withDedupe.js.map +1 -0
- package/dist/core/zod/request.d.ts +31 -0
- package/dist/core/zod/request.d.ts.map +1 -0
- package/dist/core/zod/request.js +41 -0
- package/dist/core/zod/request.js.map +1 -0
- package/dist/core/zod/rpc.d.ts +603 -0
- package/dist/core/zod/rpc.d.ts.map +1 -0
- package/dist/core/zod/rpc.js +293 -0
- package/dist/core/zod/rpc.js.map +1 -0
- package/dist/core/zod/utils.d.ts +18 -0
- package/dist/core/zod/utils.d.ts.map +1 -0
- package/dist/core/zod/utils.js +21 -0
- package/dist/core/zod/utils.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/types.d.ts +284 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/server/Handler.d.ts +257 -0
- package/dist/server/Handler.d.ts.map +1 -0
- package/dist/server/Handler.js +433 -0
- package/dist/server/Handler.js.map +1 -0
- package/dist/server/Kv.d.ts +16 -0
- package/dist/server/Kv.d.ts.map +1 -0
- package/dist/server/Kv.js +30 -0
- package/dist/server/Kv.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/internal/requestListener.d.ts +124 -0
- package/dist/server/internal/requestListener.d.ts.map +1 -0
- package/dist/server/internal/requestListener.js +173 -0
- package/dist/server/internal/requestListener.js.map +1 -0
- package/dist/wagmi/Connector.d.ts +93 -0
- package/dist/wagmi/Connector.d.ts.map +1 -0
- package/dist/wagmi/Connector.js +238 -0
- package/dist/wagmi/Connector.js.map +1 -0
- package/dist/wagmi/index.d.ts +3 -0
- package/dist/wagmi/index.d.ts.map +1 -0
- package/dist/wagmi/index.js +3 -0
- package/dist/wagmi/index.js.map +1 -0
- package/package.json +56 -2
- package/src/core/AccessKey.test.ts +257 -0
- package/src/core/AccessKey.ts +123 -0
- package/src/core/Account.test.ts +309 -0
- package/src/core/Account.ts +152 -0
- package/src/core/Adapter.ts +238 -0
- package/src/core/Ceremony.browser.test.ts +239 -0
- package/src/core/Ceremony.test.ts +151 -0
- package/src/core/Ceremony.ts +203 -0
- package/src/core/Client.ts +36 -0
- package/src/core/Dialog.browser.test.ts +309 -0
- package/src/core/Dialog.test-d.ts +19 -0
- package/src/core/Dialog.ts +442 -0
- package/src/core/Expiry.ts +34 -0
- package/src/core/Messenger.ts +206 -0
- package/src/core/Provider.browser.test.ts +774 -0
- package/src/core/Provider.connect.browser.test.ts +415 -0
- package/src/core/Provider.test-d.ts +53 -0
- package/src/core/Provider.test.ts +1566 -0
- package/src/core/Provider.ts +559 -0
- package/src/core/Remote.ts +262 -0
- package/src/core/Schema.test-d.ts +211 -0
- package/src/core/Schema.ts +143 -0
- package/src/core/Storage.ts +213 -0
- package/src/core/Store.test.ts +287 -0
- package/src/core/Store.ts +129 -0
- package/src/core/adapters/dangerous_secp256k1.ts +53 -0
- package/src/core/adapters/dialog.ts +379 -0
- package/src/core/adapters/local.test.ts +97 -0
- package/src/core/adapters/local.ts +277 -0
- package/src/core/adapters/webAuthn.ts +129 -0
- package/src/core/internal/withDedupe.test.ts +116 -0
- package/src/core/internal/withDedupe.ts +20 -0
- package/src/core/mppx.test.ts +83 -0
- package/src/core/zod/request.test.ts +121 -0
- package/src/core/zod/request.ts +70 -0
- package/src/core/zod/rpc.ts +374 -0
- package/src/core/zod/utils.test.ts +69 -0
- package/src/core/zod/utils.ts +40 -0
- package/src/index.ts +14 -0
- package/src/internal/types.ts +378 -0
- package/src/server/Handler.test.ts +1014 -0
- package/src/server/Handler.ts +605 -0
- package/src/server/Kv.ts +46 -0
- package/src/server/index.ts +2 -0
- package/src/server/internal/requestListener.ts +273 -0
- package/src/tsconfig.json +9 -0
- package/src/wagmi/Connector.ts +287 -0
- package/src/wagmi/index.ts +2 -0
|
@@ -0,0 +1,1566 @@
|
|
|
1
|
+
import { Hex, Provider as core_Provider, WebCryptoP256 } from 'ox'
|
|
2
|
+
import { KeyAuthorization } from 'ox/tempo'
|
|
3
|
+
import { type Address, createClient, custom, parseUnits } from 'viem'
|
|
4
|
+
import {
|
|
5
|
+
getBalance,
|
|
6
|
+
sendTransactionSync,
|
|
7
|
+
signMessage,
|
|
8
|
+
verifyHash,
|
|
9
|
+
verifyMessage,
|
|
10
|
+
verifyTypedData,
|
|
11
|
+
waitForTransactionReceipt,
|
|
12
|
+
} from 'viem/actions'
|
|
13
|
+
import { tempo, tempoModerato } from 'viem/chains'
|
|
14
|
+
import { Account as TempoAccount, Actions, Addresses } from 'viem/tempo'
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, test } from 'vp/test'
|
|
16
|
+
|
|
17
|
+
import { headlessWebAuthn, secp256k1 } from '../../test/adapters.js'
|
|
18
|
+
import { accounts, chain, getClient, http } from '../../test/config.js'
|
|
19
|
+
import { createServer, type Server } from '../../test/utils.js'
|
|
20
|
+
import * as Handler from '../server/Handler.js'
|
|
21
|
+
import * as Expiry from './Expiry.js'
|
|
22
|
+
import * as Provider from './Provider.js'
|
|
23
|
+
import * as Storage from './Storage.js'
|
|
24
|
+
|
|
25
|
+
const adapters = [
|
|
26
|
+
{ name: 'headlessWebAuthn', adapter: headlessWebAuthn },
|
|
27
|
+
{ name: 'secp256k1', adapter: secp256k1 },
|
|
28
|
+
] as const
|
|
29
|
+
|
|
30
|
+
describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
31
|
+
const transferCall = Actions.token.transfer.call({
|
|
32
|
+
to: '0x0000000000000000000000000000000000000001',
|
|
33
|
+
token: Addresses.pathUsd,
|
|
34
|
+
amount: parseUnits('1', 6),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/** Connects via login (or register if login returns no accounts), returns the active account address. */
|
|
38
|
+
async function connect(provider: ReturnType<typeof Provider.create>) {
|
|
39
|
+
const login = await provider.request({ method: 'wallet_connect' })
|
|
40
|
+
if (login.accounts.length > 0) return login.accounts[0]!.address
|
|
41
|
+
const register = await provider.request({
|
|
42
|
+
method: 'wallet_connect',
|
|
43
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
44
|
+
})
|
|
45
|
+
return register.accounts[0]!.address
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Funds an address with PathUSD from the pre-funded test account. */
|
|
49
|
+
async function fund(address: Address) {
|
|
50
|
+
const client = getClient()
|
|
51
|
+
await Actions.token.transferSync(client, {
|
|
52
|
+
account: accounts[0]!,
|
|
53
|
+
feeToken: Addresses.pathUsd,
|
|
54
|
+
to: address,
|
|
55
|
+
token: Addresses.pathUsd,
|
|
56
|
+
amount: parseUnits('10', 6),
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('create', () => {
|
|
61
|
+
test('default: returns an EIP-1193 provider', async () => {
|
|
62
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
63
|
+
expect(typeof provider.request).toMatch(/function/)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('eth_chainId', () => {
|
|
68
|
+
test('default: returns configured chain ID as hex', async () => {
|
|
69
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
70
|
+
const chainId = await provider.request({ method: 'eth_chainId' })
|
|
71
|
+
expect(chainId).toMatchInlineSnapshot(`"0x1079"`)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('eth_accounts', () => {
|
|
76
|
+
test('default: returns empty array initially', async () => {
|
|
77
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
78
|
+
const accounts = await provider.request({ method: 'eth_accounts' })
|
|
79
|
+
expect(accounts).toMatchInlineSnapshot(`[]`)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('behavior: returns accounts after connecting', async () => {
|
|
83
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
84
|
+
|
|
85
|
+
await connect(provider)
|
|
86
|
+
const result = await provider.request({ method: 'eth_accounts' })
|
|
87
|
+
expect(result.length).toBeGreaterThanOrEqual(1)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('eth_requestAccounts', () => {
|
|
92
|
+
test('default: returns accounts after connecting', async () => {
|
|
93
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
94
|
+
await connect(provider)
|
|
95
|
+
const result = await provider.request({ method: 'eth_requestAccounts' })
|
|
96
|
+
expect(result.length).toBeGreaterThanOrEqual(1)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('wallet_connect', () => {
|
|
101
|
+
test('default: without capabilities calls loadAccounts', async () => {
|
|
102
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
103
|
+
const result = await provider.request({ method: 'wallet_connect' })
|
|
104
|
+
for (const account of result.accounts) {
|
|
105
|
+
expect(account.address).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
106
|
+
expect(account.capabilities).toMatchInlineSnapshot(`{}`)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('behavior: with register capability calls createAccount', async () => {
|
|
111
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
112
|
+
|
|
113
|
+
const result = await provider.request({
|
|
114
|
+
method: 'wallet_connect',
|
|
115
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
116
|
+
})
|
|
117
|
+
expect(result.accounts.length).toMatchInlineSnapshot(`1`)
|
|
118
|
+
expect(result.accounts[0]!.address).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
119
|
+
expect(result.accounts[0]!.capabilities).toMatchInlineSnapshot(`{}`)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('behavior: register passes name to createAccount', async () => {
|
|
123
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
124
|
+
|
|
125
|
+
await provider.request({
|
|
126
|
+
method: 'wallet_connect',
|
|
127
|
+
params: [{ capabilities: { method: 'register', name: 'alice' } }],
|
|
128
|
+
})
|
|
129
|
+
expect(provider.store.getState().accounts.length).toBeGreaterThanOrEqual(1)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('behavior: register defaults name to "default"', async () => {
|
|
133
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
134
|
+
|
|
135
|
+
await provider.request({
|
|
136
|
+
method: 'wallet_connect',
|
|
137
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
138
|
+
})
|
|
139
|
+
expect(provider.store.getState().accounts.length).toBeGreaterThanOrEqual(1)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('behavior: login sets activeAccount to loaded account', async () => {
|
|
143
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
144
|
+
|
|
145
|
+
await provider.request({
|
|
146
|
+
method: 'wallet_connect',
|
|
147
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
148
|
+
})
|
|
149
|
+
const login = await provider.request({ method: 'wallet_connect' })
|
|
150
|
+
const result = await provider.request({ method: 'wallet_connect' })
|
|
151
|
+
expect(result.accounts[0]!.address).toBe(login.accounts[0]!.address)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('behavior: login with digest returns signature in account capabilities', async () => {
|
|
155
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
156
|
+
|
|
157
|
+
await connect(provider)
|
|
158
|
+
const result = await provider.request({
|
|
159
|
+
method: 'wallet_connect',
|
|
160
|
+
params: [{ capabilities: { digest: '0x1234' } }],
|
|
161
|
+
})
|
|
162
|
+
expect(result.accounts[0]!.capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('behavior: digest signature is verifiable on-chain', async () => {
|
|
166
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
167
|
+
const client = provider.getClient()
|
|
168
|
+
|
|
169
|
+
await connect(provider)
|
|
170
|
+
const digest = '0x00000000000000000000000000000000000000000000000000000000deadbeef' as const
|
|
171
|
+
const result = await provider.request({
|
|
172
|
+
method: 'wallet_connect',
|
|
173
|
+
params: [{ capabilities: { digest } }],
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const valid = await verifyHash(client, {
|
|
177
|
+
address: result.accounts[0]!.address,
|
|
178
|
+
hash: digest,
|
|
179
|
+
signature: result.accounts[0]!.capabilities.signature!,
|
|
180
|
+
})
|
|
181
|
+
expect(valid).toMatchInlineSnapshot(`true`)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('behavior: login without digest returns empty capabilities', async () => {
|
|
185
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
186
|
+
await connect(provider)
|
|
187
|
+
const result = await provider.request({ method: 'wallet_connect' })
|
|
188
|
+
expect(result.accounts[0]!.capabilities).toMatchInlineSnapshot(`{}`)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('behavior: register without digest returns empty capabilities', async () => {
|
|
192
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
193
|
+
|
|
194
|
+
const result = await provider.request({
|
|
195
|
+
method: 'wallet_connect',
|
|
196
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
197
|
+
})
|
|
198
|
+
expect(result.accounts[0]!.capabilities).toMatchInlineSnapshot(`{}`)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('behavior: register with digest returns signature in capabilities', async () => {
|
|
202
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
203
|
+
|
|
204
|
+
const result = await provider.request({
|
|
205
|
+
method: 'wallet_connect',
|
|
206
|
+
params: [{ capabilities: { method: 'register', digest: '0x1234' } }],
|
|
207
|
+
})
|
|
208
|
+
expect(result.accounts[0]!.capabilities.signature).toMatch(/^0x[0-9a-f]+$/)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('behavior: register digest signature is verifiable on-chain', async () => {
|
|
212
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
213
|
+
const client = provider.getClient()
|
|
214
|
+
|
|
215
|
+
const digest = '0x00000000000000000000000000000000000000000000000000000000deadbeef' as const
|
|
216
|
+
const result = await provider.request({
|
|
217
|
+
method: 'wallet_connect',
|
|
218
|
+
params: [{ capabilities: { method: 'register', digest } }],
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const valid = await verifyHash(client, {
|
|
222
|
+
address: result.accounts[0]!.address,
|
|
223
|
+
hash: digest,
|
|
224
|
+
signature: result.accounts[0]!.capabilities.signature!,
|
|
225
|
+
})
|
|
226
|
+
expect(valid).toMatchInlineSnapshot(`true`)
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
describe('wallet_disconnect', () => {
|
|
231
|
+
test('default: disconnects and clears accounts', async () => {
|
|
232
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
233
|
+
|
|
234
|
+
await connect(provider)
|
|
235
|
+
await provider.request({ method: 'wallet_disconnect' })
|
|
236
|
+
|
|
237
|
+
const accounts = await provider.request({ method: 'eth_accounts' })
|
|
238
|
+
expect(accounts).toMatchInlineSnapshot(`[]`)
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
describe('wallet_switchEthereumChain', () => {
|
|
243
|
+
test('default: switches chain', async () => {
|
|
244
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
245
|
+
|
|
246
|
+
await provider.request({
|
|
247
|
+
method: 'wallet_switchEthereumChain',
|
|
248
|
+
params: [{ chainId: `0x${tempoModerato.id.toString(16)}` }],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const chainId = await provider.request({ method: 'eth_chainId' })
|
|
252
|
+
expect(chainId).toMatchInlineSnapshot(`"0xa5bf"`)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('error: throws for unconfigured chain', async () => {
|
|
256
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
257
|
+
|
|
258
|
+
await expect(
|
|
259
|
+
provider.request({
|
|
260
|
+
method: 'wallet_switchEthereumChain',
|
|
261
|
+
params: [{ chainId: '0x1' }],
|
|
262
|
+
}),
|
|
263
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
264
|
+
`[Provider.UnsupportedChainIdError: Chain 1 not configured.]`,
|
|
265
|
+
)
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
describe('events', () => {
|
|
270
|
+
test('behavior: emits accountsChanged on connect', async () => {
|
|
271
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
272
|
+
|
|
273
|
+
const events: unknown[] = []
|
|
274
|
+
provider.on('accountsChanged', (accounts) => events.push(accounts))
|
|
275
|
+
|
|
276
|
+
const connected = await connect(provider)
|
|
277
|
+
|
|
278
|
+
expect(events).toEqual([[connected]])
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('behavior: emits connect on status change', async () => {
|
|
282
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
283
|
+
|
|
284
|
+
const events: unknown[] = []
|
|
285
|
+
provider.on('connect', (info) => events.push(info))
|
|
286
|
+
|
|
287
|
+
await connect(provider)
|
|
288
|
+
|
|
289
|
+
expect(events).toMatchInlineSnapshot(`
|
|
290
|
+
[
|
|
291
|
+
{
|
|
292
|
+
"chainId": "0x1079",
|
|
293
|
+
},
|
|
294
|
+
]
|
|
295
|
+
`)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('behavior: emits disconnect on disconnect', async () => {
|
|
299
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
300
|
+
|
|
301
|
+
await connect(provider)
|
|
302
|
+
|
|
303
|
+
const events: unknown[] = []
|
|
304
|
+
provider.on('disconnect', (error) => events.push(error))
|
|
305
|
+
|
|
306
|
+
await provider.request({ method: 'wallet_disconnect' })
|
|
307
|
+
|
|
308
|
+
expect(events.length).toMatchInlineSnapshot(`1`)
|
|
309
|
+
expect(events[0]).toBeInstanceOf(core_Provider.DisconnectedError)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('behavior: does not emit accountsChanged on duplicate login', async () => {
|
|
313
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
314
|
+
|
|
315
|
+
await connect(provider)
|
|
316
|
+
|
|
317
|
+
const events: unknown[] = []
|
|
318
|
+
provider.on('accountsChanged', (accounts) => events.push(accounts))
|
|
319
|
+
|
|
320
|
+
await provider.request({ method: 'wallet_connect' })
|
|
321
|
+
|
|
322
|
+
expect(events).toMatchInlineSnapshot(`[]`)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
test('behavior: emits chainChanged on switch', async () => {
|
|
326
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
327
|
+
|
|
328
|
+
const events: unknown[] = []
|
|
329
|
+
provider.on('chainChanged', (chainId) => events.push(chainId))
|
|
330
|
+
|
|
331
|
+
await provider.request({
|
|
332
|
+
method: 'wallet_switchEthereumChain',
|
|
333
|
+
params: [{ chainId: `0x${tempoModerato.id.toString(16)}` }],
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
expect(events).toMatchInlineSnapshot(`
|
|
337
|
+
[
|
|
338
|
+
"0xa5bf",
|
|
339
|
+
]
|
|
340
|
+
`)
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
describe('eth_sendTransaction', () => {
|
|
345
|
+
test('default: sends transaction and returns hash', async () => {
|
|
346
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
347
|
+
|
|
348
|
+
const connected = await connect(provider)
|
|
349
|
+
await fund(connected)
|
|
350
|
+
|
|
351
|
+
const hash = await provider.request({
|
|
352
|
+
method: 'eth_sendTransaction',
|
|
353
|
+
params: [{ calls: [transferCall] }],
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
expect(hash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
test('behavior: transaction is confirmed on-chain', async () => {
|
|
360
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
361
|
+
|
|
362
|
+
const connected = await connect(provider)
|
|
363
|
+
await fund(connected)
|
|
364
|
+
|
|
365
|
+
const hash = await provider.request({
|
|
366
|
+
method: 'eth_sendTransaction',
|
|
367
|
+
params: [{ calls: [transferCall] }],
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
const client = provider.getClient()
|
|
371
|
+
const receipt = await waitForTransactionReceipt(client, { hash })
|
|
372
|
+
|
|
373
|
+
const {
|
|
374
|
+
blockHash,
|
|
375
|
+
blockNumber,
|
|
376
|
+
cumulativeGasUsed,
|
|
377
|
+
effectiveGasPrice,
|
|
378
|
+
feePayer,
|
|
379
|
+
from,
|
|
380
|
+
gasUsed,
|
|
381
|
+
logs,
|
|
382
|
+
logsBloom,
|
|
383
|
+
transactionHash,
|
|
384
|
+
transactionIndex,
|
|
385
|
+
...rest
|
|
386
|
+
} = receipt
|
|
387
|
+
expect(blockHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
388
|
+
expect(typeof blockNumber).toMatch(/bigint/)
|
|
389
|
+
expect(typeof cumulativeGasUsed).toMatch(/bigint/)
|
|
390
|
+
expect(typeof effectiveGasPrice).toMatch(/bigint/)
|
|
391
|
+
expect(feePayer).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
392
|
+
expect(from).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
393
|
+
expect(typeof gasUsed).toMatch(/bigint/)
|
|
394
|
+
for (const log of logs) expect(log.address).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
395
|
+
expect(logsBloom).toMatch(/^0x/)
|
|
396
|
+
expect(transactionHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
397
|
+
expect(typeof transactionIndex).toMatch(/number/)
|
|
398
|
+
expect(rest).toMatchInlineSnapshot(`
|
|
399
|
+
{
|
|
400
|
+
"contractAddress": null,
|
|
401
|
+
"feeToken": "0x20c0000000000000000000000000000000000000",
|
|
402
|
+
"status": "success",
|
|
403
|
+
"to": "0x20c0000000000000000000000000000000000000",
|
|
404
|
+
"type": "0x76",
|
|
405
|
+
}
|
|
406
|
+
`)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
describe('eth_sendTransactionSync', () => {
|
|
411
|
+
test('default: sends transaction and returns receipt', async () => {
|
|
412
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
413
|
+
|
|
414
|
+
const connected = await connect(provider)
|
|
415
|
+
await fund(connected)
|
|
416
|
+
|
|
417
|
+
const receipt = await provider.request({
|
|
418
|
+
method: 'eth_sendTransactionSync',
|
|
419
|
+
params: [{ calls: [transferCall] }],
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
const {
|
|
423
|
+
blockHash,
|
|
424
|
+
blockNumber,
|
|
425
|
+
cumulativeGasUsed,
|
|
426
|
+
effectiveGasPrice,
|
|
427
|
+
feePayer,
|
|
428
|
+
from,
|
|
429
|
+
gasUsed,
|
|
430
|
+
logs,
|
|
431
|
+
logsBloom,
|
|
432
|
+
transactionHash,
|
|
433
|
+
transactionIndex,
|
|
434
|
+
...rest
|
|
435
|
+
} = receipt
|
|
436
|
+
expect(blockHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
437
|
+
expect(blockNumber).toMatch(/^0x/)
|
|
438
|
+
expect(cumulativeGasUsed).toMatch(/^0x/)
|
|
439
|
+
expect(effectiveGasPrice).toMatch(/^0x/)
|
|
440
|
+
expect(feePayer).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
441
|
+
expect(from).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
442
|
+
expect(gasUsed).toMatch(/^0x/)
|
|
443
|
+
for (const log of logs) expect(log.address).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
444
|
+
expect(logsBloom).toMatch(/^0x/)
|
|
445
|
+
expect(transactionHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
446
|
+
expect(transactionIndex).toMatch(/^0x/)
|
|
447
|
+
expect(rest).toMatchInlineSnapshot(`
|
|
448
|
+
{
|
|
449
|
+
"contractAddress": null,
|
|
450
|
+
"feeToken": "0x20c0000000000000000000000000000000000000",
|
|
451
|
+
"status": "0x1",
|
|
452
|
+
"to": "0x20c0000000000000000000000000000000000000",
|
|
453
|
+
"type": "0x76",
|
|
454
|
+
}
|
|
455
|
+
`)
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
describe('eth_signTransaction', () => {
|
|
460
|
+
test('default: signs transaction and returns serialized', async () => {
|
|
461
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
462
|
+
|
|
463
|
+
const connected = await connect(provider)
|
|
464
|
+
await fund(connected)
|
|
465
|
+
|
|
466
|
+
const signed = await provider.request({
|
|
467
|
+
method: 'eth_signTransaction',
|
|
468
|
+
params: [{ calls: [transferCall] }],
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
expect(signed).toMatch(/^0x/)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
test('behavior: signed transaction can be sent via eth_sendRawTransactionSync', async () => {
|
|
475
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
476
|
+
|
|
477
|
+
const connected = await connect(provider)
|
|
478
|
+
await fund(connected)
|
|
479
|
+
|
|
480
|
+
const signed = await provider.request({
|
|
481
|
+
method: 'eth_signTransaction',
|
|
482
|
+
params: [{ calls: [transferCall] }],
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
const receipt = await provider.request({
|
|
486
|
+
method: 'eth_sendRawTransactionSync',
|
|
487
|
+
params: [signed],
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
const {
|
|
491
|
+
blockHash,
|
|
492
|
+
blockNumber,
|
|
493
|
+
cumulativeGasUsed,
|
|
494
|
+
effectiveGasPrice,
|
|
495
|
+
// @ts-expect-error
|
|
496
|
+
feePayer,
|
|
497
|
+
from,
|
|
498
|
+
gasUsed,
|
|
499
|
+
logs,
|
|
500
|
+
logsBloom,
|
|
501
|
+
transactionHash,
|
|
502
|
+
transactionIndex,
|
|
503
|
+
...rest
|
|
504
|
+
} = receipt
|
|
505
|
+
expect(blockHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
506
|
+
expect(blockNumber).toMatch(/^0x/)
|
|
507
|
+
expect(cumulativeGasUsed).toMatch(/^0x/)
|
|
508
|
+
expect(effectiveGasPrice).toMatch(/^0x/)
|
|
509
|
+
expect(feePayer).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
510
|
+
expect(from).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
511
|
+
expect(gasUsed).toMatch(/^0x/)
|
|
512
|
+
for (const log of logs) expect(log.address).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
513
|
+
expect(logsBloom).toMatch(/^0x/)
|
|
514
|
+
expect(transactionHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
515
|
+
expect(transactionIndex).toMatch(/^0x/)
|
|
516
|
+
expect(rest).toMatchInlineSnapshot(`
|
|
517
|
+
{
|
|
518
|
+
"contractAddress": null,
|
|
519
|
+
"feeToken": "0x20c0000000000000000000000000000000000000",
|
|
520
|
+
"status": "0x1",
|
|
521
|
+
"to": "0x20c0000000000000000000000000000000000000",
|
|
522
|
+
"type": "0x76",
|
|
523
|
+
}
|
|
524
|
+
`)
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
test('error: throws when not connected', async () => {
|
|
528
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
529
|
+
|
|
530
|
+
await expect(
|
|
531
|
+
provider.request({
|
|
532
|
+
method: 'eth_signTransaction',
|
|
533
|
+
params: [{ calls: [transferCall] }],
|
|
534
|
+
}),
|
|
535
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
536
|
+
`[Provider.DisconnectedError: No accounts connected.]`,
|
|
537
|
+
)
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
describe('wallet_sendCalls', () => {
|
|
542
|
+
test('default: sends calls and returns id', async () => {
|
|
543
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
544
|
+
|
|
545
|
+
const connected = await connect(provider)
|
|
546
|
+
await fund(connected)
|
|
547
|
+
|
|
548
|
+
const result = await provider.request({
|
|
549
|
+
method: 'wallet_sendCalls',
|
|
550
|
+
params: [{ calls: [transferCall] }],
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
expect(result.id).toMatch(/^0x[0-9a-f]+$/)
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
test('behavior: with sync capability returns id and receipt is available', async () => {
|
|
557
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
558
|
+
|
|
559
|
+
const connected = await connect(provider)
|
|
560
|
+
await fund(connected)
|
|
561
|
+
|
|
562
|
+
const result = await provider.request({
|
|
563
|
+
method: 'wallet_sendCalls',
|
|
564
|
+
params: [
|
|
565
|
+
{
|
|
566
|
+
calls: [transferCall],
|
|
567
|
+
capabilities: { sync: true },
|
|
568
|
+
},
|
|
569
|
+
],
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
expect(result.id).toMatch(/^0x[0-9a-f]+$/)
|
|
573
|
+
expect(result.capabilities).toMatchInlineSnapshot(`
|
|
574
|
+
{
|
|
575
|
+
"sync": true,
|
|
576
|
+
}
|
|
577
|
+
`)
|
|
578
|
+
expect(result.atomic).toMatchInlineSnapshot(`true`)
|
|
579
|
+
expect(result.chainId).toMatch(/^0x[0-9a-f]+$/)
|
|
580
|
+
expect(result.status).toMatchInlineSnapshot(`200`)
|
|
581
|
+
expect(result.version).toMatchInlineSnapshot(`"2.0.0"`)
|
|
582
|
+
expect(result.receipts?.length).toMatchInlineSnapshot(`1`)
|
|
583
|
+
expect(result.receipts?.[0]?.status).toMatchInlineSnapshot(`"0x1"`)
|
|
584
|
+
})
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
describe('wallet_getCallsStatus', () => {
|
|
588
|
+
test('default: returns encoded status for a sent call batch', async () => {
|
|
589
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
590
|
+
|
|
591
|
+
const connected = await connect(provider)
|
|
592
|
+
await fund(connected)
|
|
593
|
+
|
|
594
|
+
const { id } = await provider.request({
|
|
595
|
+
method: 'wallet_sendCalls',
|
|
596
|
+
params: [
|
|
597
|
+
{
|
|
598
|
+
calls: [transferCall],
|
|
599
|
+
capabilities: { sync: true },
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
const result = await provider.request({
|
|
605
|
+
method: 'wallet_getCallsStatus',
|
|
606
|
+
params: [id],
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
expect(result.atomic).toMatchInlineSnapshot(`true`)
|
|
610
|
+
expect(result.chainId).toMatch(/^0x[0-9a-f]+$/)
|
|
611
|
+
expect(result.status).toMatchInlineSnapshot(`200`)
|
|
612
|
+
expect(result.version).toMatchInlineSnapshot(`"2.0.0"`)
|
|
613
|
+
expect(result.receipts?.length).toMatchInlineSnapshot(`1`)
|
|
614
|
+
expect(result.receipts?.[0]?.status).toMatchInlineSnapshot(`"0x1"`)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
test('error: throws for unsupported id format', async () => {
|
|
618
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
619
|
+
|
|
620
|
+
await expect(
|
|
621
|
+
provider.request({
|
|
622
|
+
method: 'wallet_getCallsStatus',
|
|
623
|
+
params: ['0xdeadbeef'],
|
|
624
|
+
}),
|
|
625
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
626
|
+
`[RpcResponse.InternalError: \`id\` not supported]`,
|
|
627
|
+
)
|
|
628
|
+
})
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
describe('wallet_getCapabilities', () => {
|
|
632
|
+
test('default: returns atomic supported for all chains', async () => {
|
|
633
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
634
|
+
|
|
635
|
+
const result = await provider.request({ method: 'wallet_getCapabilities' })
|
|
636
|
+
expect(result).toMatchInlineSnapshot(`
|
|
637
|
+
{
|
|
638
|
+
"0x1079": {
|
|
639
|
+
"accessKeys": {
|
|
640
|
+
"status": "supported",
|
|
641
|
+
},
|
|
642
|
+
"atomic": {
|
|
643
|
+
"status": "supported",
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
"0xa5bf": {
|
|
647
|
+
"accessKeys": {
|
|
648
|
+
"status": "supported",
|
|
649
|
+
},
|
|
650
|
+
"atomic": {
|
|
651
|
+
"status": "supported",
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
}
|
|
655
|
+
`)
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
test('behavior: filters by chainIds', async () => {
|
|
659
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
660
|
+
|
|
661
|
+
const connected = await connect(provider)
|
|
662
|
+
|
|
663
|
+
const result = await provider.request({
|
|
664
|
+
method: 'wallet_getCapabilities',
|
|
665
|
+
params: [connected, [Hex.fromNumber(tempoModerato.id)]],
|
|
666
|
+
})
|
|
667
|
+
expect(result).toMatchInlineSnapshot(`
|
|
668
|
+
{
|
|
669
|
+
"0xa5bf": {
|
|
670
|
+
"accessKeys": {
|
|
671
|
+
"status": "supported",
|
|
672
|
+
},
|
|
673
|
+
"atomic": {
|
|
674
|
+
"status": "supported",
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
}
|
|
678
|
+
`)
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
test('behavior: returns empty object for unknown chainIds', async () => {
|
|
682
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
683
|
+
|
|
684
|
+
const connected = await connect(provider)
|
|
685
|
+
|
|
686
|
+
const result = await provider.request({
|
|
687
|
+
method: 'wallet_getCapabilities',
|
|
688
|
+
params: [connected, ['0x1']],
|
|
689
|
+
})
|
|
690
|
+
expect(result).toMatchInlineSnapshot(`{}`)
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
test('error: throws UnauthorizedError for unconnected address', async () => {
|
|
694
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
695
|
+
|
|
696
|
+
await expect(
|
|
697
|
+
provider.request({
|
|
698
|
+
method: 'wallet_getCapabilities',
|
|
699
|
+
params: ['0x0000000000000000000000000000000000000001'],
|
|
700
|
+
}),
|
|
701
|
+
).rejects.toThrow(core_Provider.UnauthorizedError)
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
test('behavior: succeeds with connected address', async () => {
|
|
705
|
+
const provider = Provider.create({ adapter: adapter() })
|
|
706
|
+
|
|
707
|
+
const connected = await connect(provider)
|
|
708
|
+
|
|
709
|
+
const result = await provider.request({
|
|
710
|
+
method: 'wallet_getCapabilities',
|
|
711
|
+
params: [connected],
|
|
712
|
+
})
|
|
713
|
+
expect(Object.keys(result).length).toMatchInlineSnapshot(`2`)
|
|
714
|
+
expect(result[Hex.fromNumber(tempo.id)]!.atomic.status).toMatchInlineSnapshot(`"supported"`)
|
|
715
|
+
})
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
describe('wallet_getBalances', () => {
|
|
719
|
+
test('error: throws when no tokens provided', async () => {
|
|
720
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
721
|
+
|
|
722
|
+
await connect(provider)
|
|
723
|
+
|
|
724
|
+
await expect(
|
|
725
|
+
provider.request({ method: 'wallet_getBalances' }),
|
|
726
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
727
|
+
`[RpcResponse.InvalidParamsError: \`tokens\` is required.]`,
|
|
728
|
+
)
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
test('default: returns token balances with metadata', async () => {
|
|
732
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
733
|
+
|
|
734
|
+
await connect(provider)
|
|
735
|
+
|
|
736
|
+
const result = await provider.request({
|
|
737
|
+
method: 'wallet_getBalances',
|
|
738
|
+
params: [{ tokens: ['0x20c0000000000000000000000000000000000001'] }],
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
expect(result.length).toMatchInlineSnapshot(`1`)
|
|
742
|
+
expect(result[0]!.address).toMatchInlineSnapshot(
|
|
743
|
+
`"0x20c0000000000000000000000000000000000001"`,
|
|
744
|
+
)
|
|
745
|
+
expect(typeof result[0]!.name).toMatch(/string/)
|
|
746
|
+
expect(typeof result[0]!.symbol).toMatch(/string/)
|
|
747
|
+
expect(typeof result[0]!.decimals).toMatchInlineSnapshot(`"number"`)
|
|
748
|
+
expect(result[0]!.balance).toMatch(/^0x/)
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
test('behavior: accepts explicit account param', async () => {
|
|
752
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
753
|
+
|
|
754
|
+
const connected = await connect(provider)
|
|
755
|
+
|
|
756
|
+
const result = await provider.request({
|
|
757
|
+
method: 'wallet_getBalances',
|
|
758
|
+
params: [
|
|
759
|
+
{
|
|
760
|
+
account: connected,
|
|
761
|
+
tokens: ['0x20c0000000000000000000000000000000000001'],
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
})
|
|
765
|
+
|
|
766
|
+
expect(result.length).toMatchInlineSnapshot(`1`)
|
|
767
|
+
expect(result[0]!.balance).toMatch(/^0x/)
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
test('error: throws DisconnectedError when no accounts connected', async () => {
|
|
771
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
772
|
+
|
|
773
|
+
await expect(
|
|
774
|
+
provider.request({
|
|
775
|
+
method: 'wallet_getBalances',
|
|
776
|
+
params: [{ tokens: ['0x20c0000000000000000000000000000000000001'] }],
|
|
777
|
+
}),
|
|
778
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
779
|
+
`[Provider.DisconnectedError: No accounts connected.]`,
|
|
780
|
+
)
|
|
781
|
+
})
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
describe('eth_signTypedData_v4', () => {
|
|
785
|
+
const typedData = {
|
|
786
|
+
domain: { name: 'Test', version: '1', chainId: 1 },
|
|
787
|
+
types: {
|
|
788
|
+
Person: [
|
|
789
|
+
{ name: 'name', type: 'string' },
|
|
790
|
+
{ name: 'wallet', type: 'address' },
|
|
791
|
+
],
|
|
792
|
+
},
|
|
793
|
+
primaryType: 'Person' as const,
|
|
794
|
+
message: { name: 'Bob', wallet: '0x0000000000000000000000000000000000000000' },
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
test('default: signs typed data and returns signature', async () => {
|
|
798
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
799
|
+
|
|
800
|
+
const connected = await connect(provider)
|
|
801
|
+
|
|
802
|
+
const signature = await provider.request({
|
|
803
|
+
method: 'eth_signTypedData_v4',
|
|
804
|
+
params: [connected, JSON.stringify(typedData)],
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
expect(signature).toMatch(/^0x[0-9a-f]+$/)
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
test('behavior: signature is verifiable on-chain', async () => {
|
|
811
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
812
|
+
const client = provider.getClient()
|
|
813
|
+
|
|
814
|
+
const connected = await connect(provider)
|
|
815
|
+
|
|
816
|
+
const signature = await provider.request({
|
|
817
|
+
method: 'eth_signTypedData_v4',
|
|
818
|
+
params: [connected, JSON.stringify(typedData)],
|
|
819
|
+
})
|
|
820
|
+
|
|
821
|
+
const valid = await verifyTypedData(client, {
|
|
822
|
+
address: connected,
|
|
823
|
+
signature,
|
|
824
|
+
...typedData,
|
|
825
|
+
})
|
|
826
|
+
expect(valid).toMatchInlineSnapshot(`true`)
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
test('error: throws when not connected', async () => {
|
|
830
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
831
|
+
|
|
832
|
+
await expect(
|
|
833
|
+
provider.request({
|
|
834
|
+
method: 'eth_signTypedData_v4',
|
|
835
|
+
params: ['0x0000000000000000000000000000000000000001', JSON.stringify(typedData)],
|
|
836
|
+
}),
|
|
837
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
838
|
+
`[Provider.DisconnectedError: No accounts connected.]`,
|
|
839
|
+
)
|
|
840
|
+
})
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
describe('personal_sign', () => {
|
|
844
|
+
test('default: signs a message and returns signature', async () => {
|
|
845
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
846
|
+
|
|
847
|
+
const connected = await connect(provider)
|
|
848
|
+
|
|
849
|
+
const message = Hex.fromString('hello world')
|
|
850
|
+
const signature = await provider.request({
|
|
851
|
+
method: 'personal_sign',
|
|
852
|
+
params: [message, connected],
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
expect(signature).toMatch(/^0x[0-9a-f]+$/)
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
test('behavior: signature is verifiable on-chain', async () => {
|
|
859
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
860
|
+
const client = provider.getClient()
|
|
861
|
+
|
|
862
|
+
const connected = await connect(provider)
|
|
863
|
+
|
|
864
|
+
const message = Hex.fromString('hello world')
|
|
865
|
+
const signature = await provider.request({
|
|
866
|
+
method: 'personal_sign',
|
|
867
|
+
params: [message, connected],
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
const valid = await verifyMessage(client, {
|
|
871
|
+
address: connected,
|
|
872
|
+
message: { raw: message },
|
|
873
|
+
signature,
|
|
874
|
+
})
|
|
875
|
+
expect(valid).toMatchInlineSnapshot(`true`)
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
test('error: throws when not connected', async () => {
|
|
879
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
880
|
+
|
|
881
|
+
await expect(
|
|
882
|
+
provider.request({
|
|
883
|
+
method: 'personal_sign',
|
|
884
|
+
params: [Hex.fromString('hello'), '0x0000000000000000000000000000000000000001'],
|
|
885
|
+
}),
|
|
886
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
887
|
+
`[Provider.DisconnectedError: No accounts connected.]`,
|
|
888
|
+
)
|
|
889
|
+
})
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
describe('persistence', () => {
|
|
893
|
+
test('behavior: new provider hydrates accounts from shared storage', async () => {
|
|
894
|
+
const storage = Storage.memory({ key: 'persist-test' })
|
|
895
|
+
|
|
896
|
+
const provider1 = Provider.create({ adapter: adapter(), storage })
|
|
897
|
+
await connect(provider1)
|
|
898
|
+
|
|
899
|
+
const accts1 = await provider1.request({ method: 'eth_accounts' })
|
|
900
|
+
expect(accts1.length).toBeGreaterThanOrEqual(1)
|
|
901
|
+
|
|
902
|
+
// Create a second provider with the same storage — it should hydrate.
|
|
903
|
+
const provider2 = Provider.create({ adapter: adapter(), storage })
|
|
904
|
+
|
|
905
|
+
// Wait for hydration + reconnection.
|
|
906
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
907
|
+
|
|
908
|
+
const accts2 = await provider2.request({ method: 'eth_accounts' })
|
|
909
|
+
expect(accts2.length).toBeGreaterThanOrEqual(1)
|
|
910
|
+
expect(accts2[0]).toBe(accts1[0])
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
test('behavior: concurrent providers with different storage keys are isolated', async () => {
|
|
914
|
+
const providerA = Provider.create({
|
|
915
|
+
adapter: adapter(),
|
|
916
|
+
storage: Storage.memory({ key: 'provider-a' }),
|
|
917
|
+
})
|
|
918
|
+
const providerB = Provider.create({
|
|
919
|
+
adapter: adapter(),
|
|
920
|
+
storage: Storage.memory({ key: 'provider-b' }),
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
await connect(providerA)
|
|
924
|
+
|
|
925
|
+
const acctsA = await providerA.request({ method: 'eth_accounts' })
|
|
926
|
+
const acctsB = await providerB.request({ method: 'eth_accounts' })
|
|
927
|
+
|
|
928
|
+
expect(acctsA.length).toBeGreaterThanOrEqual(1)
|
|
929
|
+
expect(acctsB).toMatchInlineSnapshot(`[]`)
|
|
930
|
+
})
|
|
931
|
+
})
|
|
932
|
+
|
|
933
|
+
describe('reconnection', () => {
|
|
934
|
+
test('behavior: hydrated provider has accounts available', async () => {
|
|
935
|
+
const storage = Storage.memory({ key: 'reconnect' })
|
|
936
|
+
|
|
937
|
+
const provider1 = Provider.create({ adapter: adapter(), storage })
|
|
938
|
+
await connect(provider1)
|
|
939
|
+
|
|
940
|
+
const provider2 = Provider.create({ adapter: adapter(), storage })
|
|
941
|
+
|
|
942
|
+
// Wait for hydration.
|
|
943
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
944
|
+
|
|
945
|
+
const accts = await provider2.request({ method: 'eth_accounts' })
|
|
946
|
+
expect(accts.length).toBeGreaterThanOrEqual(1)
|
|
947
|
+
})
|
|
948
|
+
})
|
|
949
|
+
|
|
950
|
+
describe('viem compatibility', () => {
|
|
951
|
+
test('behavior: works with viem custom() transport', async () => {
|
|
952
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
953
|
+
const address = await connect(provider)
|
|
954
|
+
await fund(address)
|
|
955
|
+
|
|
956
|
+
const client = provider.getClient()
|
|
957
|
+
|
|
958
|
+
// Read action: getBalance
|
|
959
|
+
const balance = await getBalance(client, { address })
|
|
960
|
+
expect(balance).toBeGreaterThanOrEqual(0n)
|
|
961
|
+
})
|
|
962
|
+
|
|
963
|
+
test('behavior: WalletClient can sign messages', async () => {
|
|
964
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
965
|
+
const address = await connect(provider)
|
|
966
|
+
|
|
967
|
+
const client = createClient({
|
|
968
|
+
account: address,
|
|
969
|
+
chain,
|
|
970
|
+
transport: custom(provider),
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
const signature = await signMessage(client, {
|
|
974
|
+
account: address,
|
|
975
|
+
message: 'hello',
|
|
976
|
+
})
|
|
977
|
+
expect(signature).toMatch(/^0x[0-9a-f]+$/)
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
test('behavior: WalletClient can send transactions', async () => {
|
|
981
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
982
|
+
const address = await connect(provider)
|
|
983
|
+
await fund(address)
|
|
984
|
+
|
|
985
|
+
const client = createClient({
|
|
986
|
+
account: address,
|
|
987
|
+
chain,
|
|
988
|
+
transport: custom(provider),
|
|
989
|
+
})
|
|
990
|
+
|
|
991
|
+
const receipt = await sendTransactionSync(client, {
|
|
992
|
+
account: address,
|
|
993
|
+
to: '0x0000000000000000000000000000000000000001',
|
|
994
|
+
value: 0n,
|
|
995
|
+
})
|
|
996
|
+
expect(receipt.status).toBe('success')
|
|
997
|
+
})
|
|
998
|
+
})
|
|
999
|
+
|
|
1000
|
+
describe('wallet_authorizeAccessKey', () => {
|
|
1001
|
+
test('default: grants an access key and returns its address', async () => {
|
|
1002
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1003
|
+
await connect(provider)
|
|
1004
|
+
|
|
1005
|
+
const result = await provider.request({
|
|
1006
|
+
method: 'wallet_authorizeAccessKey',
|
|
1007
|
+
params: [{ expiry: Expiry.days(1) }],
|
|
1008
|
+
})
|
|
1009
|
+
expect(result.keyId).toMatch(/^0x[0-9a-fA-F]{40}$/)
|
|
1010
|
+
})
|
|
1011
|
+
|
|
1012
|
+
test('behavior: granted access key is used for sendTransactionSync', async () => {
|
|
1013
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1014
|
+
const address = await connect(provider)
|
|
1015
|
+
await fund(address)
|
|
1016
|
+
|
|
1017
|
+
await provider.request({
|
|
1018
|
+
method: 'wallet_authorizeAccessKey',
|
|
1019
|
+
params: [{ expiry: Expiry.days(1) }],
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
const receipt = await provider.request({
|
|
1023
|
+
method: 'eth_sendTransactionSync',
|
|
1024
|
+
params: [{ calls: [transferCall] }],
|
|
1025
|
+
})
|
|
1026
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
test('behavior: with expiry option', async () => {
|
|
1030
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1031
|
+
await connect(provider)
|
|
1032
|
+
|
|
1033
|
+
const expiry = Math.floor(Date.now() / 1000) + 3600
|
|
1034
|
+
const result = await provider.request({
|
|
1035
|
+
method: 'wallet_authorizeAccessKey',
|
|
1036
|
+
params: [{ expiry }],
|
|
1037
|
+
})
|
|
1038
|
+
expect(result.keyId).toMatch(/^0x[0-9a-fA-F]{40}$/)
|
|
1039
|
+
expect(result.expiry).toBe(Hex.fromNumber(expiry))
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
test('behavior: expired access key falls back to root account', async () => {
|
|
1043
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1044
|
+
const address = await connect(provider)
|
|
1045
|
+
await fund(address)
|
|
1046
|
+
|
|
1047
|
+
await provider.request({
|
|
1048
|
+
method: 'wallet_authorizeAccessKey',
|
|
1049
|
+
params: [{ expiry: Expiry.days(1) }],
|
|
1050
|
+
})
|
|
1051
|
+
expect(provider.store.getState().accessKeys.length).toBe(1)
|
|
1052
|
+
|
|
1053
|
+
// Expire the access key by mutating the store.
|
|
1054
|
+
const { accessKeys } = provider.store.getState()
|
|
1055
|
+
provider.store.setState({
|
|
1056
|
+
accessKeys: accessKeys.map((k) => ({
|
|
1057
|
+
...k,
|
|
1058
|
+
expiry: Math.floor(Date.now() / 1000) - 1,
|
|
1059
|
+
})),
|
|
1060
|
+
})
|
|
1061
|
+
|
|
1062
|
+
// Transaction should still succeed via root account fallback.
|
|
1063
|
+
const receipt = await provider.request({
|
|
1064
|
+
method: 'eth_sendTransactionSync',
|
|
1065
|
+
params: [{ calls: [transferCall] }],
|
|
1066
|
+
})
|
|
1067
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1068
|
+
// Verify transaction was sent by the root account, not the access key.
|
|
1069
|
+
expect(receipt.from.toLowerCase()).toBe(address.toLowerCase())
|
|
1070
|
+
|
|
1071
|
+
// Expired access key should be removed from the store.
|
|
1072
|
+
expect(provider.store.getState().accessKeys).toMatchInlineSnapshot(`[]`)
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
test('behavior: with limits option', async () => {
|
|
1076
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1077
|
+
const address = await connect(provider)
|
|
1078
|
+
await fund(address)
|
|
1079
|
+
|
|
1080
|
+
const result = await provider.request({
|
|
1081
|
+
method: 'wallet_authorizeAccessKey',
|
|
1082
|
+
params: [
|
|
1083
|
+
{
|
|
1084
|
+
expiry: Expiry.days(1),
|
|
1085
|
+
limits: [{ token: Addresses.pathUsd, limit: Hex.fromNumber(parseUnits('5', 6)) }],
|
|
1086
|
+
},
|
|
1087
|
+
],
|
|
1088
|
+
})
|
|
1089
|
+
expect(result.limits).toMatchInlineSnapshot(`
|
|
1090
|
+
[
|
|
1091
|
+
{
|
|
1092
|
+
"limit": "0x4c4b40",
|
|
1093
|
+
"token": "0x20c0000000000000000000000000000000000000",
|
|
1094
|
+
},
|
|
1095
|
+
]
|
|
1096
|
+
`)
|
|
1097
|
+
|
|
1098
|
+
// Transaction within limit should succeed.
|
|
1099
|
+
const receipt = await provider.request({
|
|
1100
|
+
method: 'eth_sendTransactionSync',
|
|
1101
|
+
params: [{ calls: [transferCall] }],
|
|
1102
|
+
})
|
|
1103
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
test('exceeding access key limits falls back to root account', async () => {
|
|
1107
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1108
|
+
const address = await connect(provider)
|
|
1109
|
+
await fund(address)
|
|
1110
|
+
|
|
1111
|
+
// Grant access key with a tiny limit (0.01 PUSD).
|
|
1112
|
+
await provider.request({
|
|
1113
|
+
method: 'wallet_authorizeAccessKey',
|
|
1114
|
+
params: [
|
|
1115
|
+
{
|
|
1116
|
+
expiry: Expiry.days(1),
|
|
1117
|
+
limits: [{ token: Addresses.pathUsd, limit: Hex.fromNumber(parseUnits('0.01', 6)) }],
|
|
1118
|
+
},
|
|
1119
|
+
],
|
|
1120
|
+
})
|
|
1121
|
+
|
|
1122
|
+
// Transfer 1 PUSD — exceeds access key limit, falls back to root account.
|
|
1123
|
+
const receipt = await provider.request({
|
|
1124
|
+
method: 'eth_sendTransactionSync',
|
|
1125
|
+
params: [{ calls: [transferCall] }],
|
|
1126
|
+
})
|
|
1127
|
+
expect(receipt.status).toBe('0x1')
|
|
1128
|
+
expect(receipt.from.toLowerCase()).toBe(address.toLowerCase())
|
|
1129
|
+
})
|
|
1130
|
+
|
|
1131
|
+
test('behavior: access key is removed after key-auth error (spending limit exceeded)', async () => {
|
|
1132
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1133
|
+
const address = await connect(provider)
|
|
1134
|
+
await fund(address)
|
|
1135
|
+
|
|
1136
|
+
// Grant access key with a tiny limit.
|
|
1137
|
+
await provider.request({
|
|
1138
|
+
method: 'wallet_authorizeAccessKey',
|
|
1139
|
+
params: [
|
|
1140
|
+
{
|
|
1141
|
+
expiry: Expiry.days(1),
|
|
1142
|
+
limits: [{ token: Addresses.pathUsd, limit: Hex.fromNumber(parseUnits('0.01', 6)) }],
|
|
1143
|
+
},
|
|
1144
|
+
],
|
|
1145
|
+
})
|
|
1146
|
+
|
|
1147
|
+
expect(provider.store.getState().accessKeys).toHaveLength(1)
|
|
1148
|
+
|
|
1149
|
+
// Transfer exceeds limit — key-auth error — access key should be removed.
|
|
1150
|
+
await provider.request({
|
|
1151
|
+
method: 'eth_sendTransactionSync',
|
|
1152
|
+
params: [{ calls: [transferCall] }],
|
|
1153
|
+
})
|
|
1154
|
+
|
|
1155
|
+
expect(provider.store.getState().accessKeys).toMatchInlineSnapshot(`[]`)
|
|
1156
|
+
})
|
|
1157
|
+
})
|
|
1158
|
+
|
|
1159
|
+
describe('wallet_revokeAccessKey', () => {
|
|
1160
|
+
test('default: revokes a granted access key', async () => {
|
|
1161
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1162
|
+
await connect(provider)
|
|
1163
|
+
|
|
1164
|
+
const connected = (await provider.request({ method: 'eth_accounts' }))[0]!
|
|
1165
|
+
const { keyId } = await provider.request({
|
|
1166
|
+
method: 'wallet_authorizeAccessKey',
|
|
1167
|
+
params: [{ expiry: Expiry.days(1) }],
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
await provider.request({
|
|
1171
|
+
method: 'wallet_revokeAccessKey',
|
|
1172
|
+
params: [{ address: connected, accessKeyAddress: keyId }],
|
|
1173
|
+
})
|
|
1174
|
+
|
|
1175
|
+
// After revoking, sendTransactionSync should use root key (still works)
|
|
1176
|
+
const address = (await provider.request({ method: 'eth_accounts' }))[0]!
|
|
1177
|
+
await fund(address)
|
|
1178
|
+
|
|
1179
|
+
const receipt = await provider.request({
|
|
1180
|
+
method: 'eth_sendTransactionSync',
|
|
1181
|
+
params: [{ calls: [transferCall] }],
|
|
1182
|
+
})
|
|
1183
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1184
|
+
})
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
describe('wallet_connect with authorizeAccessKey', () => {
|
|
1188
|
+
test('default: grants access key during register', async () => {
|
|
1189
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1190
|
+
|
|
1191
|
+
const result = await provider.request({
|
|
1192
|
+
method: 'wallet_connect',
|
|
1193
|
+
params: [
|
|
1194
|
+
{
|
|
1195
|
+
capabilities: {
|
|
1196
|
+
method: 'register',
|
|
1197
|
+
authorizeAccessKey: { expiry: Math.floor(Date.now() / 1000) + 3600 },
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
],
|
|
1201
|
+
})
|
|
1202
|
+
expect(result.accounts.length).toBeGreaterThanOrEqual(1)
|
|
1203
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization).toBeDefined()
|
|
1204
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization!.keyId).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
1205
|
+
|
|
1206
|
+
// Access key should be provisioned — sendTransactionSync should work
|
|
1207
|
+
const address = result.accounts[0]!.address
|
|
1208
|
+
await fund(address)
|
|
1209
|
+
|
|
1210
|
+
const receipt = await provider.request({
|
|
1211
|
+
method: 'eth_sendTransactionSync',
|
|
1212
|
+
params: [{ calls: [transferCall] }],
|
|
1213
|
+
})
|
|
1214
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1215
|
+
})
|
|
1216
|
+
|
|
1217
|
+
test('behavior: authorizeAccessKey with expiry during register', async () => {
|
|
1218
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1219
|
+
|
|
1220
|
+
const expiry = Math.floor(Date.now() / 1000) + 3600
|
|
1221
|
+
const result = await provider.request({
|
|
1222
|
+
method: 'wallet_connect',
|
|
1223
|
+
params: [{ capabilities: { method: 'register', authorizeAccessKey: { expiry } } }],
|
|
1224
|
+
})
|
|
1225
|
+
expect(result.accounts.length).toBeGreaterThanOrEqual(1)
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
test('behavior: authorizeAccessKey during login', async () => {
|
|
1229
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1230
|
+
|
|
1231
|
+
// Register first
|
|
1232
|
+
await provider.request({
|
|
1233
|
+
method: 'wallet_connect',
|
|
1234
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
1235
|
+
})
|
|
1236
|
+
|
|
1237
|
+
// Login with authorizeAccessKey
|
|
1238
|
+
const result = await provider.request({
|
|
1239
|
+
method: 'wallet_connect',
|
|
1240
|
+
params: [
|
|
1241
|
+
{
|
|
1242
|
+
capabilities: { authorizeAccessKey: { expiry: Math.floor(Date.now() / 1000) + 3600 } },
|
|
1243
|
+
},
|
|
1244
|
+
],
|
|
1245
|
+
})
|
|
1246
|
+
expect(result.accounts.length).toBeGreaterThanOrEqual(1)
|
|
1247
|
+
|
|
1248
|
+
const address = result.accounts[0]!.address
|
|
1249
|
+
await fund(address)
|
|
1250
|
+
|
|
1251
|
+
const receipt = await provider.request({
|
|
1252
|
+
method: 'eth_sendTransactionSync',
|
|
1253
|
+
params: [{ calls: [transferCall] }],
|
|
1254
|
+
})
|
|
1255
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1256
|
+
})
|
|
1257
|
+
})
|
|
1258
|
+
|
|
1259
|
+
describe('Provider.create authorizeAccessKey option', () => {
|
|
1260
|
+
test('default: wallet_connect auto-authorizes access key', async () => {
|
|
1261
|
+
const provider = Provider.create({
|
|
1262
|
+
adapter: adapter(),
|
|
1263
|
+
chains: [chain],
|
|
1264
|
+
authorizeAccessKey: () => ({ expiry: Expiry.days(1) }),
|
|
1265
|
+
})
|
|
1266
|
+
|
|
1267
|
+
const result = await provider.request({
|
|
1268
|
+
method: 'wallet_connect',
|
|
1269
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
1270
|
+
})
|
|
1271
|
+
expect(result.accounts.length).toBeGreaterThanOrEqual(1)
|
|
1272
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization).toBeDefined()
|
|
1273
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization!.keyId).toMatch(/^0x[0-9a-f]{40}$/i)
|
|
1274
|
+
})
|
|
1275
|
+
|
|
1276
|
+
test('behavior: auto-authorized access key can send transactions', async () => {
|
|
1277
|
+
const provider = Provider.create({
|
|
1278
|
+
adapter: adapter(),
|
|
1279
|
+
chains: [chain],
|
|
1280
|
+
authorizeAccessKey: () => ({ expiry: Expiry.days(1) }),
|
|
1281
|
+
})
|
|
1282
|
+
|
|
1283
|
+
const result = await provider.request({
|
|
1284
|
+
method: 'wallet_connect',
|
|
1285
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
1286
|
+
})
|
|
1287
|
+
const address = result.accounts[0]!.address
|
|
1288
|
+
await fund(address)
|
|
1289
|
+
|
|
1290
|
+
const receipt = await provider.request({
|
|
1291
|
+
method: 'eth_sendTransactionSync',
|
|
1292
|
+
params: [{ calls: [transferCall] }],
|
|
1293
|
+
})
|
|
1294
|
+
expect(receipt.status).toMatchInlineSnapshot(`"0x1"`)
|
|
1295
|
+
})
|
|
1296
|
+
|
|
1297
|
+
test('behavior: explicit authorizeAccessKey overrides default', async () => {
|
|
1298
|
+
const expiry = Math.floor(Date.now() / 1000) + 3600
|
|
1299
|
+
const provider = Provider.create({
|
|
1300
|
+
adapter: adapter(),
|
|
1301
|
+
chains: [chain],
|
|
1302
|
+
authorizeAccessKey: () => ({ expiry: Expiry.days(7) }),
|
|
1303
|
+
})
|
|
1304
|
+
|
|
1305
|
+
const result = await provider.request({
|
|
1306
|
+
method: 'wallet_connect',
|
|
1307
|
+
params: [{ capabilities: { method: 'register', authorizeAccessKey: { expiry } } }],
|
|
1308
|
+
})
|
|
1309
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization!.expiry).toBe(Hex.fromNumber(expiry))
|
|
1310
|
+
})
|
|
1311
|
+
|
|
1312
|
+
test('behavior: login auto-authorizes access key', async () => {
|
|
1313
|
+
const provider = Provider.create({
|
|
1314
|
+
adapter: adapter(),
|
|
1315
|
+
chains: [chain],
|
|
1316
|
+
authorizeAccessKey: () => ({ expiry: Expiry.days(1) }),
|
|
1317
|
+
})
|
|
1318
|
+
|
|
1319
|
+
await connect(provider)
|
|
1320
|
+
const result = await provider.request({ method: 'wallet_connect' })
|
|
1321
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization).toBeDefined()
|
|
1322
|
+
})
|
|
1323
|
+
|
|
1324
|
+
test('behavior: without option, wallet_connect does not auto-authorize', async () => {
|
|
1325
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1326
|
+
|
|
1327
|
+
const result = await provider.request({
|
|
1328
|
+
method: 'wallet_connect',
|
|
1329
|
+
params: [{ capabilities: { method: 'register' } }],
|
|
1330
|
+
})
|
|
1331
|
+
expect(result.accounts[0]!.capabilities.keyAuthorization).toBeUndefined()
|
|
1332
|
+
})
|
|
1333
|
+
})
|
|
1334
|
+
|
|
1335
|
+
describe('wallet_authorizeAccessKey with external key', () => {
|
|
1336
|
+
test('default: grants access key for an external key', async () => {
|
|
1337
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1338
|
+
await connect(provider)
|
|
1339
|
+
|
|
1340
|
+
const keyPair = await WebCryptoP256.createKeyPair()
|
|
1341
|
+
const accessKeyAccount = TempoAccount.fromWebCryptoP256(keyPair)
|
|
1342
|
+
|
|
1343
|
+
const result = await provider.request({
|
|
1344
|
+
method: 'wallet_authorizeAccessKey',
|
|
1345
|
+
params: [{ ...accessKeyAccount, expiry: Expiry.days(1) }],
|
|
1346
|
+
})
|
|
1347
|
+
expect(result.keyId).toBe(accessKeyAccount.address)
|
|
1348
|
+
expect(result.keyType).toBe('p256')
|
|
1349
|
+
})
|
|
1350
|
+
|
|
1351
|
+
test('behavior: external key authorization can be used to send a transaction', async () => {
|
|
1352
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1353
|
+
const rootAddress = await connect(provider)
|
|
1354
|
+
await fund(rootAddress)
|
|
1355
|
+
|
|
1356
|
+
const keyPair = await WebCryptoP256.createKeyPair()
|
|
1357
|
+
const accessKeyAccount = TempoAccount.fromWebCryptoP256(keyPair)
|
|
1358
|
+
|
|
1359
|
+
const result = await provider.request({
|
|
1360
|
+
method: 'wallet_authorizeAccessKey',
|
|
1361
|
+
params: [{ ...accessKeyAccount, expiry: Expiry.days(1) }],
|
|
1362
|
+
})
|
|
1363
|
+
|
|
1364
|
+
const client = provider.getClient()
|
|
1365
|
+
const receipt = await sendTransactionSync(client, {
|
|
1366
|
+
account: TempoAccount.fromWebCryptoP256(keyPair, { access: rootAddress }),
|
|
1367
|
+
calls: [transferCall],
|
|
1368
|
+
keyAuthorization: KeyAuthorization.fromRpc(result),
|
|
1369
|
+
})
|
|
1370
|
+
expect(receipt.status).toBe('success')
|
|
1371
|
+
})
|
|
1372
|
+
})
|
|
1373
|
+
|
|
1374
|
+
describe('wallet_connect with external authorizeAccessKey', () => {
|
|
1375
|
+
test('default: external key authorization via register', async () => {
|
|
1376
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1377
|
+
|
|
1378
|
+
const keyPair = await WebCryptoP256.createKeyPair()
|
|
1379
|
+
const accessKeyAccount = TempoAccount.fromWebCryptoP256(keyPair)
|
|
1380
|
+
|
|
1381
|
+
const expiry = Math.floor(Date.now() / 1000) + 3600
|
|
1382
|
+
const connectResult = await provider.request({
|
|
1383
|
+
method: 'wallet_connect',
|
|
1384
|
+
params: [
|
|
1385
|
+
{
|
|
1386
|
+
capabilities: {
|
|
1387
|
+
method: 'register',
|
|
1388
|
+
authorizeAccessKey: { expiry, ...accessKeyAccount },
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1392
|
+
})
|
|
1393
|
+
|
|
1394
|
+
const rootAddress = connectResult.accounts[0]!.address
|
|
1395
|
+
const keyAuthorization = connectResult.accounts[0]!.capabilities.keyAuthorization
|
|
1396
|
+
expect(keyAuthorization).toBeDefined()
|
|
1397
|
+
expect(keyAuthorization!.keyId).toBe(accessKeyAccount.address)
|
|
1398
|
+
|
|
1399
|
+
await fund(rootAddress)
|
|
1400
|
+
|
|
1401
|
+
const client = provider.getClient()
|
|
1402
|
+
const receipt = await sendTransactionSync(client, {
|
|
1403
|
+
account: TempoAccount.fromWebCryptoP256(keyPair, { access: rootAddress }),
|
|
1404
|
+
calls: [transferCall],
|
|
1405
|
+
keyAuthorization: KeyAuthorization.fromRpc(keyAuthorization!),
|
|
1406
|
+
})
|
|
1407
|
+
expect(receipt.status).toBe('success')
|
|
1408
|
+
})
|
|
1409
|
+
|
|
1410
|
+
test('behavior: external key authorization via login', async () => {
|
|
1411
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1412
|
+
|
|
1413
|
+
const rootAddress = await connect(provider)
|
|
1414
|
+
await fund(rootAddress)
|
|
1415
|
+
|
|
1416
|
+
const keyPair = await WebCryptoP256.createKeyPair()
|
|
1417
|
+
const accessKeyAccount = TempoAccount.fromWebCryptoP256(keyPair)
|
|
1418
|
+
|
|
1419
|
+
const expiry = Math.floor(Date.now() / 1000) + 3600
|
|
1420
|
+
const loginResult = await provider.request({
|
|
1421
|
+
method: 'wallet_connect',
|
|
1422
|
+
params: [
|
|
1423
|
+
{
|
|
1424
|
+
capabilities: {
|
|
1425
|
+
authorizeAccessKey: { expiry, ...accessKeyAccount },
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
],
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
const keyAuthorization = loginResult.accounts[0]!.capabilities.keyAuthorization
|
|
1432
|
+
expect(keyAuthorization).toBeDefined()
|
|
1433
|
+
|
|
1434
|
+
const client = provider.getClient()
|
|
1435
|
+
const receipt = await sendTransactionSync(client, {
|
|
1436
|
+
account: TempoAccount.fromWebCryptoP256(keyPair, { access: rootAddress }),
|
|
1437
|
+
calls: [transferCall],
|
|
1438
|
+
keyAuthorization: KeyAuthorization.fromRpc(keyAuthorization!),
|
|
1439
|
+
})
|
|
1440
|
+
expect(receipt.status).toBe('success')
|
|
1441
|
+
})
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1444
|
+
describe('feePayer', () => {
|
|
1445
|
+
const feePayerAccount = accounts[0]!
|
|
1446
|
+
let server: Server
|
|
1447
|
+
|
|
1448
|
+
beforeAll(async () => {
|
|
1449
|
+
server = await createServer(
|
|
1450
|
+
Handler.feePayer({
|
|
1451
|
+
account: feePayerAccount,
|
|
1452
|
+
chains: [chain],
|
|
1453
|
+
transports: { [chain.id]: http() },
|
|
1454
|
+
}).listener,
|
|
1455
|
+
)
|
|
1456
|
+
})
|
|
1457
|
+
|
|
1458
|
+
afterAll(() => {
|
|
1459
|
+
server.close()
|
|
1460
|
+
})
|
|
1461
|
+
|
|
1462
|
+
test('default: feePayer URL on eth_sendTransaction', async () => {
|
|
1463
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1464
|
+
|
|
1465
|
+
const connected = await connect(provider)
|
|
1466
|
+
await fund(connected)
|
|
1467
|
+
|
|
1468
|
+
const hash = await provider.request({
|
|
1469
|
+
method: 'eth_sendTransaction',
|
|
1470
|
+
params: [{ calls: [transferCall], feePayer: server.url }],
|
|
1471
|
+
})
|
|
1472
|
+
|
|
1473
|
+
expect(hash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
1474
|
+
|
|
1475
|
+
const client = provider.getClient()
|
|
1476
|
+
const receipt = await waitForTransactionReceipt(client, { hash })
|
|
1477
|
+
expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
|
|
1478
|
+
})
|
|
1479
|
+
|
|
1480
|
+
test('behavior: feePayer URL on eth_sendTransactionSync', async () => {
|
|
1481
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1482
|
+
|
|
1483
|
+
const connected = await connect(provider)
|
|
1484
|
+
await fund(connected)
|
|
1485
|
+
|
|
1486
|
+
const receipt = await provider.request({
|
|
1487
|
+
method: 'eth_sendTransactionSync',
|
|
1488
|
+
params: [{ calls: [transferCall], feePayer: server.url }],
|
|
1489
|
+
})
|
|
1490
|
+
|
|
1491
|
+
expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
|
|
1492
|
+
})
|
|
1493
|
+
|
|
1494
|
+
test('behavior: feePayer URL on eth_signTransaction', async () => {
|
|
1495
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1496
|
+
|
|
1497
|
+
const connected = await connect(provider)
|
|
1498
|
+
await fund(connected)
|
|
1499
|
+
|
|
1500
|
+
const signed = await provider.request({
|
|
1501
|
+
method: 'eth_signTransaction',
|
|
1502
|
+
params: [{ calls: [transferCall], feePayer: server.url }],
|
|
1503
|
+
})
|
|
1504
|
+
|
|
1505
|
+
expect(signed).toMatch(/^0x/)
|
|
1506
|
+
})
|
|
1507
|
+
|
|
1508
|
+
test('behavior: feePayer: true uses default from Provider.create', async () => {
|
|
1509
|
+
const provider = Provider.create({
|
|
1510
|
+
adapter: adapter(),
|
|
1511
|
+
chains: [chain],
|
|
1512
|
+
feePayerUrl: server.url,
|
|
1513
|
+
})
|
|
1514
|
+
|
|
1515
|
+
const connected = await connect(provider)
|
|
1516
|
+
await fund(connected)
|
|
1517
|
+
|
|
1518
|
+
const hash = await provider.request({
|
|
1519
|
+
method: 'eth_sendTransaction',
|
|
1520
|
+
params: [{ calls: [transferCall], feePayer: true }],
|
|
1521
|
+
})
|
|
1522
|
+
|
|
1523
|
+
expect(hash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
1524
|
+
|
|
1525
|
+
const client = provider.getClient()
|
|
1526
|
+
const receipt = await waitForTransactionReceipt(client, { hash })
|
|
1527
|
+
expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
|
|
1528
|
+
})
|
|
1529
|
+
|
|
1530
|
+
test('behavior: feePayer: true on eth_sendTransactionSync', async () => {
|
|
1531
|
+
const provider = Provider.create({
|
|
1532
|
+
adapter: adapter(),
|
|
1533
|
+
chains: [chain],
|
|
1534
|
+
feePayerUrl: server.url,
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
const connected = await connect(provider)
|
|
1538
|
+
await fund(connected)
|
|
1539
|
+
|
|
1540
|
+
const receipt = await provider.request({
|
|
1541
|
+
method: 'eth_sendTransactionSync',
|
|
1542
|
+
params: [{ calls: [transferCall], feePayer: true }],
|
|
1543
|
+
})
|
|
1544
|
+
|
|
1545
|
+
expect(receipt.feePayer).toBe(feePayerAccount.address.toLowerCase())
|
|
1546
|
+
})
|
|
1547
|
+
|
|
1548
|
+
test('behavior: no feePayer does not use fee payer', async () => {
|
|
1549
|
+
const provider = Provider.create({ adapter: adapter(), chains: [chain] })
|
|
1550
|
+
|
|
1551
|
+
const connected = await connect(provider)
|
|
1552
|
+
await fund(connected)
|
|
1553
|
+
|
|
1554
|
+
const hash = await provider.request({
|
|
1555
|
+
method: 'eth_sendTransaction',
|
|
1556
|
+
params: [{ calls: [transferCall] }],
|
|
1557
|
+
})
|
|
1558
|
+
|
|
1559
|
+
expect(hash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
1560
|
+
|
|
1561
|
+
const client = provider.getClient()
|
|
1562
|
+
const receipt = await waitForTransactionReceipt(client, { hash })
|
|
1563
|
+
expect(receipt.feePayer).not.toBe(feePayerAccount.address.toLowerCase())
|
|
1564
|
+
})
|
|
1565
|
+
})
|
|
1566
|
+
})
|