accounts 0.6.2 → 0.6.4
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 +14 -0
- package/dist/core/Schema.d.ts +95 -2
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/zod/rpc.d.ts +49 -1
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +41 -1
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/server/internal/handlers/relay.d.ts +6 -66
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +33 -27
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/dist/server/internal/handlers/utils.d.ts +1 -1
- package/dist/server/internal/handlers/utils.d.ts.map +1 -1
- package/dist/server/internal/handlers/utils.js +6 -5
- package/dist/server/internal/handlers/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/core/zod/rpc.ts +49 -1
- package/src/server/internal/handlers/relay.test.ts +113 -44
- package/src/server/internal/handlers/relay.ts +43 -90
- package/src/server/internal/handlers/utils.ts +8 -5
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from 'viem'
|
|
17
17
|
import { simulateCalls } from 'viem/actions'
|
|
18
18
|
import { tempo, tempoLocalnet, tempoMainnet, tempoModerato } from 'viem/chains'
|
|
19
|
-
import { Abis, Actions, Addresses, Transaction } from 'viem/tempo'
|
|
19
|
+
import { Abis, Actions, Addresses, Capabilities, Transaction } from 'viem/tempo'
|
|
20
20
|
|
|
21
21
|
import { type Handler, from } from '../../Handler.js'
|
|
22
22
|
import * as FeePayer from './feePayer.js'
|
|
@@ -67,7 +67,6 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
67
67
|
const {
|
|
68
68
|
chains = [tempo, tempoModerato],
|
|
69
69
|
feePayer: feePayerOptions,
|
|
70
|
-
feeSwap: feeSwapOptions,
|
|
71
70
|
onRequest,
|
|
72
71
|
path = '/',
|
|
73
72
|
resolveTokens = (chainId) =>
|
|
@@ -75,7 +74,11 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
75
74
|
transports = {},
|
|
76
75
|
...rest
|
|
77
76
|
} = options
|
|
78
|
-
|
|
77
|
+
|
|
78
|
+
const autoSwap = (() => {
|
|
79
|
+
if (options.autoSwap === false) return undefined
|
|
80
|
+
return { slippage: options.autoSwap?.slippage ?? 0.05 }
|
|
81
|
+
})()
|
|
79
82
|
|
|
80
83
|
const clients = new Map<number, Client>()
|
|
81
84
|
for (const chain of chains) {
|
|
@@ -141,7 +144,7 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
141
144
|
...(feePayerOptions ? { feePayer: true } : {}),
|
|
142
145
|
...(feeToken ? { feeToken } : {}),
|
|
143
146
|
}
|
|
144
|
-
let filled = await fill(client,
|
|
147
|
+
let filled = await fill(client, { autoSwap, feeToken, transaction: withOverrides })
|
|
145
148
|
|
|
146
149
|
// 3. Check if the fee payer approves this transaction.
|
|
147
150
|
const sponsored =
|
|
@@ -157,17 +160,17 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
157
160
|
// gas estimate and nonce are correct for a self-paid transaction.
|
|
158
161
|
if (feePayerOptions && !sponsored) {
|
|
159
162
|
const { feePayer: _, ...withoutFeePayer } = withOverrides
|
|
160
|
-
filled = await fill(client,
|
|
163
|
+
filled = await fill(client, { autoSwap, feeToken, transaction: withoutFeePayer })
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
const { transaction, swap
|
|
166
|
+
const { transaction, swap } = filled
|
|
164
167
|
|
|
165
168
|
// 4. Simulate and compute balance diffs + fee.
|
|
166
169
|
const calls = extractCalls(transaction)
|
|
167
170
|
const { balanceDiffs, fee } = await simulateAndParseDiffs(client, {
|
|
168
171
|
account: sender,
|
|
169
172
|
calls,
|
|
170
|
-
swap
|
|
173
|
+
swap,
|
|
171
174
|
feeToken: (transaction as { feeToken?: Address | undefined }).feeToken,
|
|
172
175
|
gas: (transaction as { gas?: bigint | undefined }).gas,
|
|
173
176
|
maxFeePerGas: (transaction as { maxFeePerGas?: bigint | undefined }).maxFeePerGas,
|
|
@@ -190,28 +193,29 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
190
193
|
}
|
|
191
194
|
: undefined
|
|
192
195
|
|
|
193
|
-
// 6. Resolve
|
|
194
|
-
const
|
|
195
|
-
if (!
|
|
196
|
+
// 6. Resolve autoSwap metadata (when AMM path was taken).
|
|
197
|
+
const autoSwap_ = await (async () => {
|
|
198
|
+
if (!swap) return undefined
|
|
196
199
|
const [inMeta, outMeta] = await Promise.all([
|
|
197
|
-
resolveTokenMetadata(client,
|
|
198
|
-
resolveTokenMetadata(client,
|
|
200
|
+
resolveTokenMetadata(client, swap.tokenIn).catch(() => undefined),
|
|
201
|
+
resolveTokenMetadata(client, swap.tokenOut).catch(() => undefined),
|
|
199
202
|
])
|
|
200
203
|
if (!inMeta || !outMeta) return undefined
|
|
201
204
|
return {
|
|
202
|
-
|
|
205
|
+
calls: swap.calls.map((c) => ({ to: c.to, data: c.data })),
|
|
206
|
+
slippage: autoSwap!.slippage,
|
|
203
207
|
maxIn: {
|
|
204
|
-
token:
|
|
205
|
-
value: Hex.fromNumber(
|
|
206
|
-
formatted: formatUnits(
|
|
208
|
+
token: swap.tokenIn,
|
|
209
|
+
value: Hex.fromNumber(swap.maxAmountIn) as `0x${string}`,
|
|
210
|
+
formatted: formatUnits(swap.maxAmountIn, inMeta.decimals),
|
|
207
211
|
decimals: inMeta.decimals,
|
|
208
212
|
symbol: inMeta.symbol,
|
|
209
213
|
name: inMeta.name,
|
|
210
214
|
},
|
|
211
215
|
minOut: {
|
|
212
|
-
token:
|
|
213
|
-
value: Hex.fromNumber(
|
|
214
|
-
formatted: formatUnits(
|
|
216
|
+
token: swap.tokenOut,
|
|
217
|
+
value: Hex.fromNumber(swap.amountOut) as `0x${string}`,
|
|
218
|
+
formatted: formatUnits(swap.amountOut, outMeta.decimals),
|
|
215
219
|
decimals: outMeta.decimals,
|
|
216
220
|
symbol: outMeta.symbol,
|
|
217
221
|
name: outMeta.name,
|
|
@@ -221,12 +225,12 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
221
225
|
|
|
222
226
|
return Utils.rpcResult(request, {
|
|
223
227
|
tx: core_Transaction.toRpc(tx as core_Transaction.Transaction),
|
|
224
|
-
|
|
228
|
+
capabilities: {
|
|
225
229
|
balanceDiffs,
|
|
226
230
|
fee,
|
|
227
231
|
sponsored: !!sponsor,
|
|
228
232
|
...(sponsor ? { sponsor } : {}),
|
|
229
|
-
...(
|
|
233
|
+
...(autoSwap_ ? { autoSwap: autoSwap_ } : {}),
|
|
230
234
|
},
|
|
231
235
|
})
|
|
232
236
|
} catch (error) {
|
|
@@ -239,10 +243,13 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
239
243
|
|
|
240
244
|
async function fill(
|
|
241
245
|
client: Client,
|
|
242
|
-
|
|
243
|
-
|
|
246
|
+
options: {
|
|
247
|
+
autoSwap?: { slippage: number } | undefined
|
|
248
|
+
feeToken?: Address | undefined
|
|
249
|
+
transaction: Record<string, unknown>
|
|
250
|
+
},
|
|
244
251
|
) {
|
|
245
|
-
const { feeToken,
|
|
252
|
+
const { autoSwap, feeToken, transaction: request } = options
|
|
246
253
|
|
|
247
254
|
// Skip re-formatting if already in RPC format (e.g. from viem's fillTransaction).
|
|
248
255
|
const format = (value: Record<string, unknown>) =>
|
|
@@ -257,11 +264,11 @@ async function fill(
|
|
|
257
264
|
} catch (error) {
|
|
258
265
|
if (!(error instanceof Error)) throw error
|
|
259
266
|
const parsed = parseInsufficientBalance(error)
|
|
260
|
-
if (!parsed || !feeToken) throw error
|
|
267
|
+
if (!parsed || !feeToken || !autoSwap) throw error
|
|
261
268
|
if (parsed.token.toLowerCase() === feeToken.toLowerCase()) throw error
|
|
262
269
|
|
|
263
270
|
const deficit = parsed.required - parsed.available
|
|
264
|
-
const maxAmountIn = deficit + (deficit * BigInt(Math.round(slippage * 1000))) / 1000n
|
|
271
|
+
const maxAmountIn = deficit + (deficit * BigInt(Math.round(autoSwap.slippage * 1000))) / 1000n
|
|
265
272
|
const swapCalls = buildSwapCalls(feeToken, parsed.token, deficit, maxAmountIn)
|
|
266
273
|
const existingCalls = request.calls as Call[] | undefined
|
|
267
274
|
// If the request was normalized to top-level to/data/value (single call),
|
|
@@ -290,6 +297,7 @@ async function fill(
|
|
|
290
297
|
return {
|
|
291
298
|
transaction: Utils.normalizeTempoTransaction(result.tx),
|
|
292
299
|
swap: {
|
|
300
|
+
calls: swapCalls,
|
|
293
301
|
tokenIn: feeToken,
|
|
294
302
|
tokenOut: parsed.token,
|
|
295
303
|
amountOut: deficit,
|
|
@@ -347,8 +355,12 @@ export namespace relay {
|
|
|
347
355
|
feePayer?:
|
|
348
356
|
| Omit<FeePayer.feePayer.Options, 'chains' | 'transports' | 'path' | 'onRequest'>
|
|
349
357
|
| undefined
|
|
350
|
-
/**
|
|
351
|
-
|
|
358
|
+
/**
|
|
359
|
+
* AMM swap options for automatic insufficient balance resolution.
|
|
360
|
+
* Set to `false` to disable. @default {}
|
|
361
|
+
*/
|
|
362
|
+
autoSwap?:
|
|
363
|
+
| false
|
|
352
364
|
| {
|
|
353
365
|
/** Slippage tolerance (e.g. 0.05 = 5%). @default 0.05 */
|
|
354
366
|
slippage?: number | undefined
|
|
@@ -367,65 +379,6 @@ export namespace relay {
|
|
|
367
379
|
/** Transports keyed by chain ID. Defaults to `http()` for each chain. */
|
|
368
380
|
transports?: Record<number, Transport> | undefined
|
|
369
381
|
}
|
|
370
|
-
|
|
371
|
-
/** Metadata returned alongside the filled transaction. */
|
|
372
|
-
export type Meta = {
|
|
373
|
-
/** Per-account balance diffs keyed by address. */
|
|
374
|
-
balanceDiffs?: Record<Address, readonly BalanceDiff[]> | undefined
|
|
375
|
-
/** Fee estimate for the transaction. */
|
|
376
|
-
fee: { amount: Hex.Hex; decimals: number; formatted: string; symbol: string } | null
|
|
377
|
-
/** Whether the transaction is sponsored by a fee payer. */
|
|
378
|
-
sponsored: boolean
|
|
379
|
-
/** Sponsor details (when sponsored). */
|
|
380
|
-
sponsor?: { address: Address; name: string; url: string } | undefined
|
|
381
|
-
/** AMM swap injected by the relay to cover an InsufficientBalance. */
|
|
382
|
-
feeSwap?:
|
|
383
|
-
| {
|
|
384
|
-
/** Max input amount with slippage. */
|
|
385
|
-
maxIn: SwapAmount
|
|
386
|
-
/** Deficit amount that triggered the swap. */
|
|
387
|
-
minOut: SwapAmount
|
|
388
|
-
/** Slippage tolerance (e.g. 0.1 = 10%). */
|
|
389
|
-
slippage: number
|
|
390
|
-
}
|
|
391
|
-
| undefined
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/** Token amount in a fee swap. */
|
|
395
|
-
export type SwapAmount = {
|
|
396
|
-
/** Token address. */
|
|
397
|
-
token: Address
|
|
398
|
-
/** Amount (hex-encoded). */
|
|
399
|
-
value: Hex.Hex
|
|
400
|
-
/** Human-readable formatted amount. */
|
|
401
|
-
formatted: string
|
|
402
|
-
/** Token decimals. */
|
|
403
|
-
decimals: number
|
|
404
|
-
/** Token symbol (e.g. "USDC.e"). */
|
|
405
|
-
symbol: string
|
|
406
|
-
/** Token name (e.g. "USDC.e"). */
|
|
407
|
-
name: string
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/** Balance diff for a single token relative to the user account. */
|
|
411
|
-
export type BalanceDiff = {
|
|
412
|
-
/** Token address. */
|
|
413
|
-
address: Address
|
|
414
|
-
/** Token decimals (e.g. 6). */
|
|
415
|
-
decimals: number
|
|
416
|
-
/** Direction relative to the user. */
|
|
417
|
-
direction: 'incoming' | 'outgoing'
|
|
418
|
-
/** Human-readable formatted currency value (e.g. "100.00"). */
|
|
419
|
-
formatted: string
|
|
420
|
-
/** Token name (e.g. "USDC.e"). */
|
|
421
|
-
name: string
|
|
422
|
-
/** Addresses receiving this asset. */
|
|
423
|
-
recipients: readonly Address[]
|
|
424
|
-
/** Token symbol (e.g. "USDC.e"). */
|
|
425
|
-
symbol: string
|
|
426
|
-
/** Token amount (hex-encoded). */
|
|
427
|
-
value: Hex.Hex
|
|
428
|
-
}
|
|
429
382
|
}
|
|
430
383
|
|
|
431
384
|
async function resolveFeeToken(
|
|
@@ -617,7 +570,7 @@ async function buildBalanceDiffs(
|
|
|
617
570
|
const fromLower = log.args.from.toLowerCase()
|
|
618
571
|
const toLower = log.args.to.toLowerCase()
|
|
619
572
|
|
|
620
|
-
// Skip swap-related transfers (reported in
|
|
573
|
+
// Skip swap-related transfers (reported in capabilities.autoSwap instead).
|
|
621
574
|
if (swap) {
|
|
622
575
|
if (token === swapTokenIn && fromLower === accountLower && toLower === dexLower) continue
|
|
623
576
|
if (token === swapTokenOut && fromLower === dexLower && toLower === accountLower) continue
|
|
@@ -644,7 +597,7 @@ async function buildBalanceDiffs(
|
|
|
644
597
|
if (log.args.owner.toLowerCase() !== accountLower) continue
|
|
645
598
|
const token = log.address.toLowerCase()
|
|
646
599
|
|
|
647
|
-
// Skip swap-related approvals (reported in
|
|
600
|
+
// Skip swap-related approvals (reported in capabilities.autoSwap instead).
|
|
648
601
|
if (swap && token === swapTokenIn && log.args.spender.toLowerCase() === dexLower) continue
|
|
649
602
|
|
|
650
603
|
const spenderKey = `${token}:${log.args.spender.toLowerCase()}`
|
|
@@ -681,7 +634,7 @@ async function buildBalanceDiffs(
|
|
|
681
634
|
)
|
|
682
635
|
|
|
683
636
|
// Build the diff array for this account.
|
|
684
|
-
const diffs:
|
|
637
|
+
const diffs: Capabilities.BalanceDiff[] = []
|
|
685
638
|
for (const entry of entries) {
|
|
686
639
|
const net =
|
|
687
640
|
entry.outgoing > entry.incoming
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hex, RpcRequest, RpcResponse } from 'ox'
|
|
2
2
|
import { Transaction as core_Transaction } from 'ox/tempo'
|
|
3
|
-
import type
|
|
3
|
+
import { type Client, BaseError } from 'viem'
|
|
4
4
|
import * as z from 'zod/mini'
|
|
5
5
|
|
|
6
6
|
export function resolveChainId(value: unknown) {
|
|
@@ -85,10 +85,13 @@ function resolveError(error: unknown): {
|
|
|
85
85
|
} {
|
|
86
86
|
if (!error || typeof error !== 'object') return {}
|
|
87
87
|
const e = error as Record<string, unknown>
|
|
88
|
-
//
|
|
89
|
-
if (
|
|
90
|
-
const inner =
|
|
91
|
-
|
|
88
|
+
// Use viem's walk() to find the innermost error with a numeric code.
|
|
89
|
+
if (error instanceof BaseError) {
|
|
90
|
+
const inner = error.walk(
|
|
91
|
+
(e) => typeof (e as Record<string, unknown>).code === 'number',
|
|
92
|
+
) as Record<string, unknown> | null
|
|
93
|
+
if (inner && typeof inner.code === 'number' && typeof inner.message === 'string')
|
|
94
|
+
return { message: inner.message, code: inner.code, data: inner.data }
|
|
92
95
|
}
|
|
93
96
|
if (typeof e.code === 'number' && typeof e.message === 'string')
|
|
94
97
|
return { message: e.message, code: e.code, data: e.data }
|