From 2ecf3f63ff7c8dc1632818101c130a2e8f50f83c Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:05:17 +0000 Subject: [PATCH] fix(session): use payee as sender in settleOnChain, not fee payer The escrow contract's settle() requires msg.sender == payee. When a feePayer was provided, sendFeePayerTx set the fee payer as both sender and gas sponsor, causing every settlement to revert with NotPayee(). Add an account parameter to settleOnChain and settle() so the payee signs as the tx sender while the fee payer only sponsors gas. Co-Authored-By: grandizzy <38490174+grandizzy@users.noreply.github.com> --- src/tempo/server/Session.ts | 2 ++ src/tempo/session/Chain.ts | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/tempo/server/Session.ts b/src/tempo/server/Session.ts index ea4fb7e3..7aad2be7 100644 --- a/src/tempo/server/Session.ts +++ b/src/tempo/server/Session.ts @@ -341,6 +341,7 @@ export async function settle( options?: { escrowContract?: Address | undefined feePayer?: viem_Account | undefined + account?: viem_Account | undefined }, ): Promise { const channel = await store.getChannel(channelId) @@ -359,6 +360,7 @@ export async function settle( resolvedEscrow, channel.highestVoucher, options?.feePayer, + options?.account, ) await store.updateChannel(channelId, (current) => { diff --git a/src/tempo/session/Chain.ts b/src/tempo/session/Chain.ts index de92db03..ca2f2fa5 100644 --- a/src/tempo/session/Chain.ts +++ b/src/tempo/session/Chain.ts @@ -101,15 +101,44 @@ export async function settleOnChain( escrowContract: Address, voucher: SignedVoucher, feePayer?: Account | undefined, + account?: Account | undefined, ): Promise { assertUint128(voucher.cumulativeAmount) const args = [voucher.channelId, voucher.cumulativeAmount, voucher.signature] as const + const resolved = account ?? client.account + if (!resolved) + throw new Error( + 'Cannot settle channel: no account available. Pass an `account` to tempo.settle(), or provide a `getClient` that returns an account-bearing client.', + ) if (feePayer) { const data = encodeFunctionData({ abi: escrowAbi, functionName: 'settle', args }) - return sendFeePayerTx(client, feePayer, escrowContract, data, 'settle') + const chainId = client.chain?.id + const feeToken = chainId + ? defaults.currency[chainId as keyof typeof defaults.currency] + : undefined + const prepared = await prepareTransactionRequest(client, { + account: resolved, + calls: [{ to: escrowContract, data }], + feePayer: true, + ...(feeToken ? { feeToken } : {}), + } as never) + const serialized = (await signTransaction(client, { + ...prepared, + account: resolved, + feePayer, + } as never)) as Hex + const receipt = await sendRawTransactionSync(client, { + serializedTransaction: serialized as Transaction.TransactionSerializedTempo, + }) + if (receipt.status !== 'success') { + throw new VerificationFailedError({ + reason: `settle transaction reverted: ${receipt.transactionHash}`, + }) + } + return receipt.transactionHash } return writeContract(client, { - account: client.account!, + account: resolved, chain: client.chain, address: escrowContract, abi: escrowAbi,