1- import axios from 'axios' ;
2- import { createPublicClient , createWalletClient , http } from 'viem' ;
1+ import { createWalletClient , createPublicClient , http , type TypedDataDefinition } from 'viem' ;
32import { privateKeyToAccount } from 'viem/accounts' ;
4- import { mainnet } from 'viem/chains' ;
3+ import { optimism } from 'viem/chains' ;
54import * as dotenv from 'dotenv' ;
65dotenv . config ( ) ;
76
8- // Replace with your actual private key - NEVER hardcode in production code
9- const PRIVATE_KEY = process . env . EVM_PRIVATE_KEY as `0x${string } `
7+ const API = 'https://stargate.finance/api/unstable' ;
8+ const API_KEY = process . env . STARGATE_API_KEY ! ;
9+ const PRIVATE_KEY = process . env . EVM_PRIVATE_KEY as `0x${string } `;
1010const account = privateKeyToAccount ( PRIVATE_KEY ) ;
11+ const wallet = createWalletClient ( { account, chain : optimism , transport : http ( ) } ) ;
12+ const client = createPublicClient ( { chain : optimism , transport : http ( ) } ) ;
1113
12- // Initialize clients
13- const ethereumClient = createPublicClient ( {
14- chain : mainnet ,
15- transport : http ( )
16- } ) ;
17-
18- const walletClient = createWalletClient ( {
19- account,
20- chain : mainnet ,
21- transport : http ( )
22- } ) ;
23-
24- async function fetchStargateRoutes ( ) {
25- try {
26- // Fetching route for USDC transfer from Ethereum to Polygon - https://docs.stargate.finance
27- const response = await axios . get ( 'https://stargate.finance/api/v1/quotes' , {
28- params : {
29- srcToken : '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' , // USDC on Ethereum
30- dstToken : '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359' , // USDC on Polygon
31- srcAddress : '0x0C0d18aa99B02946C70EAC6d47b8009b993c9BfF' ,
32- dstAddress : '0x0C0d18aa99B02946C70EAC6d47b8009b993c9BfF' ,
33- srcChainKey : 'ethereum' , // All chainKeys - https://stargate.finance/api/v1/chains
34- dstChainKey : 'polygon' ,
35- srcAmount : '1000000' , // 1 USDC (6 decimals)
36- dstAmountMin : '900000' // Amount to receive deducted by Stargate fees (max 0.15%)
37- }
38- } ) ;
39-
40- console . log ( 'Stargate quotes data:' , response . data ) ;
41- return response . data ;
42- } catch ( error ) {
43- if ( axios . isAxiosError ( error ) ) {
44- console . error ( 'Axios error:' , error . message ) ;
45- if ( error . response ) {
46- console . error ( 'Response data:' , error . response . data ) ;
47- }
48- } else {
49- console . error ( 'Unexpected error:' , error ) ;
50- }
51- throw error ;
52- }
14+ type AmountType = 'EXACT_SRC_AMOUNT' ;
15+ type FeeTolerance = { type : 'PERCENT' ; amount ?: number } ;
16+
17+ type GetQuotesInput = {
18+ srcTokenAddress : string ;
19+ dstTokenAddress : string ;
20+ srcChainKey : string ;
21+ dstChainKey : string ;
22+ amount : string | bigint ;
23+ srcWalletAddress : string ;
24+ dstWalletAddress : string ;
25+ options : {
26+ amountType ?: AmountType ;
27+ feeTolerance ?: FeeTolerance ;
28+ dstNativeDropAmount ?: number | bigint ;
29+ } ;
30+ } ;
31+
32+ type QuoteHead = { id : string } ;
33+ type GetQuotesResult = { quotes : QuoteHead [ ] } ;
34+
35+ type EvmEncodedTx = {
36+ chainId : number ;
37+ to : `0x${string } `;
38+ data ?: `0x${string } `;
39+ value ?: string | bigint ;
40+ from ?: `0x${string } `;
41+ gasLimit ?: string | bigint ;
42+ } ;
43+
44+ type TransactionStep = {
45+ type : 'TRANSACTION' ;
46+ chainKey : string ;
47+ chainType : 'EVM' ;
48+ description : string ;
49+ signerAddress : `0x${string } `;
50+ transaction : { encoded : EvmEncodedTx } ;
51+ } ;
52+
53+ type SignatureStep = {
54+ type : 'SIGNATURE' ;
55+ description : string ;
56+ chainKey ?: string ;
57+ signerAddress : `0x${string } `;
58+ signature : { type : 'EIP712' ; typedData : TypedDataDefinition } ;
59+ } ;
60+
61+ type UserStep = TransactionStep | SignatureStep ;
62+
63+ type BuildUserStepsResult = {
64+ body : { userSteps : UserStep [ ] } ;
65+ } ;
66+
67+ async function postJson < T > ( path : string , body : unknown ) : Promise < T > {
68+ const res = await fetch ( `${ API } ${ path } ` , {
69+ method : 'POST' ,
70+ headers : {
71+ 'x-api-key' : API_KEY ,
72+ 'Content-Type' : 'application/json' ,
73+ } ,
74+ body : JSON . stringify ( body ) ,
75+ } ) ;
76+ if ( ! res . ok ) throw new Error ( await res . text ( ) ) ;
77+ return res . json ( ) as Promise < T > ;
5378}
5479
55- async function executeStargateTransaction ( ) {
56- try {
57- // 1. Fetch quotes data
58- const routesData = await fetchStargateRoutes ( ) ;
59-
60- // 2. Get the first route (or implement your own selection logic)
61- // Here you can select from all the supported routes including StargateV2:Taxi, StargateBus or CCTP
62- // Supported routes are different for each token
63- // Each route contains all transactions required to execute the transfer given in executable order
64- const selectedRoute = routesData . quotes [ 0 ] ;
65- if ( ! selectedRoute ) {
66- throw new Error ( 'No quotes available' ) ;
67- }
68-
69- console . log ( 'Selected route:' , selectedRoute ) ;
70-
71- // Execute all transactions in the route steps
72- for ( let i = 0 ; i < selectedRoute . steps . length ; i ++ ) {
73- const executableTransaction = selectedRoute . steps [ i ] . transaction ;
74- console . log ( `Executing step ${ i + 1 } /${ selectedRoute . steps . length } :` , executableTransaction ) ;
75-
76- // Create transaction object, only include value if it exists and is not empty
77- const txParams : Record < string , unknown > = {
78- account,
79- to : executableTransaction . to ,
80- data : executableTransaction . data ,
81- } ;
82-
83- // Only add value if it exists and is not empty
84- if ( executableTransaction . value && executableTransaction . value !== '0' ) {
85- txParams . value = BigInt ( executableTransaction . value ) ;
86- }
87-
88- // Execute the transaction
89- const txHash = await walletClient . sendTransaction ( txParams ) ;
90- console . log ( `Step ${ i + 1 } transaction hash: ${ txHash } ` ) ;
91-
92- // Wait for transaction to be mined
93- const receipt = await ethereumClient . waitForTransactionReceipt ( { hash : txHash } ) ;
94- console . log ( `Step ${ i + 1 } transaction confirmed:` , receipt ) ;
80+ async function fetchQuotes ( ) : Promise < GetQuotesResult > {
81+ const payload : GetQuotesInput = {
82+ srcTokenAddress : '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' ,
83+ dstTokenAddress : '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' ,
84+ srcChainKey : 'optimism' ,
85+ dstChainKey : 'arbitrum' ,
86+ amount : '1000000000000000' ,
87+ srcWalletAddress : account . address ,
88+ dstWalletAddress : account . address ,
89+ options : {
90+ amountType : 'EXACT_SRC_AMOUNT' ,
91+ feeTolerance : { type : 'PERCENT' , amount : 20 } ,
92+ dstNativeDropAmount : 0 ,
93+ } ,
94+ } ;
95+ return postJson < GetQuotesResult > ( '/quotes' , payload ) ;
96+ }
97+
98+ async function buildUserSteps ( quoteId : string ) {
99+ return postJson < BuildUserStepsResult > ( '/build-user-steps' , { quoteId } ) ;
100+ }
101+
102+ async function submitSignature ( quoteId : string , signatures : string [ ] ) {
103+ await postJson < Record < string , never > > ( '/submit-signature' , { quoteId, signatures } ) ;
104+ }
105+
106+ function toBigIntOrUndefined ( v : string | bigint | undefined ) : bigint | undefined {
107+ if ( v === undefined ) return undefined ;
108+ return typeof v === 'string' ? BigInt ( v ) : v ;
109+ }
110+
111+ async function executeEvmTransaction ( step : TransactionStep ) {
112+ const tx = step . transaction . encoded ;
113+
114+ const hash = await wallet . sendTransaction ( {
115+ account,
116+ to : tx . to ,
117+ data : tx . data ,
118+ value : toBigIntOrUndefined ( tx . value ) ?? 0n ,
119+ } ) ;
120+
121+ await client . waitForTransactionReceipt ( { hash } ) ;
122+ }
123+
124+ async function signEip712 ( step : SignatureStep ) {
125+ const typed = step . signature . typedData ;
126+ const signature = await wallet . signTypedData ( typed ) ;
127+ return signature ;
128+ }
129+
130+ async function run ( ) {
131+ const quotes = await fetchQuotes ( ) ;
132+ const quote = quotes . quotes ?. [ 0 ] ;
133+ if ( ! quote ) throw new Error ( 'No quote' ) ;
134+
135+ const { body } = await buildUserSteps ( quote . id ) ;
136+ for ( const step of body . userSteps ) {
137+ if ( step . type === 'SIGNATURE' ) {
138+ const sig = await signEip712 ( step ) ;
139+ await submitSignature ( quote . id , [ sig ] ) ;
140+ } else if ( step . type === 'TRANSACTION' ) {
141+ await executeEvmTransaction ( step ) ;
95142 }
96-
97- console . log ( 'All steps executed successfully' ) ;
98- return true ;
99- } catch ( error ) {
100- console . error ( 'Error executing Stargate transaction:' , error ) ;
101- throw error ;
102143 }
103144}
104145
105- // Execute the transaction
106- void executeStargateTransaction ( )
107- . then ( ( ) => {
108- console . log ( 'Successfully executed Stargate transaction' ) ;
109- } )
110- . catch ( ( err ) => {
111- console . error ( 'Failed to execute Stargate transaction:' , err ) ;
112- } ) ;
146+ void run ( ) ;
0 commit comments