@@ -33,6 +33,20 @@ import {
3333import { MulticallHandler } from "../../target/types/multicall_handler" ;
3434import { common } from "./SvmSpoke.common" ;
3535import { FillDataParams , FillDataValues } from "../../src/types/svm" ;
36+ import { getApproveCheckedInstruction , getTransferCheckedInstruction } from "@solana-program/token" ;
37+ import {
38+ AccountRole ,
39+ address ,
40+ appendTransactionMessageInstruction ,
41+ createKeyPairFromBytes ,
42+ createSignerFromKeyPair ,
43+ getProgramDerivedAddress ,
44+ IAccountMeta ,
45+ pipe ,
46+ } from "@solana/kit" ;
47+ import { createDefaultSolanaClient , createDefaultTransaction , signAndSendTransaction } from "./utils" ;
48+ import { FillRelayAsyncInput } from "../../src/svm/clients/SvmSpoke" ;
49+ import { SvmSpokeClient } from "../../src/svm" ;
3650const { provider, connection, program, owner, chainId, seedBalance } = common ;
3751const { initializeState, assertSE } = common ;
3852
@@ -384,4 +398,141 @@ describe("svm_spoke.fill.across_plus", () => {
384398 "Recipient's balance should be increased by the relay amount"
385399 ) ;
386400 } ) ;
401+
402+ describe ( "codama client and solana kit" , ( ) => {
403+ it ( "Forwards tokens to the final recipient within invoked message call using codama client" , async ( ) => {
404+ const iRelayerBal = ( await getAccount ( connection , relayerATA ) ) . amount ;
405+
406+ // Construct ix to transfer all tokens from handler to the final recipient.
407+ const transferIx = createTransferCheckedInstruction (
408+ handlerATA ,
409+ mint ,
410+ finalRecipientATA ,
411+ handlerSigner ,
412+ relayData . outputAmount ,
413+ mintDecimals
414+ ) ;
415+
416+ const multicallHandlerCoder = new MulticallHandlerCoder ( [ transferIx ] ) ;
417+
418+ const handlerMessage = multicallHandlerCoder . encode ( ) ;
419+
420+ const message = new AcrossPlusMessageCoder ( {
421+ handler : handlerProgram . programId ,
422+ readOnlyLen : multicallHandlerCoder . readOnlyLen ,
423+ valueAmount : new BN ( 0 ) ,
424+ accounts : multicallHandlerCoder . compiledMessage . accountKeys ,
425+ handlerMessage,
426+ } ) ;
427+
428+ const encodedMessage = message . encode ( ) ;
429+
430+ // Update relay data with the encoded message.
431+ const newRelayData = { ...relayData , message : encodedMessage } ;
432+ updateRelayData ( newRelayData ) ;
433+
434+ const rpcClient = createDefaultSolanaClient ( ) ;
435+ const signer = await createSignerFromKeyPair ( await createKeyPairFromBytes ( relayer . secretKey ) ) ;
436+
437+ const [ eventAuthority ] = await getProgramDerivedAddress ( {
438+ programAddress : address ( program . programId . toString ( ) ) ,
439+ seeds : [ "__event_authority" ] ,
440+ } ) ;
441+ const relayHash = Array . from ( calculateRelayHashUint8Array ( relayData , chainId ) ) ;
442+
443+ const formattedAccounts = {
444+ state : address ( accounts . state . toString ( ) ) ,
445+ instructionParams : address ( program . programId . toString ( ) ) ,
446+ mint : address ( mint . toString ( ) ) ,
447+ relayerTokenAccount : address ( relayerATA . toString ( ) ) ,
448+ recipientTokenAccount : address ( handlerATA . toString ( ) ) ,
449+ fillStatus : address ( accounts . fillStatus . toString ( ) ) ,
450+ tokenProgram : address ( TOKEN_PROGRAM_ID . toString ( ) ) ,
451+ associatedTokenProgram : address ( ASSOCIATED_TOKEN_PROGRAM_ID . toString ( ) ) ,
452+ systemProgram : address ( anchor . web3 . SystemProgram . programId . toString ( ) ) ,
453+ program : address ( program . programId . toString ( ) ) ,
454+ eventAuthority,
455+ signer,
456+ } ;
457+
458+ const formattedRelayData = {
459+ relayHash : new Uint8Array ( relayHash ) ,
460+ relayData : {
461+ depositor : address ( relayData . depositor . toString ( ) ) ,
462+ recipient : address ( relayData . recipient . toString ( ) ) ,
463+ exclusiveRelayer : address ( relayData . exclusiveRelayer . toString ( ) ) ,
464+ inputToken : address ( relayData . inputToken . toString ( ) ) ,
465+ outputToken : address ( relayData . outputToken . toString ( ) ) ,
466+ inputAmount : relayData . inputAmount . toNumber ( ) ,
467+ outputAmount : relayData . outputAmount . toNumber ( ) ,
468+ originChainId : relayData . originChainId . toNumber ( ) ,
469+ depositId : new Uint8Array ( relayData . depositId ) ,
470+ fillDeadline : relayData . fillDeadline ,
471+ exclusivityDeadline : relayData . exclusivityDeadline ,
472+ message : encodedMessage ,
473+ } ,
474+ repaymentChainId : 1 ,
475+ repaymentAddress : address ( relayer . publicKey . toString ( ) ) ,
476+ } ;
477+
478+ const approveIx = getApproveCheckedInstruction ( {
479+ source : address ( accounts . relayerTokenAccount . toString ( ) ) ,
480+ mint : address ( accounts . mint . toString ( ) ) ,
481+ delegate : address ( accounts . state . toString ( ) ) ,
482+ owner : address ( accounts . signer . toString ( ) ) ,
483+ amount : BigInt ( relayData . outputAmount . toString ( ) ) ,
484+ decimals : mintDecimals ,
485+ } ) ;
486+
487+ const fillRelayInput : FillRelayAsyncInput = {
488+ ...formattedRelayData ,
489+ ...formattedAccounts ,
490+ } ;
491+
492+ const fillRelayIxData = await SvmSpokeClient . getFillRelayInstructionAsync ( fillRelayInput ) ;
493+ const fillRelayIx = {
494+ ...fillRelayIxData ,
495+ accounts : fillRelayIxData . accounts . map ( ( account ) =>
496+ account . address === program . programId . toString ( ) ||
497+ account . address === TOKEN_PROGRAM_ID . toString ( ) ||
498+ account . address === ASSOCIATED_TOKEN_PROGRAM_ID . toString ( )
499+ ? { ...account , role : AccountRole . READONLY }
500+ : account
501+ ) ,
502+ } ;
503+
504+ const _remainingAccounts : AccountMeta [ ] = [
505+ { pubkey : handlerProgram . programId , isSigner : false , isWritable : false } ,
506+ ...multicallHandlerCoder . compiledKeyMetas ,
507+ ] ;
508+ const remainingAccounts : IAccountMeta < string > [ ] = _remainingAccounts . map ( ( account ) => ( {
509+ address : address ( account . pubkey . toString ( ) ) ,
510+ role : account . isWritable ? AccountRole . WRITABLE : AccountRole . READONLY ,
511+ } ) ) ;
512+ ( fillRelayIx . accounts as IAccountMeta < string > [ ] ) . push ( ...remainingAccounts ) ;
513+
514+ await pipe (
515+ await createDefaultTransaction ( rpcClient , signer ) ,
516+ ( tx ) => appendTransactionMessageInstruction ( approveIx , tx ) ,
517+ ( tx ) => appendTransactionMessageInstruction ( fillRelayIx , tx ) ,
518+ ( tx ) => signAndSendTransaction ( rpcClient , tx )
519+ ) ;
520+
521+ // Verify relayer's balance after the fill
522+ const fRelayerBal = ( await getAccount ( connection , relayerATA ) ) . amount ;
523+ assertSE (
524+ fRelayerBal ,
525+ iRelayerBal - BigInt ( relayAmount ) ,
526+ "Relayer's balance should be reduced by the relay amount"
527+ ) ;
528+
529+ // Verify final recipient's balance after the fill
530+ const finalRecipientAccount = await getAccount ( connection , finalRecipientATA ) ;
531+ assertSE (
532+ finalRecipientAccount . amount ,
533+ relayAmount ,
534+ "Final recipient's balance should be increased by the relay amount"
535+ ) ;
536+ } ) ;
537+ } ) ;
387538} ) ;
0 commit comments