From c29133398c4215839ded23b2899fcf8c043db2a0 Mon Sep 17 00:00:00 2001 From: brianna Date: Fri, 29 May 2026 18:34:20 +0800 Subject: [PATCH] feat: forward optional `gas` override on sendTransaction / sendCalls The PrivyAlchemy EVM adapter currently lets viem auto-estimate gasLimit when broadcasting a tx. For complex routes (e.g. LiFi multi-hop via FeeCollector + a DEX) the auto-estimate runs too tight and txs revert at ~95% utilization. Observed on Base mainnet: 0x30b19449... gasLimit 549,099 gasUsed 522,654 (95.2%) reverted 0x17142457... gasLimit 515,171 gasUsed 491,293 (95.4%) reverted 0x445a73d0... gasLimit 434,657 gasUsed 409,803 (94.3%) reverted Aggregators like LiFi return a padded `gasLimit` in their quote response, but the adapter destructures only `to`/`data`/`value` from the Call and drops it. Changes: - Introduce `EvmCall` (re-export of viem's `Call` with an additional optional `gas?: bigint` field) for IEvmProviderAdapter's `sendTransaction` and `sendCalls` signatures. - `PrivyAlchemyEvmProviderAdapter.sendTransaction`: forward `call.gas` to walletClient.sendTransaction when set. - `PrivyAlchemyEvmProviderAdapter.sendCalls`: forward `call.gas` per-call into the Alchemy smart-wallet `sendCalls` payload. - `ViemProviderAdapter` (abstract): update the not-implemented stubs to use the new EvmCall type. Fully backward-compatible: omitting `gas` preserves current behaviour (viem/Alchemy estimates). Callers that want to pin the limit can now do so by setting `gas` on the call object. --- .../evm/privyAlchemyEvmProviderAdapter.ts | 15 ++++++++++++--- src/providers/evm/viemProviderAdapter.ts | 7 ++++--- src/providers/types.ts | 17 +++++++++++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/providers/evm/privyAlchemyEvmProviderAdapter.ts b/src/providers/evm/privyAlchemyEvmProviderAdapter.ts index 440b780..d494c5d 100644 --- a/src/providers/evm/privyAlchemyEvmProviderAdapter.ts +++ b/src/providers/evm/privyAlchemyEvmProviderAdapter.ts @@ -7,7 +7,6 @@ import { toHex, TypedDataDefinition, type Address, - type Call, type Chain, type Hex, type Log, @@ -24,6 +23,7 @@ import { } from "viem/actions"; import { createEvmNetworkContext, EVM_MAINNET_CHAINS } from "../../core/chains.js"; import type { + EvmCall, GetLogsParams, IEvmProviderAdapter, ReadContractParams, @@ -451,7 +451,7 @@ export class PrivyAlchemyEvmProviderAdapter implements IEvmProviderAdapter { return `0x${hex}`; } - async sendTransaction(chainId: number, call: Call): Promise
{ + async sendTransaction(chainId: number, call: EvmCall): Promise
{ const { walletClient } = this.getClients(chainId); return walletClient.sendTransaction({ account: walletClient.account!, @@ -459,12 +459,17 @@ export class PrivyAlchemyEvmProviderAdapter implements IEvmProviderAdapter { to: call.to, data: call.data, value: call.value, + // Forward an explicit gas limit when the caller supplies one. + // Avoids the bundler's default estimate running too tight on + // complex routes (e.g. LiFi multi-hop with FeeCollector + + // DEX legs), which we've seen revert at ~95% gas utilization. + ...(call.gas !== undefined ? { gas: call.gas } : {}), }); } async sendCalls( chainId: number, - _calls: Call[] + _calls: EvmCall[] ): Promise
{ const { smartWalletClient } = this.getClients(chainId); const suffix = this.builderCodeSuffix; @@ -477,6 +482,10 @@ export class PrivyAlchemyEvmProviderAdapter implements IEvmProviderAdapter { ? appendBuilderCodeData(call.data ?? "0x", suffix) : call.data ?? "0x", ...(value !== 0n ? { value } : {}), + // Per-call gas hint, forwarded to the bundler so it doesn't + // underestimate. Best-effort: if the smart wallet client + // ignores the field, behavior is unchanged. + ...(call.gas !== undefined ? { gas: call.gas } : {}), }; }), capabilities: { diff --git a/src/providers/evm/viemProviderAdapter.ts b/src/providers/evm/viemProviderAdapter.ts index db52e04..0ef55ee 100644 --- a/src/providers/evm/viemProviderAdapter.ts +++ b/src/providers/evm/viemProviderAdapter.ts @@ -1,7 +1,8 @@ -import type { Address, Call, Log, TransactionReceipt } from "viem"; +import type { Address, Log, TransactionReceipt } from "viem"; import { createEvmNetworkContext } from "../../core/chains.js"; import type { + EvmCall, GetLogsParams, IEvmProviderAdapter, ReadContractParams, @@ -26,11 +27,11 @@ export class ViemProviderAdapter implements IEvmProviderAdapter { return createEvmNetworkContext(chainId); } - async sendTransaction(_chainId: number, _call: Call): Promise
{ + async sendTransaction(_chainId: number, _call: EvmCall): Promise
{ throw new Error("sendTransaction() not implemented. Override in subclass."); } - async sendCalls(_chainId: number, _calls: Call[]): Promise
{ + async sendCalls(_chainId: number, _calls: EvmCall[]): Promise
{ throw new Error("sendCalls() not implemented. Override in subclass."); } diff --git a/src/providers/types.ts b/src/providers/types.ts index cba4c49..0d54842 100644 --- a/src/providers/types.ts +++ b/src/providers/types.ts @@ -2,6 +2,19 @@ import type { Address, Call, Log, TransactionReceipt } from "viem"; import type { NetworkContext, SolanaCluster } from "../core/chains.js"; +/** + * Call shape accepted by sendTransaction / sendCalls. Extends viem's Call + * with an optional `gas` field so callers can override the wallet's + * gas-limit estimate (useful when an aggregator like LiFi already + * supplies a padded recommended gas limit and the bundler's default + * estimate runs too tight). + * + * Pass-through is best-effort: implementations forward `gas` to the + * underlying viem/Alchemy client. If a particular backend ignores it, + * the bundler falls back to its own estimate (current behaviour). + */ +export type EvmCall = Call; + export type SolanaInstructionLike = { programId: string; keys: Array<{ pubkey: string; isSigner: boolean; isWritable: boolean }>; @@ -31,8 +44,8 @@ export type GetLogsParams = { export interface IEvmProviderAdapter extends IProviderAdapter { getAddress(): Promise
; - sendTransaction(chainId: number, call: Call): Promise
; - sendCalls(chainId: number, calls: Call[]): Promise
; + sendTransaction(chainId: number, call: EvmCall): Promise
; + sendCalls(chainId: number, calls: EvmCall[]): Promise
; getTransactionReceipt( chainId: number, hash: Address