tempo.ts 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +134 -0
- package/README.md +6 -2
- package/dist/ox/Transaction.js +1 -1
- package/dist/ox/Transaction.js.map +1 -1
- package/dist/server/Handler.d.ts +346 -0
- package/dist/server/Handler.d.ts.map +1 -0
- package/dist/server/Handler.js +441 -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 +25 -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 +174 -0
- package/dist/server/internal/requestListener.js.map +1 -0
- package/dist/viem/Actions/account.d.ts +40 -0
- package/dist/viem/Actions/account.d.ts.map +1 -0
- package/dist/viem/Actions/account.js +87 -0
- package/dist/viem/Actions/account.js.map +1 -0
- package/dist/viem/Actions/amm.d.ts +51 -1245
- package/dist/viem/Actions/amm.d.ts.map +1 -1
- package/dist/viem/Actions/amm.js +15 -478
- package/dist/viem/Actions/amm.js.map +1 -1
- package/dist/viem/Actions/index.d.ts +1 -0
- package/dist/viem/Actions/index.d.ts.map +1 -1
- package/dist/viem/Actions/index.js +1 -0
- package/dist/viem/Actions/index.js.map +1 -1
- package/dist/viem/Actions/reward.d.ts +0 -1067
- package/dist/viem/Actions/reward.d.ts.map +1 -1
- package/dist/viem/Actions/reward.js +4 -212
- package/dist/viem/Actions/reward.js.map +1 -1
- package/dist/viem/Decorator.d.ts +28 -263
- package/dist/viem/Decorator.d.ts.map +1 -1
- package/dist/viem/Decorator.js +2 -10
- package/dist/viem/Decorator.js.map +1 -1
- package/dist/viem/Storage.d.ts +23 -0
- package/dist/viem/Storage.d.ts.map +1 -0
- package/dist/viem/Storage.js +47 -0
- package/dist/viem/Storage.js.map +1 -0
- package/dist/viem/Transport.d.ts +10 -1
- package/dist/viem/Transport.d.ts.map +1 -1
- package/dist/viem/Transport.js +22 -3
- package/dist/viem/Transport.js.map +1 -1
- package/dist/viem/internal/utils.d.ts +6 -0
- package/dist/viem/internal/utils.d.ts.map +1 -1
- package/dist/viem/internal/utils.js +24 -0
- package/dist/viem/internal/utils.js.map +1 -1
- package/dist/wagmi/Actions/amm.d.ts +0 -225
- package/dist/wagmi/Actions/amm.d.ts.map +1 -1
- package/dist/wagmi/Actions/amm.js +0 -248
- package/dist/wagmi/Actions/amm.js.map +1 -1
- package/dist/wagmi/Actions/reward.d.ts +0 -110
- package/dist/wagmi/Actions/reward.d.ts.map +1 -1
- package/dist/wagmi/Actions/reward.js +0 -121
- package/dist/wagmi/Actions/reward.js.map +1 -1
- package/dist/wagmi/Connector.d.ts +6 -17
- package/dist/wagmi/Connector.d.ts.map +1 -1
- package/dist/wagmi/Connector.js +17 -43
- package/dist/wagmi/Connector.js.map +1 -1
- package/dist/wagmi/Hooks/amm.d.ts +0 -236
- package/dist/wagmi/Hooks/amm.d.ts.map +1 -1
- package/dist/wagmi/Hooks/amm.js +0 -285
- package/dist/wagmi/Hooks/amm.js.map +1 -1
- package/dist/wagmi/Hooks/reward.d.ts +0 -88
- package/dist/wagmi/Hooks/reward.d.ts.map +1 -1
- package/dist/wagmi/Hooks/reward.js +0 -103
- package/dist/wagmi/Hooks/reward.js.map +1 -1
- package/dist/wagmi/KeyManager.d.ts +57 -0
- package/dist/wagmi/KeyManager.d.ts.map +1 -0
- package/dist/wagmi/KeyManager.js +101 -0
- package/dist/wagmi/KeyManager.js.map +1 -0
- package/dist/wagmi/index.d.ts +1 -0
- package/dist/wagmi/index.d.ts.map +1 -1
- package/dist/wagmi/index.js +1 -0
- package/dist/wagmi/index.js.map +1 -1
- package/package.json +8 -2
- package/src/ox/Transaction.ts +1 -1
- package/src/ox/e2e.test.ts +7 -0
- package/src/server/Handler.test.ts +566 -0
- package/src/server/Handler.ts +577 -0
- package/src/server/Kv.ts +40 -0
- package/src/server/index.ts +2 -0
- package/src/server/internal/requestListener.ts +285 -0
- package/src/viem/Actions/account.test.ts +414 -0
- package/src/viem/Actions/account.ts +108 -0
- package/src/viem/Actions/amm.test.ts +10 -284
- package/src/viem/Actions/amm.ts +88 -768
- package/src/viem/Actions/index.ts +1 -0
- package/src/viem/Actions/reward.test.ts +4 -212
- package/src/viem/Actions/reward.ts +4 -291
- package/src/viem/Decorator.test.ts +1 -0
- package/src/viem/Decorator.ts +32 -294
- package/src/viem/Storage.ts +88 -0
- package/src/viem/Transport.ts +40 -2
- package/src/viem/e2e.test.ts +106 -3
- package/src/viem/internal/utils.ts +21 -0
- package/src/wagmi/Actions/amm.test.ts +7 -85
- package/src/wagmi/Actions/amm.ts +0 -346
- package/src/wagmi/Actions/reward.test.ts +0 -99
- package/src/wagmi/Actions/reward.ts +0 -203
- package/src/wagmi/Connector.test.ts +4 -1
- package/src/wagmi/Connector.ts +24 -58
- package/src/wagmi/Hooks/amm.test.ts +8 -200
- package/src/wagmi/Hooks/amm.ts +0 -443
- package/src/wagmi/Hooks/reward.test.ts +1 -142
- package/src/wagmi/Hooks/reward.ts +0 -196
- package/src/wagmi/KeyManager.ts +159 -0
- package/src/wagmi/index.ts +1 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import { Hono } from 'hono'
|
|
3
|
+
import type { RpcRequest } from 'ox'
|
|
4
|
+
import * as Base64 from 'ox/Base64'
|
|
5
|
+
import * as Hex from 'ox/Hex'
|
|
6
|
+
import { http } from 'viem'
|
|
7
|
+
import { sendTransaction, sendTransactionSync } from 'viem/actions'
|
|
8
|
+
import {
|
|
9
|
+
afterAll,
|
|
10
|
+
afterEach,
|
|
11
|
+
beforeAll,
|
|
12
|
+
beforeEach,
|
|
13
|
+
describe,
|
|
14
|
+
expect,
|
|
15
|
+
test,
|
|
16
|
+
} from 'vitest'
|
|
17
|
+
import { createServer, type Server } from '../../test/server/utils.js'
|
|
18
|
+
import { accounts, getClient, transport } from '../../test/viem/config.js'
|
|
19
|
+
import { withFeePayer } from '../viem/Transport.js'
|
|
20
|
+
import * as Handler from './Handler.js'
|
|
21
|
+
import * as Kv from './Kv.js'
|
|
22
|
+
|
|
23
|
+
describe('from', () => {
|
|
24
|
+
test('default', () => {
|
|
25
|
+
const handler = Handler.from()
|
|
26
|
+
expect(handler).toBeDefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('.fetch', async () => {
|
|
30
|
+
const handler = Handler.from()
|
|
31
|
+
handler.get('/test', () => new Response('test'))
|
|
32
|
+
|
|
33
|
+
const response = await handler.fetch(new Request('http://localhost/test'))
|
|
34
|
+
expect(response.status).toBe(200)
|
|
35
|
+
expect(await response.text()).toBe('test')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('.listener', async () => {
|
|
39
|
+
const handler = Handler.from()
|
|
40
|
+
handler.get('/test', () =>
|
|
41
|
+
Response.json({ message: 'hello from listener' }),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const server = await createServer(handler.listener)
|
|
45
|
+
|
|
46
|
+
// Make a request to the server
|
|
47
|
+
const response = await fetch(`${server.url}/test`)
|
|
48
|
+
expect(response.status).toBe(200)
|
|
49
|
+
|
|
50
|
+
const data = await response.json()
|
|
51
|
+
expect(data).toEqual({ message: 'hello from listener' })
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('integration', () => {
|
|
55
|
+
const handler = Handler.from()
|
|
56
|
+
handler.get('/foo', () => new Response('foo'))
|
|
57
|
+
handler.post('/bar', () => new Response('bar'))
|
|
58
|
+
|
|
59
|
+
test('hono', async () => {
|
|
60
|
+
const app = new Hono()
|
|
61
|
+
app.use((c) => handler.fetch(c.req.raw))
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
const response = await app.request('/foo')
|
|
65
|
+
expect(await response.text()).toBe('foo')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
const response = await app.request('/bar', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
})
|
|
72
|
+
expect(await response.text()).toBe('bar')
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('node.js', async () => {
|
|
77
|
+
const server = await createServer(handler.listener)
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
const response = await fetch(`${server.url}/foo`)
|
|
81
|
+
expect(await response.text()).toBe('foo')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
const response = await fetch(`${server.url}/bar`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
})
|
|
88
|
+
expect(await response.text()).toBe('bar')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await server.closeAsync()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('express', async () => {
|
|
95
|
+
const app = express()
|
|
96
|
+
app.use(handler.listener)
|
|
97
|
+
|
|
98
|
+
const server = await createServer(app)
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
const response = await fetch(`${server.url}/foo`)
|
|
102
|
+
expect(await response.text()).toBe('foo')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
const response = await fetch(`${server.url}/bar`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
})
|
|
109
|
+
expect(await response.text()).toBe('bar')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await server.closeAsync()
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('keyManager', () => {
|
|
118
|
+
let kv: Kv.Kv
|
|
119
|
+
let handler: Handler.Handler
|
|
120
|
+
|
|
121
|
+
const credential = {
|
|
122
|
+
authenticatorAttachment: 'platform',
|
|
123
|
+
clientExtensionResults: { credProps: { rk: true } },
|
|
124
|
+
id: '8S-IAM1gQ2KsH3KNM517PRMdcFQ',
|
|
125
|
+
rawId: '8S-IAM1gQ2KsH3KNM517PRMdcFQ',
|
|
126
|
+
response: {
|
|
127
|
+
attestationObject:
|
|
128
|
+
'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MAcVTk7MjAtuAgVX170AFPEviADNYENirB9yjTOdez0THXBUpQECAyYgASFYIKjtG4n36vPprMvoOCi1rQC6h5EIBVxHoEW0xq1lQQZuIlgg_V4PIauVB6JcokNxrPCa2ueWylzbd8nqma5nLvg5Gs8',
|
|
129
|
+
authenticatorData:
|
|
130
|
+
'SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MAcVTk7MjAtuAgVX170AFPEviADNYENirB9yjTOdez0THXBUpQECAyYgASFYIKjtG4n36vPprMvoOCi1rQC6h5EIBVxHoEW0xq1lQQZuIlgg_V4PIauVB6JcokNxrPCa2ueWylzbd8nqma5nLvg5Gs8',
|
|
131
|
+
clientDataJSON:
|
|
132
|
+
'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiOEhURkFrS1E2U2hTbW5MelRra3ZMYkhrXzByU3FteEtRbG9nTy1KdmtvbyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTE3MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0',
|
|
133
|
+
publicKey:
|
|
134
|
+
'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqO0biffq8-msy-g4KLWtALqHkQgFXEegRbTGrWVBBm79Xg8hq5UHolyiQ3Gs8Jra55bKXNt3yeqZrmcu-Dkazw',
|
|
135
|
+
publicKeyAlgorithm: -7,
|
|
136
|
+
transports: ['hybrid', 'internal'],
|
|
137
|
+
},
|
|
138
|
+
type: 'public-key',
|
|
139
|
+
}
|
|
140
|
+
const publicKey =
|
|
141
|
+
'0xa8ed1b89f7eaf3e9accbe83828b5ad00ba879108055c47a045b4c6ad6541066efd5e0f21ab9507a25ca24371acf09adae796ca5cdb77c9ea99ae672ef8391acf'
|
|
142
|
+
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
kv = Kv.memory()
|
|
145
|
+
handler = Handler.keyManager({ kv })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('GET /challenge', () => {
|
|
149
|
+
test('default', async () => {
|
|
150
|
+
const response = await handler.fetch(
|
|
151
|
+
new Request('http://localhost/challenge'),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
expect(response.status).toBe(200)
|
|
155
|
+
|
|
156
|
+
const data =
|
|
157
|
+
(await response.json()) as Handler.keyManager.ChallengeResponse
|
|
158
|
+
expect(Hex.validate(data.challenge)).toBe(true)
|
|
159
|
+
|
|
160
|
+
// Verify challenge was stored in KV
|
|
161
|
+
const stored = await kv.get(`challenge:${data.challenge}`)
|
|
162
|
+
expect(stored).toBe('1')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('behavior: rpId', async () => {
|
|
166
|
+
const handlerWithRpId = Handler.keyManager({
|
|
167
|
+
kv,
|
|
168
|
+
rp: 'example.com',
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const response = await handlerWithRpId.fetch(
|
|
172
|
+
new Request('http://localhost/challenge'),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
expect(response.status).toBe(200)
|
|
176
|
+
|
|
177
|
+
const data =
|
|
178
|
+
(await response.json()) as Handler.keyManager.ChallengeResponse
|
|
179
|
+
expect(data.rp).toEqual({
|
|
180
|
+
id: 'example.com',
|
|
181
|
+
name: 'example.com',
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('GET /:id', () => {
|
|
187
|
+
test('default', async () => {
|
|
188
|
+
const credentialId = 'test-credential-id'
|
|
189
|
+
const publicKey = '0x1234567890abcdef'
|
|
190
|
+
|
|
191
|
+
await kv.set(`credential:${credentialId}`, publicKey)
|
|
192
|
+
|
|
193
|
+
const response = await handler.fetch(
|
|
194
|
+
new Request(`http://localhost/${credentialId}`),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
expect(response.status).toBe(200)
|
|
198
|
+
|
|
199
|
+
const data = await response.json()
|
|
200
|
+
expect(data).toEqual({ publicKey })
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('behavior: returns 404 for non-existent credential', async () => {
|
|
204
|
+
const response = await handler.fetch(
|
|
205
|
+
new Request('http://localhost/non-existent'),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
expect(response.status).toBe(404)
|
|
209
|
+
expect(await response.text()).toBe('Credential not found')
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('POST /:id', () => {
|
|
214
|
+
test('default', async () => {
|
|
215
|
+
const credentialId = credential.id
|
|
216
|
+
const challenge = Base64.toHex(
|
|
217
|
+
JSON.parse(Base64.toString(credential.response.clientDataJSON))
|
|
218
|
+
.challenge,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// Store the challenge first
|
|
222
|
+
await kv.set(`challenge:${challenge}`, '1')
|
|
223
|
+
|
|
224
|
+
const response = await handler.fetch(
|
|
225
|
+
new Request(`http://localhost/${credentialId}`, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: { 'Content-Type': 'application/json' },
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
credential,
|
|
230
|
+
publicKey,
|
|
231
|
+
}),
|
|
232
|
+
}),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
expect(response.status).toBe(204)
|
|
236
|
+
|
|
237
|
+
// Verify public key was stored
|
|
238
|
+
const storedPublicKey = await kv.get(`credential:${credentialId}`)
|
|
239
|
+
expect(storedPublicKey).toBe(publicKey)
|
|
240
|
+
|
|
241
|
+
// Verify challenge was consumed
|
|
242
|
+
const storedChallenge = await kv.get(`challenge:${challenge}`)
|
|
243
|
+
expect(storedChallenge).toBeUndefined()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('behavior: returns 400 when credential is missing', async () => {
|
|
247
|
+
const response = await handler.fetch(
|
|
248
|
+
new Request('http://localhost/test-id', {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
headers: { 'Content-Type': 'application/json' },
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
publicKey,
|
|
253
|
+
}),
|
|
254
|
+
}),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
expect(response.status).toBe(400)
|
|
258
|
+
const data = await response.json()
|
|
259
|
+
expect(data.error).toBe('Missing `credential`')
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test('behavior: returns 400 when publicKey is missing', async () => {
|
|
263
|
+
const response = await handler.fetch(
|
|
264
|
+
new Request('http://localhost/test-id', {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: { 'Content-Type': 'application/json' },
|
|
267
|
+
body: JSON.stringify({
|
|
268
|
+
credential,
|
|
269
|
+
}),
|
|
270
|
+
}),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
expect(response.status).toBe(400)
|
|
274
|
+
const data = await response.json()
|
|
275
|
+
expect(data.error).toBe('Missing `publicKey`')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('behavior: returns 400 for invalid or expired challenge', async () => {
|
|
279
|
+
const response = await handler.fetch(
|
|
280
|
+
new Request(`http://localhost/${credential.id}`, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: { 'Content-Type': 'application/json' },
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
credential,
|
|
285
|
+
publicKey,
|
|
286
|
+
}),
|
|
287
|
+
}),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
expect(response.status).toBe(400)
|
|
291
|
+
const data = await response.json()
|
|
292
|
+
expect(data.error).toBe('Invalid or expired `challenge`')
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('behavior: returns 400 for invalid clientDataJSON type', async () => {
|
|
296
|
+
const invalidCredential = {
|
|
297
|
+
...credential,
|
|
298
|
+
response: {
|
|
299
|
+
...credential.response,
|
|
300
|
+
clientDataJSON: Base64.fromString(
|
|
301
|
+
JSON.stringify({
|
|
302
|
+
type: 'webauthn.get', // Invalid type
|
|
303
|
+
challenge: '8HTFAkKQ6ShSmnLzTkkvLbHk_0rSqmxKQlogO-Jvkoo',
|
|
304
|
+
origin: 'http://localhost:5173',
|
|
305
|
+
crossOrigin: false,
|
|
306
|
+
}),
|
|
307
|
+
),
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const challenge = Base64.toHex(
|
|
312
|
+
JSON.parse(
|
|
313
|
+
Base64.toString(invalidCredential.response.clientDataJSON as string),
|
|
314
|
+
).challenge,
|
|
315
|
+
)
|
|
316
|
+
await kv.set(`challenge:${challenge}`, '1')
|
|
317
|
+
|
|
318
|
+
const response = await handler.fetch(
|
|
319
|
+
new Request(`http://localhost/${credential.id}`, {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: { 'Content-Type': 'application/json' },
|
|
322
|
+
body: JSON.stringify({
|
|
323
|
+
credential: invalidCredential,
|
|
324
|
+
publicKey,
|
|
325
|
+
}),
|
|
326
|
+
}),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
expect(response.status).toBe(400)
|
|
330
|
+
const data = await response.json()
|
|
331
|
+
expect(data.error).toBe('Invalid `clientDataJSON.type`')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test('behavior: returns 400 for invalid origin when rpId is set', async () => {
|
|
335
|
+
const handlerWithRpId = Handler.keyManager({
|
|
336
|
+
kv,
|
|
337
|
+
rp: 'example.com',
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const challenge = Base64.toHex(
|
|
341
|
+
JSON.parse(Base64.toString(credential.response.clientDataJSON))
|
|
342
|
+
.challenge,
|
|
343
|
+
)
|
|
344
|
+
await kv.set(`challenge:${challenge}`, '1')
|
|
345
|
+
|
|
346
|
+
const response = await handlerWithRpId.fetch(
|
|
347
|
+
new Request(`http://localhost/${credential.id}`, {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
headers: { 'Content-Type': 'application/json' },
|
|
350
|
+
body: JSON.stringify({
|
|
351
|
+
credential,
|
|
352
|
+
publicKey,
|
|
353
|
+
}),
|
|
354
|
+
}),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
expect(response.status).toBe(400)
|
|
358
|
+
const data = await response.json()
|
|
359
|
+
expect(data.error).toBe('Invalid `clientDataJSON.origin`')
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
test('behavior: allows localhost origin when rpId includes localhost', async () => {
|
|
363
|
+
const handlerWithLocalhost = Handler.keyManager({
|
|
364
|
+
kv,
|
|
365
|
+
rp: 'localhost',
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const challenge = Base64.toHex(
|
|
369
|
+
JSON.parse(Base64.toString(credential.response.clientDataJSON))
|
|
370
|
+
.challenge,
|
|
371
|
+
)
|
|
372
|
+
await kv.set(`challenge:${challenge}`, '1')
|
|
373
|
+
|
|
374
|
+
const response = await handlerWithLocalhost.fetch(
|
|
375
|
+
new Request(`http://localhost/${credential.id}`, {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
headers: { 'Content-Type': 'application/json' },
|
|
378
|
+
body: JSON.stringify({
|
|
379
|
+
credential,
|
|
380
|
+
publicKey,
|
|
381
|
+
}),
|
|
382
|
+
}),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
expect(response.status).toBe(204)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
test('behavior: returns 400 when user not present flag is not set', async () => {
|
|
389
|
+
// Create credential with UP flag not set (bit 0 = 0)
|
|
390
|
+
const authenticatorDataBytes = Base64.toBytes(
|
|
391
|
+
credential.response.authenticatorData,
|
|
392
|
+
)
|
|
393
|
+
// Clear the UP flag (bit 0) in byte 32
|
|
394
|
+
authenticatorDataBytes[32] = authenticatorDataBytes[32]! & ~0x01
|
|
395
|
+
|
|
396
|
+
const invalidCredential = {
|
|
397
|
+
...credential,
|
|
398
|
+
response: {
|
|
399
|
+
...credential.response,
|
|
400
|
+
authenticatorData: Base64.fromBytes(authenticatorDataBytes),
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const challenge = Base64.toHex(
|
|
405
|
+
JSON.parse(Base64.toString(credential.response.clientDataJSON))
|
|
406
|
+
.challenge,
|
|
407
|
+
)
|
|
408
|
+
await kv.set(`challenge:${challenge}`, '1')
|
|
409
|
+
|
|
410
|
+
const response = await handler.fetch(
|
|
411
|
+
new Request(`http://localhost/${credential.id}`, {
|
|
412
|
+
method: 'POST',
|
|
413
|
+
headers: { 'Content-Type': 'application/json' },
|
|
414
|
+
body: JSON.stringify({
|
|
415
|
+
credential: invalidCredential,
|
|
416
|
+
publicKey,
|
|
417
|
+
}),
|
|
418
|
+
}),
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
expect(response.status).toBe(400)
|
|
422
|
+
const data = await response.json()
|
|
423
|
+
expect(data.error).toBe('User not present')
|
|
424
|
+
})
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
describe('feePayer', () => {
|
|
429
|
+
const userAccount = accounts[9]!
|
|
430
|
+
const feePayerAccount = accounts[0]!
|
|
431
|
+
|
|
432
|
+
let server: Server
|
|
433
|
+
let requests: RpcRequest.RpcRequest[] = []
|
|
434
|
+
|
|
435
|
+
beforeAll(async () => {
|
|
436
|
+
server = await createServer(
|
|
437
|
+
Handler.feePayer({
|
|
438
|
+
account: feePayerAccount,
|
|
439
|
+
client: getClient(),
|
|
440
|
+
onRequest: async (request) => {
|
|
441
|
+
requests.push(request)
|
|
442
|
+
},
|
|
443
|
+
}).listener,
|
|
444
|
+
)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
afterAll(() => {
|
|
448
|
+
server.close()
|
|
449
|
+
process.on('SIGINT', () => {
|
|
450
|
+
server.close()
|
|
451
|
+
process.exit(0)
|
|
452
|
+
})
|
|
453
|
+
process.on('SIGTERM', () => {
|
|
454
|
+
server.close()
|
|
455
|
+
process.exit(0)
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
afterEach(() => {
|
|
460
|
+
requests = []
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
describe('POST /', () => {
|
|
464
|
+
test('behavior: eth_signRawTransaction', async () => {
|
|
465
|
+
const client = getClient({
|
|
466
|
+
account: userAccount,
|
|
467
|
+
transport: withFeePayer(transport, http(server.url)),
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
await sendTransaction(client, {
|
|
471
|
+
feePayer: true,
|
|
472
|
+
to: '0x0000000000000000000000000000000000000000',
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
expect(
|
|
476
|
+
requests.map(({ method, params }) => [method, params]),
|
|
477
|
+
).toMatchInlineSnapshot(`
|
|
478
|
+
[
|
|
479
|
+
[
|
|
480
|
+
"eth_signRawTransaction",
|
|
481
|
+
[
|
|
482
|
+
"0x76f871820539808502cb417800825d82d8d79400000000000000000000000000000000000000008080c0808080808000c0b841b907983eed3ef10ace951150a92d4281dc4879db229ec52da8dc1a85370f41255d414cc614dd25feab2b3c741006f6049c50bceb1f1f73cf2c574a956bd62b031c9ac4fDC8e5D72AaADE30F9Ff52D392D60c68A64afeefeefeefee",
|
|
483
|
+
],
|
|
484
|
+
],
|
|
485
|
+
]
|
|
486
|
+
`)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
test('behavior: eth_sendRawTransaction', async () => {
|
|
490
|
+
const client = getClient({
|
|
491
|
+
account: userAccount,
|
|
492
|
+
transport: withFeePayer(transport, http(server.url), {
|
|
493
|
+
policy: 'sign-and-broadcast',
|
|
494
|
+
}),
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
await sendTransaction(client, {
|
|
498
|
+
feePayer: true,
|
|
499
|
+
to: '0x0000000000000000000000000000000000000000',
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
expect(
|
|
503
|
+
requests.map(({ method, params }) => [method, params]),
|
|
504
|
+
).toMatchInlineSnapshot(`
|
|
505
|
+
[
|
|
506
|
+
[
|
|
507
|
+
"eth_sendRawTransaction",
|
|
508
|
+
[
|
|
509
|
+
"0x76f871820539808502cb417800825d82d8d79400000000000000000000000000000000000000008080c0800180808000c0b841c5a62964b827e6d9b703beec996c496dec13afda19776b9aedce1125d75cf3220815c2d6466a8100d56e8a5e91e6fb15600143effc7cfb2d5f6f48352361cd711c9ac4fDC8e5D72AaADE30F9Ff52D392D60c68A64afeefeefeefee",
|
|
510
|
+
],
|
|
511
|
+
],
|
|
512
|
+
]
|
|
513
|
+
`)
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
test('behavior: eth_sendRawTransactionSync', async () => {
|
|
517
|
+
const client = getClient({
|
|
518
|
+
account: userAccount,
|
|
519
|
+
transport: withFeePayer(transport, http(server.url), {
|
|
520
|
+
policy: 'sign-and-broadcast',
|
|
521
|
+
}),
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
await sendTransactionSync(client, {
|
|
525
|
+
feePayer: true,
|
|
526
|
+
to: '0x0000000000000000000000000000000000000000',
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
expect(
|
|
530
|
+
requests.map(({ method, params }) => [method, params]),
|
|
531
|
+
).toMatchInlineSnapshot(`
|
|
532
|
+
[
|
|
533
|
+
[
|
|
534
|
+
"eth_sendRawTransactionSync",
|
|
535
|
+
[
|
|
536
|
+
"0x76f871820539808502cb417800825d82d8d79400000000000000000000000000000000000000008080c0800280808000c0b841f1b96061f66f044829e4237e881924e7d2efcff378df03212451831af5aedbe6262a8f2001ee0ffc4dab7211bc324c42298da463e01ce05402f5c16f884a608b1c9ac4fDC8e5D72AaADE30F9Ff52D392D60c68A64afeefeefeefee",
|
|
537
|
+
],
|
|
538
|
+
],
|
|
539
|
+
]
|
|
540
|
+
`)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test('behavior: unsupported method', async () => {
|
|
544
|
+
await expect(
|
|
545
|
+
fetch(server.url, {
|
|
546
|
+
method: 'POST',
|
|
547
|
+
body: JSON.stringify({
|
|
548
|
+
jsonrpc: '2.0',
|
|
549
|
+
id: 1,
|
|
550
|
+
method: 'eth_chainId',
|
|
551
|
+
}),
|
|
552
|
+
}).then((response) => response.json()),
|
|
553
|
+
).resolves.toMatchInlineSnapshot(`
|
|
554
|
+
{
|
|
555
|
+
"error": {
|
|
556
|
+
"code": -32004,
|
|
557
|
+
"name": "RpcResponse.MethodNotSupportedError",
|
|
558
|
+
"stack": "",
|
|
559
|
+
},
|
|
560
|
+
"id": 1,
|
|
561
|
+
"jsonrpc": "2.0",
|
|
562
|
+
}
|
|
563
|
+
`)
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
})
|