accounts 0.7.2 → 0.8.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 +23 -0
- package/README.md +19 -20
- package/dist/cli/Provider.d.ts +1 -1
- package/dist/cli/Provider.d.ts.map +1 -1
- package/dist/cli/Provider.js +4 -1
- package/dist/cli/Provider.js.map +1 -1
- package/dist/cli/keyring.js +1 -1
- package/dist/cli/keyring.js.map +1 -1
- package/dist/core/Account.d.ts +2 -0
- package/dist/core/Account.d.ts.map +1 -1
- package/dist/core/Account.js.map +1 -1
- package/dist/core/Adapter.d.ts +9 -1
- package/dist/core/Adapter.d.ts.map +1 -1
- package/dist/core/Dialog.d.ts +16 -1
- package/dist/core/Dialog.d.ts.map +1 -1
- package/dist/core/Dialog.js +40 -3
- package/dist/core/Dialog.js.map +1 -1
- package/dist/core/Messenger.d.ts +15 -0
- package/dist/core/Messenger.d.ts.map +1 -1
- package/dist/core/Messenger.js.map +1 -1
- package/dist/core/Provider.d.ts +2 -0
- package/dist/core/Provider.d.ts.map +1 -1
- package/dist/core/Provider.js +24 -6
- package/dist/core/Provider.js.map +1 -1
- package/dist/core/Remote.d.ts +7 -1
- package/dist/core/Remote.d.ts.map +1 -1
- package/dist/core/Remote.js +18 -2
- package/dist/core/Remote.js.map +1 -1
- package/dist/core/Schema.d.ts +17 -3
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/Store.d.ts +2 -0
- package/dist/core/Store.d.ts.map +1 -1
- package/dist/core/Store.js +12 -7
- package/dist/core/Store.js.map +1 -1
- package/dist/core/TrustedHosts.d.ts.map +1 -1
- package/dist/core/TrustedHosts.js +2 -0
- package/dist/core/TrustedHosts.js.map +1 -1
- package/dist/core/WebAuthnCeremony.d.ts +8 -0
- package/dist/core/WebAuthnCeremony.d.ts.map +1 -1
- package/dist/core/WebAuthnCeremony.js.map +1 -1
- package/dist/core/adapters/dialog.d.ts +3 -1
- package/dist/core/adapters/dialog.d.ts.map +1 -1
- package/dist/core/adapters/dialog.js +8 -5
- package/dist/core/adapters/dialog.js.map +1 -1
- package/dist/core/adapters/local.js +4 -4
- package/dist/core/adapters/local.js.map +1 -1
- package/dist/core/adapters/webAuthn.d.ts.map +1 -1
- package/dist/core/adapters/webAuthn.js +7 -2
- package/dist/core/adapters/webAuthn.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +17 -7
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +5 -1
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/react/Remote.d.ts +2 -0
- package/dist/react/Remote.d.ts.map +1 -1
- package/dist/react/Remote.js +69 -0
- package/dist/react/Remote.js.map +1 -1
- package/dist/react-native/Provider.d.ts.map +1 -1
- package/dist/react-native/Provider.js +4 -1
- package/dist/react-native/Provider.js.map +1 -1
- package/dist/react-native/adapter.d.ts +1 -1
- package/dist/react-native/adapter.d.ts.map +1 -1
- package/dist/react-native/adapter.js +2 -0
- package/dist/react-native/adapter.js.map +1 -1
- package/dist/server/CliAuth.d.ts +82 -11
- package/dist/server/CliAuth.d.ts.map +1 -1
- package/dist/server/CliAuth.js +82 -11
- package/dist/server/CliAuth.js.map +1 -1
- package/dist/server/internal/handlers/codeAuth.d.ts +2 -2
- package/dist/server/internal/handlers/codeAuth.js +2 -2
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +182 -88
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/dist/server/internal/handlers/utils.d.ts +2 -2
- package/dist/server/internal/handlers/utils.d.ts.map +1 -1
- package/dist/server/internal/handlers/utils.js +7 -2
- package/dist/server/internal/handlers/utils.js.map +1 -1
- package/dist/server/internal/handlers/webAuthn.d.ts +2 -0
- package/dist/server/internal/handlers/webAuthn.d.ts.map +1 -1
- package/dist/server/internal/handlers/webAuthn.js +20 -9
- package/dist/server/internal/handlers/webAuthn.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/Provider.test.ts +3 -1
- package/src/cli/Provider.ts +4 -2
- package/src/cli/keyring.ts +1 -1
- package/src/core/Account.ts +2 -0
- package/src/core/Adapter.ts +9 -1
- package/src/core/Dialog.browser.test.ts +3 -3
- package/src/core/Dialog.ts +51 -4
- package/src/core/Messenger.ts +12 -0
- package/src/core/Provider.ts +46 -18
- package/src/core/Remote.ts +26 -4
- package/src/core/Store.ts +12 -4
- package/src/core/TrustedHosts.ts +1 -0
- package/src/core/WebAuthnCeremony.ts +8 -0
- package/src/core/adapters/dialog.ts +10 -5
- package/src/core/adapters/local.ts +4 -4
- package/src/core/adapters/webAuthn.ts +7 -2
- package/src/core/zod/rpc.ts +5 -1
- package/src/react/Remote.ts +76 -0
- package/src/react-native/Provider.ts +9 -1
- package/src/react-native/adapter.ts +3 -1
- package/src/server/CliAuth.ts +82 -11
- package/src/server/internal/handlers/codeAuth.ts +2 -2
- package/src/server/internal/handlers/relay.test.ts +351 -1
- package/src/server/internal/handlers/relay.ts +207 -93
- package/src/server/internal/handlers/utils.ts +8 -2
- package/src/server/internal/handlers/webAuthn.ts +24 -12
|
@@ -130,107 +130,156 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
130
130
|
typeof parameters.from === 'string' ? (parameters.from as Address) : undefined
|
|
131
131
|
const requestFeeToken =
|
|
132
132
|
typeof parameters.feeToken === 'string' ? (parameters.feeToken as Address) : undefined
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
})
|
|
142
|
-
: requestFeeToken
|
|
143
|
-
|
|
144
|
-
// 2. Fill transaction via RPC node (with AMM resolution on InsufficientBalance).
|
|
145
|
-
const normalized = Utils.normalizeFillTransactionRequest(parameters)
|
|
146
|
-
const transaction = {
|
|
133
|
+
const externalFeePayerUrl =
|
|
134
|
+
typeof parameters.feePayer === 'string' ? parameters.feePayer : undefined
|
|
135
|
+
const requestsSponsorship =
|
|
136
|
+
(!!feePayerOptions || !!externalFeePayerUrl) && parameters.feePayer !== false
|
|
137
|
+
|
|
138
|
+
const { feePayer: _feePayer, ...normalized } =
|
|
139
|
+
Utils.normalizeFillTransactionRequest(parameters)
|
|
140
|
+
const baseTx = {
|
|
147
141
|
...normalized,
|
|
148
142
|
...(typeof chainId !== 'undefined' ? { chainId } : {}),
|
|
149
|
-
...(
|
|
150
|
-
...(feeToken ? { feeToken } : {}),
|
|
143
|
+
...(requestFeeToken ? { feeToken: requestFeeToken } : {}),
|
|
151
144
|
}
|
|
152
|
-
let filled = await (async () => {
|
|
153
|
-
if (requestsSponsorship && Sponsorship.isPreparedTransaction(transaction))
|
|
154
|
-
return { transaction: Utils.normalizeTempoTransaction(transaction) }
|
|
155
|
-
return await fill(client, { autoSwap, feeToken, transaction })
|
|
156
|
-
})()
|
|
157
145
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
146
|
+
let filled: Awaited<ReturnType<typeof fill>>
|
|
147
|
+
let sponsored = false
|
|
148
|
+
let feeToken = requestFeeToken
|
|
149
|
+
|
|
150
|
+
// Lazily resolve a swap source token when autoSwap needs one.
|
|
151
|
+
const resolveFeeTokenForSwap = from
|
|
152
|
+
? (insufficientToken: Address) =>
|
|
153
|
+
resolveFeeToken(client, {
|
|
154
|
+
account: from,
|
|
155
|
+
feeToken: undefined,
|
|
156
|
+
tokens: (resolveTokens(chainId) ?? []).filter(
|
|
157
|
+
(t) => t.toLowerCase() !== insufficientToken.toLowerCase(),
|
|
158
|
+
),
|
|
165
159
|
})
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
: undefined
|
|
161
|
+
|
|
162
|
+
// When the app provides its own fee payer URL, route the fill
|
|
163
|
+
// through that service so it can sign the transaction.
|
|
164
|
+
const fillClient = externalFeePayerUrl
|
|
165
|
+
? createClient({
|
|
166
|
+
chain: client.chain,
|
|
167
|
+
batch: { multicall: { deployless: true } },
|
|
168
|
+
transport: http(externalFeePayerUrl),
|
|
169
|
+
})
|
|
170
|
+
: client
|
|
171
|
+
|
|
172
|
+
if (requestsSponsorship && !feePayerOptions?.validate) {
|
|
173
|
+
// Path A: sponsorship guaranteed (no validate) — skip fee token
|
|
174
|
+
// resolution, fill once with feePayer, then parallelize the rest.
|
|
175
|
+
const transaction = { ...baseTx, feePayer: true }
|
|
176
|
+
if (Sponsorship.isPreparedTransaction(transaction)) {
|
|
177
|
+
filled = {
|
|
178
|
+
transaction: Utils.normalizeTempoTransaction(transaction),
|
|
179
|
+
sponsor: undefined,
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
filled = await fill(fillClient, {
|
|
183
|
+
autoSwap,
|
|
184
|
+
feeToken,
|
|
185
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
186
|
+
transaction,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
sponsored = true
|
|
190
|
+
} else if (requestsSponsorship && feePayerOptions?.validate) {
|
|
191
|
+
// Path B: sponsorship possible but may be rejected — fill both
|
|
192
|
+
// variants in parallel, then pick the right one.
|
|
193
|
+
const sponsoredTx = { ...baseTx, feePayer: true }
|
|
194
|
+
|
|
195
|
+
if (Sponsorship.isPreparedTransaction(sponsoredTx)) {
|
|
196
|
+
// Already prepared — skip fills, just validate sponsorship.
|
|
197
|
+
const prepared = {
|
|
198
|
+
transaction: Utils.normalizeTempoTransaction(sponsoredTx),
|
|
199
|
+
sponsor: undefined,
|
|
200
|
+
}
|
|
201
|
+
sponsored = await Sponsorship.shouldSponsor({
|
|
202
|
+
sender: from,
|
|
203
|
+
transaction: prepared.transaction,
|
|
204
|
+
validate: feePayerOptions!.validate,
|
|
205
|
+
})
|
|
206
|
+
filled = prepared
|
|
207
|
+
} else {
|
|
208
|
+
const fillOptions = {
|
|
209
|
+
autoSwap,
|
|
210
|
+
feeToken,
|
|
211
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
212
|
+
}
|
|
213
|
+
const [sponsoredFill, unsponsoredFill] = await Promise.all([
|
|
214
|
+
fill(fillClient, { ...fillOptions, transaction: sponsoredTx }),
|
|
215
|
+
fill(client, { ...fillOptions, transaction: baseTx }),
|
|
216
|
+
])
|
|
217
|
+
sponsored = await Sponsorship.shouldSponsor({
|
|
218
|
+
sender: from,
|
|
219
|
+
transaction: sponsoredFill.transaction,
|
|
220
|
+
validate: feePayerOptions!.validate,
|
|
221
|
+
})
|
|
222
|
+
filled = sponsored ? sponsoredFill : unsponsoredFill
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// Path C: no sponsorship configured — resolve fee token, fill once.
|
|
226
|
+
feeToken = features.feeTokenResolution
|
|
227
|
+
? await resolveFeeToken(client, {
|
|
228
|
+
account: from,
|
|
229
|
+
feeToken: requestFeeToken,
|
|
230
|
+
tokens: resolveTokens(chainId),
|
|
231
|
+
})
|
|
232
|
+
: requestFeeToken
|
|
233
|
+
const transaction = { ...baseTx, ...(feeToken ? { feeToken } : {}) }
|
|
234
|
+
filled = await fill(client, {
|
|
235
|
+
autoSwap,
|
|
236
|
+
feeToken,
|
|
237
|
+
resolveFeeToken: resolveFeeTokenForSwap,
|
|
238
|
+
transaction,
|
|
239
|
+
})
|
|
173
240
|
}
|
|
174
241
|
|
|
175
242
|
const transaction_filled = filled.transaction
|
|
176
243
|
const swap = 'swap' in filled ? filled.swap : undefined
|
|
244
|
+
if (!feeToken)
|
|
245
|
+
feeToken =
|
|
246
|
+
(transaction_filled.feeToken as Address | undefined) ?? resolveTokens(chainId)?.[0]
|
|
177
247
|
|
|
178
|
-
//
|
|
179
|
-
const { balanceDiffs, fee } = features.simulate
|
|
180
|
-
? await simulateAndParseDiffs(client, {
|
|
181
|
-
account: from,
|
|
182
|
-
calls: extractCalls(transaction_filled),
|
|
183
|
-
swap,
|
|
184
|
-
feeToken: transaction_filled.feeToken,
|
|
185
|
-
gas: transaction_filled.gas,
|
|
186
|
-
maxFeePerGas: transaction_filled.maxFeePerGas,
|
|
187
|
-
})
|
|
188
|
-
: { balanceDiffs: undefined, fee: undefined }
|
|
189
|
-
|
|
190
|
-
// 5. Sign as fee payer (if sponsored and not already signed).
|
|
248
|
+
// Parallelize: simulate, fee payer signing, and autoSwap metadata.
|
|
191
249
|
const alreadySigned =
|
|
192
250
|
'feePayerSignature' in transaction_filled &&
|
|
193
251
|
transaction_filled.feePayerSignature != null
|
|
194
|
-
const transaction_final = await (async () => {
|
|
195
|
-
if (!sponsored || !feePayerOptions || alreadySigned) return transaction_filled
|
|
196
|
-
return await Sponsorship.sign({
|
|
197
|
-
account: feePayerOptions.account,
|
|
198
|
-
sender: from,
|
|
199
|
-
transaction: transaction_filled,
|
|
200
|
-
})
|
|
201
|
-
})()
|
|
202
252
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
253
|
+
const [{ balanceDiffs, fee }, transaction_final, autoSwap_] = await Promise.all([
|
|
254
|
+
// Simulate and compute balance diffs + fee.
|
|
255
|
+
features.simulate
|
|
256
|
+
? simulateAndParseDiffs(client, {
|
|
257
|
+
account: from,
|
|
258
|
+
calls: extractCalls(transaction_filled),
|
|
259
|
+
swap,
|
|
260
|
+
feeToken,
|
|
261
|
+
gas: transaction_filled.gas,
|
|
262
|
+
maxFeePerGas: transaction_filled.maxFeePerGas,
|
|
263
|
+
})
|
|
264
|
+
: { balanceDiffs: undefined, fee: undefined },
|
|
265
|
+
// Sign as fee payer (if sponsored and not already signed).
|
|
266
|
+
sponsored && feePayerOptions && !alreadySigned
|
|
267
|
+
? Sponsorship.sign({
|
|
268
|
+
account: feePayerOptions.account,
|
|
269
|
+
sender: from,
|
|
270
|
+
transaction: transaction_filled,
|
|
271
|
+
})
|
|
272
|
+
: Promise.resolve(transaction_filled),
|
|
273
|
+
// Resolve autoSwap metadata (when AMM path was taken).
|
|
274
|
+
resolveAutoSwapMetadata(client, { autoSwap, swap }),
|
|
275
|
+
])
|
|
276
|
+
|
|
277
|
+
const sponsor = (() => {
|
|
278
|
+
if (!sponsored) return undefined
|
|
279
|
+
// App-provided fee payer: relay back the sponsor from the upstream response.
|
|
280
|
+
if (externalFeePayerUrl) return filled.sponsor
|
|
281
|
+
if (feePayerOptions) return Sponsorship.getSponsor(feePayerOptions)
|
|
282
|
+
return filled.sponsor
|
|
234
283
|
})()
|
|
235
284
|
|
|
236
285
|
return RpcResponse.from(
|
|
@@ -501,6 +550,7 @@ async function fill(
|
|
|
501
550
|
options: {
|
|
502
551
|
autoSwap?: { slippage: number } | undefined
|
|
503
552
|
feeToken?: Address | undefined
|
|
553
|
+
resolveFeeToken?: ((insufficientToken: Address) => Promise<Address | undefined>) | undefined
|
|
504
554
|
transaction: Record<string, unknown>
|
|
505
555
|
},
|
|
506
556
|
) {
|
|
@@ -511,14 +561,24 @@ async function fill(
|
|
|
511
561
|
value.type === '0x76' ? value : Utils.formatFillTransactionRequest(client, value)
|
|
512
562
|
|
|
513
563
|
try {
|
|
564
|
+
const formatted = format(request)
|
|
514
565
|
const result = await client.request({
|
|
515
566
|
method: 'eth_fillTransaction',
|
|
516
|
-
params: [
|
|
567
|
+
params: [formatted as never],
|
|
517
568
|
})
|
|
518
|
-
|
|
569
|
+
// FIXME: node estimates gas with secp256k1 dummy sig + null feePayerSignature.
|
|
570
|
+
// Actual tx has larger keychain/webAuthn sigs + real fee payer sig, costing
|
|
571
|
+
// more intrinsic gas. Mirror the bump from viem's tempo chainConfig.
|
|
572
|
+
// @ts-expect-error
|
|
573
|
+
if (result.tx.gas && result.tx.feePayer)
|
|
574
|
+
result.tx.gas = Hex.fromNumber(BigInt(result.tx.gas) + 20_000n)
|
|
575
|
+
const sponsor = (result as Record<string, any>).capabilities?.sponsor as
|
|
576
|
+
| { address: Address; name?: string; url?: string }
|
|
577
|
+
| undefined
|
|
578
|
+
return { transaction: Utils.normalizeTempoTransaction(result.tx), sponsor }
|
|
519
579
|
} catch (error) {
|
|
520
580
|
if (!(error instanceof Error)) throw error
|
|
521
|
-
if (!
|
|
581
|
+
if (!autoSwap) throw error
|
|
522
582
|
|
|
523
583
|
const revert = ExecutionError.parse(error)
|
|
524
584
|
if (revert?.errorName !== 'InsufficientBalance') throw error
|
|
@@ -526,13 +586,19 @@ async function fill(
|
|
|
526
586
|
const [available, required, token] = revert.args
|
|
527
587
|
if (typeof available === 'undefined' || typeof required === 'undefined' || !token) throw error
|
|
528
588
|
|
|
529
|
-
|
|
589
|
+
// Resolve a source token for the swap: use the provided feeToken,
|
|
590
|
+
// or fall back to resolveFeeToken() to find one the sender holds.
|
|
591
|
+
const sourceToken =
|
|
592
|
+
feeToken && feeToken.toLowerCase() !== token.toLowerCase()
|
|
593
|
+
? feeToken
|
|
594
|
+
: await options.resolveFeeToken?.(token as Address)
|
|
595
|
+
if (!sourceToken || sourceToken.toLowerCase() === token.toLowerCase()) throw error
|
|
530
596
|
|
|
531
597
|
const deficit = required - available
|
|
532
598
|
const maxAmountIn = deficit + (deficit * BigInt(Math.round(autoSwap.slippage * 1000))) / 1000n
|
|
533
599
|
|
|
534
600
|
const originalCalls = (request.calls as Call[] | undefined) ?? []
|
|
535
|
-
const swapCalls = buildSwapCalls(
|
|
601
|
+
const swapCalls = buildSwapCalls(sourceToken, token, deficit, maxAmountIn)
|
|
536
602
|
|
|
537
603
|
const result = await client.request({
|
|
538
604
|
method: 'eth_fillTransaction',
|
|
@@ -543,11 +609,15 @@ async function fill(
|
|
|
543
609
|
}) as never,
|
|
544
610
|
],
|
|
545
611
|
})
|
|
612
|
+
const sponsor = (result as Record<string, any>).capabilities?.sponsor as
|
|
613
|
+
| { address: Address; name?: string; url?: string }
|
|
614
|
+
| undefined
|
|
546
615
|
return {
|
|
547
616
|
transaction: Utils.normalizeTempoTransaction(result.tx),
|
|
617
|
+
sponsor,
|
|
548
618
|
swap: {
|
|
549
619
|
calls: swapCalls,
|
|
550
|
-
tokenIn:
|
|
620
|
+
tokenIn: sourceToken,
|
|
551
621
|
tokenOut: token,
|
|
552
622
|
amountOut: deficit,
|
|
553
623
|
maxAmountIn,
|
|
@@ -851,6 +921,50 @@ async function resolveTokenMetadata(
|
|
|
851
921
|
}
|
|
852
922
|
}
|
|
853
923
|
|
|
924
|
+
async function resolveAutoSwapMetadata(
|
|
925
|
+
client: Client,
|
|
926
|
+
options: {
|
|
927
|
+
autoSwap?: { slippage: number } | undefined
|
|
928
|
+
swap?:
|
|
929
|
+
| {
|
|
930
|
+
calls: readonly { to: Address; data: `0x${string}` }[]
|
|
931
|
+
tokenIn: Address
|
|
932
|
+
tokenOut: Address
|
|
933
|
+
amountOut: bigint
|
|
934
|
+
maxAmountIn: bigint
|
|
935
|
+
}
|
|
936
|
+
| undefined
|
|
937
|
+
},
|
|
938
|
+
) {
|
|
939
|
+
const { autoSwap, swap } = options
|
|
940
|
+
if (!autoSwap || !swap) return undefined
|
|
941
|
+
const [inMeta, outMeta] = await Promise.all([
|
|
942
|
+
resolveTokenMetadata(client, swap.tokenIn).catch(() => undefined),
|
|
943
|
+
resolveTokenMetadata(client, swap.tokenOut).catch(() => undefined),
|
|
944
|
+
])
|
|
945
|
+
if (!inMeta || !outMeta) return undefined
|
|
946
|
+
return {
|
|
947
|
+
calls: swap.calls.map((c) => ({ to: c.to, data: c.data })),
|
|
948
|
+
slippage: autoSwap.slippage,
|
|
949
|
+
maxIn: {
|
|
950
|
+
token: swap.tokenIn,
|
|
951
|
+
value: Hex.fromNumber(swap.maxAmountIn) as `0x${string}`,
|
|
952
|
+
formatted: formatUnits(swap.maxAmountIn, inMeta.decimals),
|
|
953
|
+
decimals: inMeta.decimals,
|
|
954
|
+
symbol: inMeta.symbol,
|
|
955
|
+
name: inMeta.name,
|
|
956
|
+
},
|
|
957
|
+
minOut: {
|
|
958
|
+
token: swap.tokenOut,
|
|
959
|
+
value: Hex.fromNumber(swap.amountOut) as `0x${string}`,
|
|
960
|
+
formatted: formatUnits(swap.amountOut, outMeta.decimals),
|
|
961
|
+
decimals: outMeta.decimals,
|
|
962
|
+
symbol: outMeta.symbol,
|
|
963
|
+
name: outMeta.name,
|
|
964
|
+
},
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
854
968
|
async function computeFee(
|
|
855
969
|
client: Client,
|
|
856
970
|
options: {
|
|
@@ -6,7 +6,11 @@ import * as z from 'zod/mini'
|
|
|
6
6
|
export function resolveChainId(value: unknown) {
|
|
7
7
|
if (typeof value === 'number') return value
|
|
8
8
|
if (typeof value === 'bigint') return Number(value)
|
|
9
|
-
if (typeof value === 'string'
|
|
9
|
+
if (typeof value === 'string') {
|
|
10
|
+
if (Hex.validate(value)) return Hex.toNumber(value)
|
|
11
|
+
const n = Number(value)
|
|
12
|
+
if (Number.isFinite(n)) return n
|
|
13
|
+
}
|
|
10
14
|
return undefined
|
|
11
15
|
}
|
|
12
16
|
|
|
@@ -16,7 +20,9 @@ export function formatFillTransactionRequest(client: Client, value: Record<strin
|
|
|
16
20
|
return format({ ...value } as never, 'fillTransaction') as Record<string, unknown>
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
export function normalizeFillTransactionRequest(
|
|
23
|
+
export function normalizeFillTransactionRequest(
|
|
24
|
+
tx: Record<string, unknown>,
|
|
25
|
+
): Record<string, unknown> & { calls: unknown[] } {
|
|
20
26
|
const { to, data, value, ...rest } = tx
|
|
21
27
|
if (Array.isArray(tx.calls) && tx.calls.length > 0)
|
|
22
28
|
return {
|
|
@@ -57,7 +57,7 @@ export function webAuthn(options: webAuthn.Options): Handler {
|
|
|
57
57
|
...(userId ? { user: { id: new TextEncoder().encode(userId), name } } : undefined),
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
-
await kv.set(`challenge:${challenge}`, Date.now())
|
|
60
|
+
await kv.set(`challenge:${challenge}`, { created: Date.now(), name })
|
|
61
61
|
|
|
62
62
|
return Response.json({ options })
|
|
63
63
|
} catch (error) {
|
|
@@ -74,10 +74,9 @@ export function webAuthn(options: webAuthn.Options): Handler {
|
|
|
74
74
|
Bytes.toString(new Uint8Array(deserialized.clientDataJSON)),
|
|
75
75
|
) as { challenge: string }
|
|
76
76
|
const challenge = Hex.fromBytes(Base64.toBytes(clientData.challenge))
|
|
77
|
-
const stored = await kv.get<number>(`challenge:${challenge}`)
|
|
78
|
-
if (!stored || Date.now() - stored > challengeTtl * 1_000)
|
|
77
|
+
const stored = await kv.get<{ created: number; name: string }>(`challenge:${challenge}`)
|
|
78
|
+
if (!stored || Date.now() - stored.created > challengeTtl * 1_000)
|
|
79
79
|
throw new Error('Missing or expired challenge')
|
|
80
|
-
await kv.delete(`challenge:${challenge}`)
|
|
81
80
|
|
|
82
81
|
const result = Registration.verify(credential, {
|
|
83
82
|
challenge,
|
|
@@ -88,10 +87,17 @@ export function webAuthn(options: webAuthn.Options): Handler {
|
|
|
88
87
|
const { publicKey } = result.credential
|
|
89
88
|
const credentialId = credential.id
|
|
90
89
|
|
|
91
|
-
await kv.set(`credential:${credentialId}`, { publicKey })
|
|
92
|
-
|
|
93
90
|
const json = { credentialId, publicKey }
|
|
94
|
-
const hook = await
|
|
91
|
+
const [, hook] = await Promise.all([
|
|
92
|
+
kv.set(`credential:${credentialId}`, { publicKey }),
|
|
93
|
+
onRegister?.({
|
|
94
|
+
credentialId,
|
|
95
|
+
name: stored.name,
|
|
96
|
+
publicKey,
|
|
97
|
+
request: c.req.raw,
|
|
98
|
+
}),
|
|
99
|
+
kv.delete(`challenge:${challenge}`),
|
|
100
|
+
])
|
|
95
101
|
return mergeResponse(json, hook)
|
|
96
102
|
} catch (error) {
|
|
97
103
|
return Response.json({ error: (error as Error).message }, { status: 400 })
|
|
@@ -136,12 +142,13 @@ export function webAuthn(options: webAuthn.Options): Handler {
|
|
|
136
142
|
challenge: string
|
|
137
143
|
}
|
|
138
144
|
const challenge = Hex.fromBytes(Base64.toBytes(clientData.challenge))
|
|
139
|
-
|
|
145
|
+
|
|
146
|
+
const [stored, credentialData] = await Promise.all([
|
|
147
|
+
kv.get<number>(`challenge:${challenge}`),
|
|
148
|
+
kv.get<{ publicKey: string }>(`credential:${response.id}`),
|
|
149
|
+
])
|
|
140
150
|
if (!stored || Date.now() - stored > challengeTtl * 1_000)
|
|
141
151
|
throw new Error('Missing or expired challenge')
|
|
142
|
-
await kv.delete(`challenge:${challenge}`)
|
|
143
|
-
|
|
144
|
-
const credentialData = await kv.get<{ publicKey: string }>(`credential:${response.id}`)
|
|
145
152
|
if (!credentialData) throw new Error('Unknown credential')
|
|
146
153
|
|
|
147
154
|
const valid = Authentication.verify(response, {
|
|
@@ -160,7 +167,10 @@ export function webAuthn(options: webAuthn.Options): Handler {
|
|
|
160
167
|
publicKey: credentialData.publicKey,
|
|
161
168
|
...(userHandle && userHandle.length > 0 ? { userId: userHandle } : undefined),
|
|
162
169
|
}
|
|
163
|
-
const hook = await
|
|
170
|
+
const [hook] = await Promise.all([
|
|
171
|
+
onAuthenticate?.({ ...json, request: c.req.raw }),
|
|
172
|
+
kv.delete(`challenge:${challenge}`),
|
|
173
|
+
])
|
|
164
174
|
return mergeResponse(json, hook)
|
|
165
175
|
} catch (error) {
|
|
166
176
|
return Response.json({ error: (error as Error).message }, { status: 400 })
|
|
@@ -179,6 +189,8 @@ export declare namespace webAuthn {
|
|
|
179
189
|
/** Called after a successful registration. The returned response is merged onto the default JSON response. */
|
|
180
190
|
onRegister?: (parameters: {
|
|
181
191
|
credentialId: string
|
|
192
|
+
/** The name provided during `/register/options` (e.g. user email). */
|
|
193
|
+
name: string
|
|
182
194
|
publicKey: string
|
|
183
195
|
request: Request
|
|
184
196
|
}) => Response | Promise<Response> | void | Promise<void>
|