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.
@@ -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
- const slippage = feeSwapOptions?.slippage ?? 0.05
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, withOverrides, { feeToken, slippage })
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, withoutFeePayer, { feeToken, slippage })
163
+ filled = await fill(client, { autoSwap, feeToken, transaction: withoutFeePayer })
161
164
  }
162
165
 
163
- const { transaction, swap: feeSwap } = filled
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: feeSwap,
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 feeSwap metadata (when AMM path was taken).
194
- const feeSwapMeta = await (async (): Promise<relay.Meta['feeSwap']> => {
195
- if (!feeSwap) return undefined
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, feeSwap.tokenIn).catch(() => undefined),
198
- resolveTokenMetadata(client, feeSwap.tokenOut).catch(() => undefined),
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
- slippage,
205
+ calls: swap.calls.map((c) => ({ to: c.to, data: c.data })),
206
+ slippage: autoSwap!.slippage,
203
207
  maxIn: {
204
- token: feeSwap.tokenIn,
205
- value: Hex.fromNumber(feeSwap.maxAmountIn) as `0x${string}`,
206
- formatted: formatUnits(feeSwap.maxAmountIn, inMeta.decimals),
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: feeSwap.tokenOut,
213
- value: Hex.fromNumber(feeSwap.amountOut) as `0x${string}`,
214
- formatted: formatUnits(feeSwap.amountOut, outMeta.decimals),
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
- meta: {
228
+ capabilities: {
225
229
  balanceDiffs,
226
230
  fee,
227
231
  sponsored: !!sponsor,
228
232
  ...(sponsor ? { sponsor } : {}),
229
- ...(feeSwapMeta ? { feeSwap: feeSwapMeta } : {}),
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
- request: Record<string, unknown>,
243
- options: { feeToken?: Address | undefined; slippage?: number | undefined } = {},
246
+ options: {
247
+ autoSwap?: { slippage: number } | undefined
248
+ feeToken?: Address | undefined
249
+ transaction: Record<string, unknown>
250
+ },
244
251
  ) {
245
- const { feeToken, slippage = 0.05 } = options
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
- /** AMM swap options for automatic InsufficientBalance resolution. */
351
- feeSwap?:
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 meta.feeSwap instead).
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 meta.feeSwap instead).
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: relay.BalanceDiff[] = []
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 { Client } from 'viem'
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
- // Walk to the innermost cause with a numeric code (raw RPC error).
89
- if (e.cause && typeof e.cause === 'object') {
90
- const inner = resolveError(e.cause)
91
- if (inner.message) return inner
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 }