@@ -7,8 +7,11 @@ import {
77 erc20Abi ,
88 http ,
99 PublicClient ,
10+ SimulateContractReturnType ,
11+ TransactionReceipt ,
1012 Transport ,
1113 zeroAddress ,
14+ zeroHash ,
1215} from 'viem' ;
1316import {
1417 bob ,
@@ -25,7 +28,13 @@ import {
2528 optimism ,
2629 sei ,
2730} from 'viem/chains' ;
28- import { BitcoinSigner , GetQuoteParams , LayerZeroMessageWallet } from '../src/gateway/types' ;
31+ import {
32+ BitcoinSigner ,
33+ ExecuteQuoteParams ,
34+ GatewayOrderType ,
35+ GetQuoteParams ,
36+ LayerZeroMessageWallet ,
37+ } from '../src/gateway/types' ;
2938import * as btc from '@scure/btc-signer' ;
3039import { base64 } from '@scure/base' ;
3140import { mnemonicToSeedSync } from 'bip39' ;
@@ -566,6 +575,155 @@ describe('LayerZero Tests', () => {
566575 ) . rejects . toThrow ( 'WBTC OFT not found for chain: 40184' ) ;
567576 } ) ;
568577 } ) ;
578+
579+ it ( 'should execute approve if approval required' , async ( ) => {
580+ const client = new LayerZeroGatewayClient ( ) ;
581+
582+ const mockQuote : ExecuteQuoteParams = {
583+ type : GatewayOrderType . CrossChainSwap ,
584+ finalOutputSats : 100000 ,
585+ finalFeeSats : 0 ,
586+ params : {
587+ fromChain : 'ethereum' ,
588+ fromToken : '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' ,
589+ toChain : 'bob' ,
590+ toToken : '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' ,
591+ fromUserAddress : '0x5AC6f7ee4D571872333845e8Abc0bDef200B4Df6' ,
592+ toUserAddress : '0x5AC6f7ee4D571872333845e8Abc0bDef200B4Df6' ,
593+ amount : '100000' ,
594+ feeRate : 1 ,
595+ } ,
596+ data : {
597+ oftAddress : '0x0555e30da8f98308edb960aa94c0db47230d2b9c' ,
598+ destinationEid : 30279 ,
599+ sourceEid : 30101 ,
600+ feeBreakdown : {
601+ nativeFee : BigInt ( '245222054031895' ) ,
602+ lzTokenFee : BigInt ( '0' ) ,
603+ } ,
604+ } ,
605+ } ;
606+
607+ const publicClient = createPublicClient ( {
608+ chain : mainnet ,
609+ transport : http ( ) ,
610+ } ) ;
611+
612+ const publicClientReadContractSpy = vi
613+ . spyOn ( publicClient , 'readContract' )
614+ . mockImplementationOnce ( ( ) => Promise . resolve ( true ) )
615+ . mockImplementationOnce ( ( ) => Promise . resolve ( 10n ) ) ;
616+
617+ const publicClientWaitForTransactionReceiptSpy = vi
618+ . spyOn ( publicClient , 'waitForTransactionReceipt' )
619+ . mockImplementation ( ( ) => {
620+ const txReceipt : TransactionReceipt = { } as TransactionReceipt ;
621+ return Promise . resolve ( txReceipt ) ;
622+ } ) ;
623+
624+ const publicClientSimulateContractSpy = vi . spyOn ( publicClient , 'simulateContract' ) . mockImplementation ( ( ) => {
625+ const simulateContractReturnType : Awaited < ReturnType < typeof publicClient . simulateContract > > = { } as Awaited <
626+ ReturnType < typeof publicClient . simulateContract >
627+ > ;
628+ return Promise . resolve ( simulateContractReturnType ) ;
629+ } ) ;
630+
631+ const walletClient = createWalletClient ( {
632+ chain : mainnet ,
633+ transport : http ( ) ,
634+ account : privateKeyToAccount ( process . env . PRIVATE_KEY as Hex ) ,
635+ } ) ;
636+
637+ const walletClientWriteContractSpy = vi . spyOn ( walletClient , 'writeContract' ) . mockImplementation ( ( ) => {
638+ const writeContractReturnType = { } as Awaited < ReturnType < typeof walletClient . writeContract > > ;
639+ return Promise . resolve ( writeContractReturnType ) ;
640+ } ) ;
641+
642+ const txHash = await client . executeQuote ( {
643+ quote : mockQuote ,
644+ walletClient,
645+ publicClient : publicClient as PublicClient < Transport > ,
646+ } ) ;
647+
648+ expect ( txHash ) . toBeDefined ( ) ;
649+ expect ( publicClientReadContractSpy ) . toHaveBeenCalledTimes ( 2 ) ;
650+ expect ( publicClientWaitForTransactionReceiptSpy ) . toHaveBeenCalledTimes ( 2 ) ;
651+ expect ( publicClientSimulateContractSpy ) . toHaveBeenCalledTimes ( 2 ) ;
652+ expect ( walletClientWriteContractSpy ) . toHaveBeenCalledTimes ( 2 ) ;
653+ } , 120000 ) ;
654+
655+ it ( 'should skip approve step' , async ( ) => {
656+ const client = new LayerZeroGatewayClient ( ) ;
657+
658+ const mockQuote : ExecuteQuoteParams = {
659+ type : GatewayOrderType . CrossChainSwap ,
660+ finalOutputSats : 100000 ,
661+ finalFeeSats : 0 ,
662+ params : {
663+ fromChain : 'bsc' ,
664+ fromToken : '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' ,
665+ toChain : 'bob' ,
666+ toToken : '0x0555E30da8f98308EdB960aa94C0Db47230d2B9c' ,
667+ fromUserAddress : '0x5AC6f7ee4D571872333845e8Abc0bDef200B4Df6' ,
668+ toUserAddress : '0x5AC6f7ee4D571872333845e8Abc0bDef200B4Df6' ,
669+ amount : '100000' ,
670+ feeRate : 1 ,
671+ } ,
672+ data : {
673+ oftAddress : '0x0555e30da8f98308edb960aa94c0db47230d2b9c' ,
674+ destinationEid : 30279 ,
675+ sourceEid : 30102 ,
676+ feeBreakdown : {
677+ nativeFee : BigInt ( '245222054031895' ) ,
678+ lzTokenFee : BigInt ( '0' ) ,
679+ } ,
680+ } ,
681+ } ;
682+
683+ const publicClient = createPublicClient ( {
684+ chain : bsc ,
685+ transport : http ( ) ,
686+ } ) ;
687+
688+ const publicClientReadContractSpy = vi . spyOn ( publicClient , 'readContract' ) ;
689+
690+ const publicClientWaitForTransactionReceiptSpy = vi
691+ . spyOn ( publicClient , 'waitForTransactionReceipt' )
692+ . mockImplementation ( ( ) => {
693+ const txReceipt : TransactionReceipt = { } as TransactionReceipt ;
694+ return Promise . resolve ( txReceipt ) ;
695+ } ) ;
696+
697+ const publicClientSimulateContractSpy = vi . spyOn ( publicClient , 'simulateContract' ) . mockImplementation ( ( ) => {
698+ const simulateContractReturnType : Awaited < ReturnType < typeof publicClient . simulateContract > > = { } as Awaited <
699+ ReturnType < typeof publicClient . simulateContract >
700+ > ;
701+ return Promise . resolve ( simulateContractReturnType ) ;
702+ } ) ;
703+
704+ const walletClient = createWalletClient ( {
705+ chain : bsc ,
706+ transport : http ( ) ,
707+ account : privateKeyToAccount ( process . env . PRIVATE_KEY as Hex ) ,
708+ } ) ;
709+
710+ const walletClientWriteContractSpy = vi . spyOn ( walletClient , 'writeContract' ) . mockImplementation ( ( ) => {
711+ const writeContractReturnType = { } as Awaited < ReturnType < typeof walletClient . writeContract > > ;
712+ return Promise . resolve ( writeContractReturnType ) ;
713+ } ) ;
714+
715+ const txHash = await client . executeQuote ( {
716+ quote : mockQuote ,
717+ walletClient,
718+ publicClient : publicClient as PublicClient < Transport > ,
719+ } ) ;
720+
721+ expect ( txHash ) . toBeDefined ( ) ;
722+ expect ( publicClientReadContractSpy ) . toHaveBeenCalledTimes ( 0 ) ;
723+ expect ( publicClientWaitForTransactionReceiptSpy ) . toHaveBeenCalledTimes ( 1 ) ;
724+ expect ( publicClientSimulateContractSpy ) . toHaveBeenCalledTimes ( 1 ) ;
725+ expect ( walletClientWriteContractSpy ) . toHaveBeenCalledTimes ( 1 ) ;
726+ } , 120000 ) ;
569727} ) ;
570728
571729/**
@@ -633,7 +791,7 @@ class ScureBitcoinSigner implements BitcoinSigner {
633791class TestLayerZeroGatewayClient extends ( await import ( '../src/gateway/layerzero' ) ) . LayerZeroGatewayClient {
634792 _publicClient : any ;
635793 constructor ( chainId : number , l0Client : any , publicClient : any ) {
636- super ( chainId ) ;
794+ super ( ) ;
637795 // @ts -ignore
638796 this . l0Client = l0Client ;
639797 this . _publicClient = publicClient ;
0 commit comments