@@ -12,7 +12,7 @@ import {
1212 type Hex ,
1313} from 'viem'
1414import { privateKeyToAccount } from 'viem/accounts'
15- import { Abis , Addresses , Transaction } from 'viem/tempo'
15+ import { Abis , Addresses , Chain as TempoChain , Transaction } from 'viem/tempo'
1616import { describe , expect , test } from 'vp/test'
1717
1818import { VerificationFailedError } from '../../../Errors.js'
@@ -57,7 +57,7 @@ function createMockClient(
5757) {
5858 return createClient ( {
5959 account : feePayer ,
60- chain : { id : chainId } as never ,
60+ chain : TempoChain . testnet ,
6161 transport : custom ( {
6262 async request ( args ) {
6363 parameters . rpcMethods ?. push ( args . method )
@@ -168,13 +168,14 @@ async function createSerializedTransaction(parameters: {
168168 data ?: `0x${string } ` | undefined
169169 value ?: bigint | undefined
170170 } [ ]
171+ feeToken ?: Address | false | undefined
171172 gas ?: bigint | undefined
172173 signed ?: boolean | undefined
173174} ) {
174175 return ( await Transaction . serialize ( {
175176 chainId,
176177 calls : parameters . calls ,
177- feeToken : descriptor . token ,
178+ ... ( parameters . feeToken === false ? { } : { feeToken : parameters . feeToken ?? descriptor . token } ) ,
178179 nonce : 0 ,
179180 ...( parameters . gas !== undefined ? { gas : parameters . gas } : { } ) ,
180181 ...( parameters . signed
@@ -222,6 +223,7 @@ function autoSwapCalls(amountOut: bigint = deposit) {
222223async function createOpenTransaction (
223224 parameters : {
224225 authorizedSigner ?: `0x${string } ` | undefined
226+ feeToken ?: Address | false | undefined
225227 gas ?: bigint | undefined
226228 operator ?: `0x${string } ` | undefined
227229 payee ?: `0x${string } ` | undefined
@@ -251,6 +253,7 @@ async function createOpenTransaction(
251253 } )
252254 return createSerializedTransaction ( {
253255 calls : [ ...( parameters . prefixCalls ?? [ ] ) , { to : parameters . to ?? tip20ChannelEscrow , data } ] ,
256+ feeToken : parameters . feeToken ,
254257 gas : parameters . gas ,
255258 signed : parameters . signed ,
256259 } )
@@ -260,6 +263,7 @@ async function createTopUpTransaction(
260263 parameters : {
261264 additionalDeposit ?: bigint | undefined
262265 descriptor_ ?: Channel . ChannelDescriptor | undefined
266+ feeToken ?: Address | false | undefined
263267 gas ?: bigint | undefined
264268 signed ?: boolean | undefined
265269 to ?: `0x${string } ` | undefined
@@ -282,6 +286,7 @@ async function createTopUpTransaction(
282286 } )
283287 return createSerializedTransaction ( {
284288 calls : [ ...( parameters . prefixCalls ?? [ ] ) , { to : parameters . to ?? tip20ChannelEscrow , data } ] ,
289+ feeToken : parameters . feeToken ,
285290 gas : parameters . gas ,
286291 signed : parameters . signed ,
287292 } )
@@ -827,6 +832,57 @@ describe('precompile broadcastOpenTransaction', () => {
827832 ) . toHaveLength ( 2 )
828833 } )
829834
835+ test ( 'fee-payer completes open with an explicit allowed fee token' , async ( ) => {
836+ let broadcast : Hex | undefined
837+ const serializedTransaction = await createOpenTransaction ( {
838+ feeToken : false ,
839+ gas : 100_000n ,
840+ signed : true ,
841+ } )
842+ const transaction = Transaction . deserialize (
843+ serializedTransaction as Transaction . TransactionSerializedTempo ,
844+ )
845+ const payer = transaction . from !
846+ const expiringNonceHash = Channel . computeExpiringNonceHash (
847+ Channel . transactionForExpiringNonceHash ( { feePayer, transaction } ) ,
848+ { sender : payer } ,
849+ )
850+ const expectedDescriptor = { ...descriptor , payer, expiringNonceHash }
851+ const channelId = Channel . computeId ( {
852+ ...expectedDescriptor ,
853+ chainId,
854+ escrow : tip20ChannelEscrow ,
855+ } )
856+ const state = { settled : 0n , deposit, closeRequestedAt : 0 }
857+
858+ await Chain . broadcastOpenTransaction ( {
859+ allowedFeeTokens : [ sourceToken ] ,
860+ chainId,
861+ client : createMockClient ( {
862+ channel : { descriptor : expectedDescriptor , state } ,
863+ onRequest ( method , params ) {
864+ if ( method === 'eth_sendRawTransactionSync' ) broadcast = ( params as readonly [ Hex ] ) [ 0 ]
865+ } ,
866+ receipt : receipt ( [ openedLog ( { channelId, expiringNonceHash } ) ] ) ,
867+ } ) ,
868+ escrowContract : tip20ChannelEscrow ,
869+ expectedAuthorizedSigner : descriptor . authorizedSigner ,
870+ expectedChannelId : channelId ,
871+ expectedCurrency : descriptor . token ,
872+ expectedExpiringNonceHash : expiringNonceHash ,
873+ expectedOperator : descriptor . operator ,
874+ expectedPayee : descriptor . payee ,
875+ expectedPayer : payer ,
876+ feePayer,
877+ serializedTransaction,
878+ } )
879+
880+ expect ( broadcast ) . toBeDefined ( )
881+ expect (
882+ Transaction . deserialize ( broadcast as Transaction . TransactionSerializedTempo ) . feeToken ,
883+ ) . toBe ( sourceToken )
884+ } )
885+
830886 test ( 'hosted fee-payer relays a sender-signed open without local co-signing' , async ( ) => {
831887 const rpcMethods : string [ ] = [ ]
832888 const serializedTransaction = await createOpenTransaction ( { signed : true } )
@@ -1195,6 +1251,41 @@ describe('precompile broadcastTopUpTransaction', () => {
11951251 ) . toHaveLength ( 2 )
11961252 } )
11971253
1254+ test ( 'fee-payer completes top-up with an explicit allowed fee token' , async ( ) => {
1255+ let broadcast : Hex | undefined
1256+ const serializedTransaction = await createTopUpTransaction ( {
1257+ feeToken : false ,
1258+ gas : 100_000n ,
1259+ signed : true ,
1260+ } )
1261+ const channelId = Channel . computeId ( { ...descriptor , chainId, escrow : tip20ChannelEscrow } )
1262+ const newDeposit = deposit * 2n
1263+
1264+ await Chain . broadcastTopUpTransaction ( {
1265+ additionalDeposit : deposit ,
1266+ allowedFeeTokens : [ sourceToken ] ,
1267+ chainId,
1268+ client : createMockClient ( {
1269+ channel : { descriptor, state : { settled : 0n , deposit : newDeposit , closeRequestedAt : 0 } } ,
1270+ onRequest ( method , params ) {
1271+ if ( method === 'eth_sendRawTransactionSync' ) broadcast = ( params as readonly [ Hex ] ) [ 0 ]
1272+ } ,
1273+ receipt : receipt ( [ topUpLog ( { channelId, newDeposit } ) ] ) ,
1274+ } ) ,
1275+ descriptor,
1276+ escrowContract : tip20ChannelEscrow ,
1277+ expectedChannelId : channelId ,
1278+ expectedCurrency : descriptor . token ,
1279+ feePayer,
1280+ serializedTransaction,
1281+ } )
1282+
1283+ expect ( broadcast ) . toBeDefined ( )
1284+ expect (
1285+ Transaction . deserialize ( broadcast as Transaction . TransactionSerializedTempo ) . feeToken ,
1286+ ) . toBe ( sourceToken )
1287+ } )
1288+
11981289 test ( 'rejects top-up calldata amount mismatches before broadcasting' , async ( ) => {
11991290 const serializedTransaction = await createTopUpTransaction ( { additionalDeposit : 1n } )
12001291
0 commit comments