From 6ce75b8a1cb097910cba683e24bec195d1ab98b8 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Mon, 27 May 2024 19:37:54 +0200 Subject: [PATCH] chore: inline all examples so sequence of execution is easier to follow --- examples/node/examples/bridge.ts | 86 +++-- .../node/examples/klimaRetireExactCarbon.ts | 338 ++++++++++++------ examples/node/examples/multihop.ts | 291 ++++++++++----- examples/node/examples/polynomialDeposit.ts | 252 +++++++++---- .../examples/utils/executeCrossChainQuote.ts | 70 ---- .../node/examples/utils/executeTransaction.ts | 41 --- examples/node/examples/utils/setUpSDK.ts | 72 ---- examples/node/package.json | 5 +- .../read-contract-klima-example.ts | 84 ----- .../read-contract-polynomial-example.ts | 57 --- .../read-contract-viem-example.ts | 71 ---- 11 files changed, 660 insertions(+), 707 deletions(-) delete mode 100644 examples/node/examples/utils/executeCrossChainQuote.ts delete mode 100644 examples/node/examples/utils/executeTransaction.ts delete mode 100644 examples/node/examples/utils/setUpSDK.ts delete mode 100644 examples/node/temp-test-examples/read-contract-klima-example.ts delete mode 100644 examples/node/temp-test-examples/read-contract-polynomial-example.ts delete mode 100644 examples/node/temp-test-examples/read-contract-viem-example.ts diff --git a/examples/node/examples/bridge.ts b/examples/node/examples/bridge.ts index 9f259dc..ad07c7f 100644 --- a/examples/node/examples/bridge.ts +++ b/examples/node/examples/bridge.ts @@ -1,42 +1,74 @@ import * as lifiDataTypes from '@lifi/data-types' -import { executeRoute, getRoutes, ChainId, CoinKey, Execution } from '@lifi/sdk' -import { promptConfirm } from '../helpers/promptConfirm' -import type { PrivateKeyAccount, Chain } from 'viem' +import { + executeRoute, + getRoutes, + ChainId, + CoinKey, + createConfig, + EVM, +} from '@lifi/sdk' +import type { Address, Chain } from 'viem' +import { createWalletClient, http } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' import { mainnet, arbitrum, optimism, polygon } from 'viem/chains' -import { setUpSDK } from './utils/setUpSDK' - -const dataTypes = (lifiDataTypes as any).default +import 'dotenv/config' +import { promptConfirm } from '../helpers/promptConfirm' -// NOTE: we add the wallet address to the route request. -// This means that any route responses will feature that address for -// use in route execution. -// In the example below we are bridging - exchanging USDC on Optimism and USDT tokens on Arbitrum -const getRequestRoute = ({ address }: PrivateKeyAccount) => ({ - toAddress: address, - fromAddress: address, - fromChainId: ChainId.OPT, // Optimism - fromAmount: '100000', // 1 USDT - fromTokenAddress: dataTypes.findDefaultToken(CoinKey.USDC, ChainId.OPT) - .address, - toChainId: ChainId.ARB, // Arbitrum - toTokenAddress: dataTypes.findDefaultToken(CoinKey.USDT, ChainId.ARB).address, - options: { - slippage: 0.03, // = 3% - }, -}) +const { findDefaultToken } = (lifiDataTypes as any).default async function run() { console.info('>> Starting Bridge Demo') console.info('>> Initialize LiFi SDK') - const { account } = setUpSDK({ - initialChain: optimism as Chain, - switchChains: [mainnet, arbitrum, optimism, polygon] as Chain[], + const privateKey = process.env.PRIVATE_KEY as Address + + // NOTE: Here we are using the private key to get the account, + // but you can also use a Mnemonic account - see https://viem.sh/docs/accounts/mnemonic + const account = privateKeyToAccount(privateKey) + + const client = createWalletClient({ + account, + chain: optimism as Chain, + transport: http(), + }) + + const switchChains = [mainnet, arbitrum, optimism, polygon] as Chain[] + + createConfig({ + integrator: 'lifi-sdk-example', + providers: [ + EVM({ + getWalletClient: () => Promise.resolve(client), + switchChain: (chainId) => + Promise.resolve( + createWalletClient({ + account, + chain: switchChains.find((chain) => { + if (chain.id == chainId) { + return chain + } + }) as Chain, + transport: http(), + }) + ), + }), + ], }) console.info('>> Initialized, Requesting route') - const routeRequest = getRequestRoute(account) + const routeRequest = { + toAddress: account.address, + fromAddress: account.address, + fromChainId: ChainId.OPT, // Optimism + fromAmount: '100000', // 1 USDT + fromTokenAddress: findDefaultToken(CoinKey.USDC, ChainId.OPT).address, + toChainId: ChainId.ARB, // Arbitrum + toTokenAddress: findDefaultToken(CoinKey.USDT, ChainId.ARB).address, + options: { + slippage: 0.03, // = 3% + }, + } const routeResponse = await getRoutes(routeRequest) const route = routeResponse.routes[0] diff --git a/examples/node/examples/klimaRetireExactCarbon.ts b/examples/node/examples/klimaRetireExactCarbon.ts index 6a93724..b921128 100644 --- a/examples/node/examples/klimaRetireExactCarbon.ts +++ b/examples/node/examples/klimaRetireExactCarbon.ts @@ -1,146 +1,246 @@ import * as lifiDataTypes from '@lifi/data-types' -import type { ContractCallsQuoteRequest, LiFiStep } from '@lifi/sdk' -import { ChainId, CoinKey, getContractCallsQuote } from '@lifi/sdk' -import type { Chain } from 'viem' +import type { ContractCallsQuoteRequest, StatusResponse } from '@lifi/sdk' +import { + ChainId, + CoinKey, + createConfig, + EVM, + getContractCallsQuote, + getStatus, + getTokenAllowance, + setTokenAllowance, +} from '@lifi/sdk' +import type { Chain, Address, Hash } from 'viem' import { parseAbi, encodeFunctionData, parseUnits, createPublicClient, http, + createWalletClient, + publicActions, } from 'viem' import { mainnet, arbitrum, optimism, polygon } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import 'dotenv/config' import { promptConfirm } from '../helpers/promptConfirm' -import { executeCrossChainQuote } from './utils/executeCrossChainQuote' -import { setUpSDK } from './utils/setUpSDK' -import { WalletClientWithPublicActions } from './types' - -const dataTypes = (lifiDataTypes as any).default - -const USDCe_POL = dataTypes.findDefaultToken(CoinKey.USDCe, ChainId.POL) - -const Base_Carbon_Tonne_POL = '0x2F800Db0fdb5223b3C3f354886d907A671414A7F' - -// https://docs.klimadao.finance/developers/contracts/retirement/v2-diamond/generalized-retirement -const KLIMA_ETHEREUM_CONTRACT_POL = '0x8cE54d9625371fb2a068986d32C85De8E6e995f8' -const KLIMA_ABI = [ - 'function getSourceAmountDefaultRetirement(address,address,uint256) external view returns (uint256 amountIn)', - 'function retireExactCarbonDefault(address, address, uint256, uint256, string, address, string, string, uint8)', -] -const KLIMA_GAS_LIMIT = '1300000' - -const getKlimaQuote = async ( - fromChain: ChainId, - fromToken: string, - client: WalletClientWithPublicActions, - userAddress: string, - retireAmount: string -): Promise => { - const abi = parseAbi(KLIMA_ABI) - - const publicClient = createPublicClient({ - chain: polygon, - transport: http(), - }) - - const sourceAmountDefaultRetirement = await publicClient.readContract({ - address: KLIMA_ETHEREUM_CONTRACT_POL, - abi, - functionName: 'getSourceAmountDefaultRetirement', - args: [ - USDCe_POL.address, // address sourceToken, - Base_Carbon_Tonne_POL, // address poolToken, - retireAmount, // uint256 retireAmount, - ], - }) - - const usdcAmount = parseUnits( - sourceAmountDefaultRetirement.toString(), - USDCe_POL.decimals - ).toString() - - console.log('>> usdcAmount', usdcAmount) - - const retireTxData = encodeFunctionData({ - abi, - functionName: 'retireExactCarbonDefault', - args: [ - USDCe_POL.address, // address sourceToken, - Base_Carbon_Tonne_POL, // address poolToken, - usdcAmount, // uint256 maxAmountIn, - retireAmount, // uint256 retireAmount, - 'LI.FI', // string memory retiringEntityString, - '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0', // address beneficiaryAddress, - 'LI.FI', // string memory beneficiaryString, - 'Cross Chain Contract Calls', // string memory retirementMessage, - 0, // LibTransfer.From fromMode], - ], - }) - - // quote - const quoteRequest: ContractCallsQuoteRequest = { - fromChain, - fromToken, - fromAddress: userAddress, - toChain: ChainId.POL, - toToken: USDCe_POL.address, - toAmount: usdcAmount, - contractCalls: [ - { - fromAmount: usdcAmount, - fromTokenAddress: USDCe_POL.address, - toContractAddress: KLIMA_ETHEREUM_CONTRACT_POL, - toContractCallData: retireTxData, - toContractGasLimit: KLIMA_GAS_LIMIT, - }, - ], - } - - console.info('>> create ContractCallsQuoteRequest', quoteRequest) +import type { WalletClientWithPublicActions } from './types' +import { AddressZero } from './constants' - return getContractCallsQuote(quoteRequest) -} +const { findDefaultToken } = (lifiDataTypes as any).default const run = async () => { console.info('>> Klima Retire Demo: Retire(burn) Carbon tokens to offset CO2') - console.info('>> Initialize LiFi SDK') - - const { client, account } = setUpSDK({ - initialChain: optimism as Chain, - switchChains: [mainnet, arbitrum, optimism, polygon] as Chain[], - usePublicActions: true, - }) - - // config - const fromChain = ChainId.OPT - const fromToken = dataTypes.findDefaultToken( - CoinKey.USDC, - ChainId.OPT - ).address - const retireAmount = '100000' // 1 USDC try { - // get quote - const quote = await getKlimaQuote( + console.info('>> Initialize LiFi SDK') + + const privateKey = process.env.PRIVATE_KEY as Address + + // NOTE: Here we are using the private key to get the account, + // but you can also use a Mnemonic account - see https://viem.sh/docs/accounts/mnemonic + const account = privateKeyToAccount(privateKey) + + const client = createWalletClient({ + account, + chain: optimism as Chain, + transport: http(), + }).extend(publicActions) as WalletClientWithPublicActions + + const switchChains = [mainnet, arbitrum, optimism, polygon] as Chain[] + + createConfig({ + integrator: 'lifi-sdk-example', + providers: [ + EVM({ + getWalletClient: () => Promise.resolve(client), + switchChain: (chainId) => + Promise.resolve( + createWalletClient({ + account, + chain: switchChains.find((chain) => { + if (chain.id == chainId) { + return chain + } + }) as Chain, + transport: http(), + }) + ), + }), + ], + }) + + // config + const fromChain = ChainId.OPT + const fromToken = findDefaultToken(CoinKey.USDC, ChainId.OPT).address + const retireAmount = '100000' // 1 USDC + const USDCe_POL = findDefaultToken(CoinKey.USDCe, ChainId.POL) + const Base_Carbon_Tonne_POL = '0x2F800Db0fdb5223b3C3f354886d907A671414A7F' + // https://docs.klimadao.finance/developers/contracts/retirement/v2-diamond/generalized-retirement + const KLIMA_ETHEREUM_CONTRACT_POL = + '0x8cE54d9625371fb2a068986d32C85De8E6e995f8' + const KLIMA_GAS_LIMIT = '1300000' + const KLIMA_ABI = [ + 'function getSourceAmountDefaultRetirement(address,address,uint256) external view returns (uint256 amountIn)', + 'function retireExactCarbonDefault(address, address, uint256, uint256, string, address, string, string, uint8)', + ] + const abi = parseAbi(KLIMA_ABI) + + const publicClient = createPublicClient({ + chain: polygon, + transport: http(), + }) + + const sourceAmountDefaultRetirement = await publicClient.readContract({ + address: KLIMA_ETHEREUM_CONTRACT_POL, + abi, + functionName: 'getSourceAmountDefaultRetirement', + args: [ + USDCe_POL.address, // address sourceToken, + Base_Carbon_Tonne_POL, // address poolToken, + retireAmount, // uint256 retireAmount, + ], + }) + + const usdcAmount = parseUnits( + sourceAmountDefaultRetirement.toString(), + USDCe_POL.decimals + ).toString() + + console.log('>> usdcAmount', usdcAmount) + + const retireTxData = encodeFunctionData({ + abi, + functionName: 'retireExactCarbonDefault', + args: [ + USDCe_POL.address, // address sourceToken, + Base_Carbon_Tonne_POL, // address poolToken, + usdcAmount, // uint256 maxAmountIn, + retireAmount, // uint256 retireAmount, + 'LI.FI', // string memory retiringEntityString, + '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0', // address beneficiaryAddress, + 'LI.FI', // string memory beneficiaryString, + 'Cross Chain Contract Calls', // string memory retirementMessage, + 0, // LibTransfer.From fromMode], + ], + }) + + // quote + const contractCallsQuoteRequest: ContractCallsQuoteRequest = { fromChain, fromToken, - client as WalletClientWithPublicActions, - account.address, - retireAmount + fromAddress: account.address, + toChain: ChainId.POL, + toToken: USDCe_POL.address, + toAmount: usdcAmount, + contractCalls: [ + { + fromAmount: usdcAmount, + fromTokenAddress: USDCe_POL.address, + toContractAddress: KLIMA_ETHEREUM_CONTRACT_POL, + toContractCallData: retireTxData, + toContractGasLimit: KLIMA_GAS_LIMIT, + }, + ], + } + + console.info( + '>> create ContractCallsQuoteRequest', + contractCallsQuoteRequest ) - console.info('>> Quote received', quote) - // continue? + const contactCallsQuoteResponse = await getContractCallsQuote( + contractCallsQuoteRequest + ) + + console.info('>> Quote received', contactCallsQuoteResponse) + if (!(await promptConfirm('Execute Quote?'))) { return } - // execute quote - await executeCrossChainQuote( - client as WalletClientWithPublicActions, - account.address, - quote - ) + if (contactCallsQuoteResponse.action.fromToken.address !== AddressZero) { + const approval = await getTokenAllowance( + contactCallsQuoteResponse.action.fromToken, + account.address, + contactCallsQuoteResponse.estimate.approvalAddress + ) + + // set approval if needed + if (approval < BigInt(contactCallsQuoteResponse.action.fromAmount)) { + const txHash = await setTokenAllowance({ + walletClient: client, + spenderAddress: contactCallsQuoteResponse.estimate.approvalAddress, + token: contactCallsQuoteResponse.action.fromToken, + amount: BigInt(contactCallsQuoteResponse.action.fromAmount), + }) + + if (txHash) { + const transactionReceipt = await client.waitForTransactionReceipt({ + hash: txHash, + retryCount: 20, + retryDelay: ({ count }: { count: number; error: Error }) => + Math.min(~~(1 << count) * 200, 3000), + }) + + console.info( + `>> Set Token Allowance - transaction complete: amount: ${contactCallsQuoteResponse.action.fromToken} txHash: ${transactionReceipt.transactionHash}.` + ) + } + } + } + + const transactionRequest = contactCallsQuoteResponse.transactionRequest + + console.info('>> Execute transaction', transactionRequest) + + const { maxFeePerGas, maxPriorityFeePerGas } = + await client.estimateFeesPerGas() + + const hash = await client.sendTransaction({ + to: transactionRequest.to as Address, + account: client.account!, + value: transactionRequest.value ? transactionRequest.value : undefined, + data: transactionRequest.data as Hash, + gas: transactionRequest.gasLimit + ? BigInt(transactionRequest.gasLimit as string) + : undefined, + // gasPrice: transactionRequest.gasPrice + // ? BigInt(transactionRequest.gasPrice as string) + // : undefined, + maxFeePerGas, + maxPriorityFeePerGas, + chain: null, + } as any) + + console.info('>> Transaction sent', hash) + + const receipt = await client.waitForTransactionReceipt({ + hash, + }) + + console.info('>> Transaction receipt', receipt) + + // wait for execution + let result: StatusResponse + do { + await new Promise((res) => { + setTimeout(() => { + res(null) + }, 5000) + }) + + result = await getStatus({ + txHash: receipt.transactionHash, + bridge: contactCallsQuoteResponse.tool, + fromChain: contactCallsQuoteResponse.action.fromChainId, + toChain: contactCallsQuoteResponse.action.toChainId, + }) + + console.info('>> Status update', result) + } while (result.status !== 'DONE' && result.status !== 'FAILED') + + console.info('>> DONE', result) } catch (e) { console.error(e) } diff --git a/examples/node/examples/multihop.ts b/examples/node/examples/multihop.ts index 0c3f0b2..c26fd34 100644 --- a/examples/node/examples/multihop.ts +++ b/examples/node/examples/multihop.ts @@ -1,117 +1,228 @@ import * as lifiDataTypes from '@lifi/data-types' import type { ContractCallsQuoteRequest, - LiFiStep, QuoteRequest, + StatusResponse, } from '@lifi/sdk' -import { CoinKey, ChainId, getQuote, getContractCallsQuote } from '@lifi/sdk' -import type { Address, Chain } from 'viem' -import { fromHex } from 'viem' +import { + CoinKey, + ChainId, + getQuote, + getContractCallsQuote, + createConfig, + EVM, + getTokenAllowance, + setTokenAllowance, + getStatus, +} from '@lifi/sdk' +import type { Address, Chain, Hash } from 'viem' +import { createWalletClient, fromHex, http, publicActions } from 'viem' import { mainnet, arbitrum, optimism, polygon } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { AddressZero } from './constants' +import 'dotenv/config' import { promptConfirm } from '../helpers/promptConfirm' -import { executeCrossChainQuote } from './utils/executeCrossChainQuote' -import { setUpSDK } from './utils/setUpSDK' -import type { WalletClientWithPublicActions } from './types' - -const dataTypes = (lifiDataTypes as any).default -interface GetMultihopQuoteParams { - fromChain: ChainId - fromToken: string - toChain: ChainId - toToken: string - amount: string - address: string -} - -const getMultihopQuote = async ({ - fromChain, - fromToken, - toChain, - toToken, - amount, - address, -}: GetMultihopQuoteParams): Promise => { - // Get bridge route from polygon to destination chain - const secondBridgeQuoteRequest: QuoteRequest = { - fromChain: ChainId.POL, // polygon - fromToken: dataTypes.findDefaultToken(CoinKey.USDC, ChainId.POL).address, // USDC on polygon - fromAmount: amount, - toChain, - toToken, - fromAddress: address, // will actually be a relayer - allowBridges: ['hop', 'stargate', 'across', 'amarok'], - maxPriceImpact: 0.4, - } - - console.info( - '>> created second bridge quote request', - secondBridgeQuoteRequest - ) - - const secondBridgeQuote = await getQuote(secondBridgeQuoteRequest) - console.info('>> got second quote', secondBridgeQuote) - - // quote - const quoteRequest: ContractCallsQuoteRequest = { - fromChain, - fromToken, - fromAddress: address, - toChain: secondBridgeQuote.action.fromChainId, - toToken: secondBridgeQuote.action.fromToken.address, - toAmount: secondBridgeQuote.action.fromAmount, - contractCalls: [ - { - fromAmount: secondBridgeQuote.action.fromAmount, - fromTokenAddress: secondBridgeQuote.action.fromToken.address, - toContractAddress: secondBridgeQuote.transactionRequest!.to!, - toContractCallData: - secondBridgeQuote.transactionRequest!.data!.toString(), - toContractGasLimit: fromHex( - secondBridgeQuote.transactionRequest!.gasLimit!.toString() as Address, - 'bigint' - ).toString(), - }, - ], - } - - console.info('>> get contract calls quote', quoteRequest) - return getContractCallsQuote(quoteRequest) -} +const { findDefaultToken } = (lifiDataTypes as any).default const run = async () => { console.info('>> Starting Multihop demo - route USDC.ARB to USDC.OPT') try { console.info('>> Initialize LiFi SDK') - const { client, account } = setUpSDK({ - initialChain: arbitrum, - switchChains: [mainnet, arbitrum, optimism, polygon] as Chain[], - usePublicActions: true, + const privateKey = process.env.PRIVATE_KEY as Address + + // NOTE: Here we are using the private key to get the account, + // but you can also use a Mnemonic account - see https://viem.sh/docs/accounts/mnemonic + const account = privateKeyToAccount(privateKey) + + const client = createWalletClient({ + account, + chain: arbitrum, + transport: http(), + }).extend(publicActions) + + const switchChains = [mainnet, arbitrum, optimism, polygon] as Chain[] + + createConfig({ + integrator: 'lifi-sdk-example', + providers: [ + EVM({ + getWalletClient: () => Promise.resolve(client), + switchChain: (chainId) => + Promise.resolve( + createWalletClient({ + account, + chain: switchChains.find((chain) => { + if (chain.id == chainId) { + return chain + } + }) as Chain, + transport: http(), + }) + ), + }), + ], }) - // TODO: question: is there a clearer way to declare the start, intermediate and destination chain? - const quoteConfig = { - fromChain: ChainId.ARB, - fromToken: dataTypes.findDefaultToken(CoinKey.USDC, ChainId.ARB).address, // USDC on arbitrum - toChain: ChainId.OPT, - toToken: dataTypes.findDefaultToken(CoinKey.USDC, ChainId.OPT).address, - amount: '100000', // 1 usd - address: account.address, + // configure + const fromChain = ChainId.ARB + const fromToken = findDefaultToken(CoinKey.USDC, ChainId.ARB).address + const toChain = ChainId.OPT + const toToken = findDefaultToken(CoinKey.USDC, ChainId.OPT).address + const amount = '100000' // 1 usd + + const secondBridgeQuoteRequest: QuoteRequest = { + fromChain: ChainId.POL, + fromToken: findDefaultToken(CoinKey.USDC, ChainId.POL).address, + fromAmount: amount, + toChain, + toToken, + fromAddress: account.address, // will actually be a relayer + // allowBridges: ['hop', 'across', 'amarok'], + // denyBridges: ['stargate'], + maxPriceImpact: 0.4, + } + + console.info( + '>> created second bridge quote request', + secondBridgeQuoteRequest + ) + + const secondBridgeQuote = await getQuote(secondBridgeQuoteRequest) + console.info('>> got second quote', secondBridgeQuote) + + // quote + const quoteRequest: ContractCallsQuoteRequest = { + fromChain, + fromToken, + fromAddress: account.address, + toChain: secondBridgeQuote.action.fromChainId, + toToken: secondBridgeQuote.action.fromToken.address, + toAmount: secondBridgeQuote.action.fromAmount, + contractCalls: [ + { + fromAmount: secondBridgeQuote.action.fromAmount, + fromTokenAddress: secondBridgeQuote.action.fromToken.address, + toContractAddress: secondBridgeQuote.transactionRequest!.to!, + toContractCallData: + secondBridgeQuote.transactionRequest!.data!.toString(), + toContractGasLimit: fromHex( + secondBridgeQuote.transactionRequest!.gasLimit!.toString() as Address, + 'bigint' + ).toString(), + }, + ], } - const multiHopQuote = await getMultihopQuote(quoteConfig) - console.info('>> got multihop quote', multiHopQuote) + console.info('>> get contract calls quote', quoteRequest) + + const contactCallsQuoteResponse = await getContractCallsQuote(quoteRequest) + + console.info( + '>> got contract calls quote response', + contactCallsQuoteResponse + ) if (!(await promptConfirm('Execute Quote?'))) { return } - await executeCrossChainQuote( - client as WalletClientWithPublicActions, - account.address, - multiHopQuote + if (contactCallsQuoteResponse.action.fromToken.address !== AddressZero) { + const approval = await getTokenAllowance( + contactCallsQuoteResponse.action.fromToken, + account.address, + contactCallsQuoteResponse.estimate.approvalAddress + ) + + // set approval if needed + if (approval < BigInt(contactCallsQuoteResponse.action.fromAmount)) { + const txHash = await setTokenAllowance({ + walletClient: client, + spenderAddress: contactCallsQuoteResponse.estimate.approvalAddress, + token: contactCallsQuoteResponse.action.fromToken, + amount: BigInt(contactCallsQuoteResponse.action.fromAmount), + }) + + if (txHash) { + const transactionReceipt = await client.waitForTransactionReceipt({ + hash: txHash, + retryCount: 20, + retryDelay: ({ count }: { count: number; error: Error }) => + Math.min(~~(1 << count) * 200, 3000), + }) + + console.info( + `>> Set Token Allowance - transaction complete: amount: ${contactCallsQuoteResponse.action.fromToken} txHash: ${transactionReceipt.transactionHash}.` + ) + } + } + } + + const transactionRequest = contactCallsQuoteResponse.transactionRequest + + console.info('>> Execute transaction', transactionRequest) + + const { maxFeePerGas, maxPriorityFeePerGas } = + await client.estimateFeesPerGas() + + console.log( + 'viem maxFeePerGas, maxPriorityFeePerGas', + maxFeePerGas, + maxPriorityFeePerGas + ) + + console.log( + 'out gasPrice', + transactionRequest.gasPrice + ? BigInt(transactionRequest.gasPrice as string) + : undefined ) + + const hash = await client.sendTransaction({ + to: transactionRequest.to as Address, + account: client.account!, + value: transactionRequest.value ? transactionRequest.value : undefined, + data: transactionRequest.data as Hash, + gas: transactionRequest.gasLimit + ? BigInt(transactionRequest.gasLimit as string) + : undefined, + // gasPrice: transactionRequest.gasPrice + // ? BigInt(transactionRequest.gasPrice as string) + // : undefined, + maxFeePerGas, + maxPriorityFeePerGas, + chain: null, + } as any) + + console.info('>> Transaction sent', hash) + + const receipt = await client.waitForTransactionReceipt({ + hash, + }) + + console.info('>> Transaction receipt', receipt) + + // wait for execution + let result: StatusResponse + do { + await new Promise((res) => { + setTimeout(() => { + res(null) + }, 5000) + }) + + result = await getStatus({ + txHash: receipt.transactionHash, + bridge: contactCallsQuoteResponse.tool, + fromChain: contactCallsQuoteResponse.action.fromChainId, + toChain: contactCallsQuoteResponse.action.toChainId, + }) + + console.info('>> Status update', result) + } while (result.status !== 'DONE' && result.status !== 'FAILED') + + console.info('>> DONE', result) } catch (e) { console.error(e) } diff --git a/examples/node/examples/polynomialDeposit.ts b/examples/node/examples/polynomialDeposit.ts index c89a053..49a1007 100644 --- a/examples/node/examples/polynomialDeposit.ts +++ b/examples/node/examples/polynomialDeposit.ts @@ -1,95 +1,203 @@ -import type { ContractCallsQuoteRequest, LiFiStep } from '@lifi/sdk' -import { ChainId, getContractCallsQuote } from '@lifi/sdk' -import type { Chain, WalletClient } from 'viem' -import { encodeFunctionData, parseAbi, parseEther } from 'viem' +import type { ContractCallsQuoteRequest, StatusResponse } from '@lifi/sdk' +import { + ChainId, + createConfig, + EVM, + getContractCallsQuote, + getStatus, + getTokenAllowance, + setTokenAllowance, +} from '@lifi/sdk' +import type { Address, Chain, Hash } from 'viem' +import { + createWalletClient, + encodeFunctionData, + http, + parseAbi, + parseEther, + publicActions, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' import { mainnet, arbitrum, optimism, polygon } from 'viem/chains' +import 'dotenv/config' import { promptConfirm } from '../helpers/promptConfirm' -import { executeCrossChainQuote } from './utils/executeCrossChainQuote' -import { setUpSDK } from './utils/setUpSDK' -import { WalletClientWithPublicActions } from './types' +import type { WalletClientWithPublicActions } from './types' import { AddressZero } from './constants' -const sETH_OPT = '0xE405de8F52ba7559f9df3C368500B6E6ae6Cee49' -const POLYNOMIAL_ETHEREUM_CONTRACT_OPT = - '0x2D46292cbB3C601c6e2c74C32df3A4FCe99b59C7' -const POLYNOMIAL_ABI = [ - 'function initiateDeposit(address user, uint amount) external', -] -const POLYNOMIAL_GAS_LIMIT = '200000' - -const getPolynomialQuote = async ( - fromChain: ChainId, - fromToken: string, - client: WalletClient, - userAddress: string, - amount: string -): Promise => { - const abi = parseAbi(POLYNOMIAL_ABI) - - const stakeTxData = encodeFunctionData({ - abi, - functionName: 'initiateDeposit', - args: [userAddress, amount], - }) - - const quoteRequest: ContractCallsQuoteRequest = { - fromChain, - fromToken, - fromAddress: userAddress, - toChain: ChainId.OPT, - toToken: sETH_OPT, - toAmount: amount, - contractCalls: [ - { - fromAmount: amount, // TODO: is this the right value? not sure what it should be - fromTokenAddress: fromToken, // TODO: is this the right value? not sure what it should be - toContractAddress: POLYNOMIAL_ETHEREUM_CONTRACT_OPT, - toContractCallData: stakeTxData, - toContractGasLimit: POLYNOMIAL_GAS_LIMIT, - }, - ], - } - - console.info('>> create contract calls quote request', quoteRequest) - - return getContractCallsQuote(quoteRequest) -} - const run = async () => { console.info('>> Starting Polynomial Demo: Deposit sETH on Optimism') - // configure the chain token and amount to be used - const fromChain = ChainId.ETH // TODO: consider changing this to optimism? - const fromToken = AddressZero - const amount = parseEther('0.04').toString() - console.info('>> Initialize LiFi SDK') try { - const { account, client } = setUpSDK({ - initialChain: mainnet, - switchChains: [mainnet, arbitrum, optimism, polygon] as Chain[], - usePublicActions: true, + const privateKey = process.env.PRIVATE_KEY as Address + + // NOTE: Here we are using the private key to get the account, + // but you can also use a Mnemonic account - see https://viem.sh/docs/accounts/mnemonic + const account = privateKeyToAccount(privateKey) + + const client = createWalletClient({ + account, + chain: mainnet, + transport: http(), + }).extend(publicActions) as WalletClientWithPublicActions + + const switchChains = [mainnet, arbitrum, optimism, polygon] + + createConfig({ + integrator: 'lifi-sdk-example', + providers: [ + EVM({ + getWalletClient: () => Promise.resolve(client), + switchChain: (chainId) => + Promise.resolve( + createWalletClient({ + account, + chain: switchChains.find((chain) => { + if (chain.id == chainId) { + return chain + } + }) as Chain, + transport: http(), + }) + ), + }), + ], + }) + + // configure + const fromChain = ChainId.ETH + const fromToken = AddressZero + const amount = parseEther('0.04').toString() + const sETH_OPT = '0xE405de8F52ba7559f9df3C368500B6E6ae6Cee49' + const POLYNOMIAL_ETHEREUM_CONTRACT_OPT = + '0x2D46292cbB3C601c6e2c74C32df3A4FCe99b59C7' + const POLYNOMIAL_GAS_LIMIT = '200000' + const POLYNOMIAL_ABI = [ + 'function initiateDeposit(address user, uint amount) external', + ] + const abi = parseAbi(POLYNOMIAL_ABI) + + const stakeTxData = encodeFunctionData({ + abi, + functionName: 'initiateDeposit', + args: [account.address, amount], }) - const quote = await getPolynomialQuote( + const contractCallsQuoteRequest: ContractCallsQuoteRequest = { fromChain, fromToken, - client, - account.address, - amount + fromAddress: account.address, + toChain: ChainId.OPT, + toToken: sETH_OPT, + toAmount: amount, + contractCalls: [ + { + fromAmount: amount, + fromTokenAddress: sETH_OPT, // TODO: check if these are the correct values + toContractAddress: POLYNOMIAL_ETHEREUM_CONTRACT_OPT, + toContractCallData: stakeTxData, + toContractGasLimit: POLYNOMIAL_GAS_LIMIT, + }, + ], + } + + console.info( + '>> create contract calls quote request', + contractCallsQuoteRequest + ) + + const contactCallsQuoteResponse = await getContractCallsQuote( + contractCallsQuoteRequest ) - console.info('>> Contract Calls Quote', quote) + console.info('>> Contract Calls Quote', contactCallsQuoteResponse) if (!(await promptConfirm('Execute Quote?'))) { return } - await executeCrossChainQuote( - client as WalletClientWithPublicActions, - account.address, - quote - ) + if (contactCallsQuoteResponse.action.fromToken.address !== AddressZero) { + const approval = await getTokenAllowance( + contactCallsQuoteResponse.action.fromToken, + account.address, + contactCallsQuoteResponse.estimate.approvalAddress + ) + + // set approval if needed + if (approval < BigInt(contactCallsQuoteResponse.action.fromAmount)) { + const txHash = await setTokenAllowance({ + walletClient: client, + spenderAddress: contactCallsQuoteResponse.estimate.approvalAddress, + token: contactCallsQuoteResponse.action.fromToken, + amount: BigInt(contactCallsQuoteResponse.action.fromAmount), + }) + + if (txHash) { + const transactionReceipt = await client.waitForTransactionReceipt({ + hash: txHash, + retryCount: 20, + retryDelay: ({ count }: { count: number; error: Error }) => + Math.min(~~(1 << count) * 200, 3000), + }) + + console.info( + `>> Set Token Allowance - transaction complete: amount: ${contactCallsQuoteResponse.action.fromToken} txHash: ${transactionReceipt.transactionHash}.` + ) + } + } + } + + const transactionRequest = contactCallsQuoteResponse.transactionRequest + + console.info('>> Execute transaction', transactionRequest) + + const { maxFeePerGas, maxPriorityFeePerGas } = + await client.estimateFeesPerGas() + + const hash = await client.sendTransaction({ + to: transactionRequest.to as Address, + account: client.account!, + value: transactionRequest.value ? transactionRequest.value : undefined, + data: transactionRequest.data as Hash, + gas: transactionRequest.gasLimit + ? BigInt(transactionRequest.gasLimit as string) + : undefined, + // gasPrice: transactionRequest.gasPrice + // ? BigInt(transactionRequest.gasPrice as string) + // : undefined, + maxFeePerGas, + maxPriorityFeePerGas, + chain: null, + } as any) + + console.info('>> Transaction sent', hash) + + const receipt = await client.waitForTransactionReceipt({ + hash, + }) + + console.info('>> Transaction receipt', receipt) + + // wait for execution + let result: StatusResponse + do { + await new Promise((res) => { + setTimeout(() => { + res(null) + }, 5000) + }) + + result = await getStatus({ + txHash: receipt.transactionHash, + bridge: contactCallsQuoteResponse.tool, + fromChain: contactCallsQuoteResponse.action.fromChainId, + toChain: contactCallsQuoteResponse.action.toChainId, + }) + + console.info('>> Status update', result) + } while (result.status !== 'DONE' && result.status !== 'FAILED') + + console.info('>> DONE', result) } catch (e) { console.error(e) } diff --git a/examples/node/examples/utils/executeCrossChainQuote.ts b/examples/node/examples/utils/executeCrossChainQuote.ts deleted file mode 100644 index a7a8032..0000000 --- a/examples/node/examples/utils/executeCrossChainQuote.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { LiFiStep, StatusResponse } from '@lifi/sdk' -import { getTokenAllowance, setTokenAllowance, getStatus } from '@lifi/sdk' -import type { WalletClientWithPublicActions } from '../types' -import { executeTransaction } from './executeTransaction' -import { AddressZero } from '../constants' - -export const retryCount = 20 - -export const retryDelay = ({ count }: { count: number; error: Error }) => - Math.min(~~(1 << count) * 200, 3000) - -export const executeCrossChainQuote = async ( - client: WalletClientWithPublicActions, - address: string, - quote: LiFiStep -) => { - // Approval - if (quote.action.fromToken.address !== AddressZero) { - const approval = await getTokenAllowance( - quote.action.fromToken, - address, - quote.estimate.approvalAddress - ) - - // set approval if needed - if (approval < BigInt(quote.action.fromAmount)) { - const txHash = await setTokenAllowance({ - walletClient: client, - spenderAddress: quote.estimate.approvalAddress, - token: quote.action.fromToken, - amount: BigInt(quote.action.fromAmount), - }) - - if (txHash) { - const transactionReceipt = await client.waitForTransactionReceipt({ - hash: txHash, - retryCount, - retryDelay, - }) - - console.info( - `>> Set Token Allowance - transaction complete: amount: ${quote.action.fromToken} txHash: ${transactionReceipt.transactionHash}.` - ) - } - } - } - - const receipt = await executeTransaction(client, quote.transactionRequest!) - - // wait for execution - let result: StatusResponse - do { - await new Promise((res) => { - setTimeout(() => { - res(null) - }, 5000) - }) - - result = await getStatus({ - txHash: receipt.transactionHash, - bridge: quote.tool, - fromChain: quote.action.fromChainId, - toChain: quote.action.toChainId, - }) - - console.info('>> Status update', result) - } while (result.status !== 'DONE' && result.status !== 'FAILED') - - console.info('>> DONE', result) -} diff --git a/examples/node/examples/utils/executeTransaction.ts b/examples/node/examples/utils/executeTransaction.ts deleted file mode 100644 index 08fd53a..0000000 --- a/examples/node/examples/utils/executeTransaction.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Address, Hash } from 'viem' -import type { TransactionRequest } from '@lifi/sdk' -import type { WalletClientWithPublicActions } from '../types' - -export const executeTransaction = async ( - client: WalletClientWithPublicActions, - transactionRequest: TransactionRequest -) => { - console.info('>> Execute transaction', transactionRequest) - - const hash = await client.sendTransaction({ - to: transactionRequest.to as Address, - account: client.account!, - value: transactionRequest.value ? transactionRequest.value : undefined, - data: transactionRequest.data as Hash, - gas: transactionRequest.gasLimit - ? BigInt(transactionRequest.gasLimit as string) - : undefined, - gasPrice: transactionRequest.gasPrice - ? BigInt(transactionRequest.gasPrice as string) - : undefined, - maxFeePerGas: transactionRequest.maxFeePerGas - ? BigInt(transactionRequest.maxFeePerGas as string) - : undefined, - maxPriorityFeePerGas: transactionRequest.maxPriorityFeePerGas - ? BigInt(transactionRequest.maxPriorityFeePerGas as string) - : undefined, - kzg: undefined, - chain: null, - }) - - console.info('>> Transaction sent', hash) - - const transactionReceipt = await client.waitForTransactionReceipt({ - hash, - }) - - console.info('>> Transaction receipt', transactionReceipt) - - return transactionReceipt -} diff --git a/examples/node/examples/utils/setUpSDK.ts b/examples/node/examples/utils/setUpSDK.ts deleted file mode 100644 index 8a384da..0000000 --- a/examples/node/examples/utils/setUpSDK.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { createConfig, EVM } from '@lifi/sdk' -import type { Address, Chain } from 'viem' -import { createWalletClient, http, publicActions } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import 'dotenv/config' - -interface SetUpSDKParams { - /** - * initialChain: this is the chain to be used when setup up the viem client for the first time, - */ - initialChain: Chain - /** - * switchChains: list of chains made available to the @lifi/sdk switchChain function and controls which chains will be available, - */ - switchChains?: Chain[] - /** - * usePublicActions: some of the examples require that the wallet client have access to public client actions, when this value is true - * the wallet client will be extended with public actions - more about this at https://viem.sh/docs/clients/wallet#optional-extend-with-public-actions - */ - usePublicActions?: boolean -} - -export const setUpSDK = ({ - initialChain, - switchChains, - usePublicActions = false, -}: SetUpSDKParams) => { - const privateKey = process.env.PRIVATE_KEY as Address - - // NOTE: Here we are using the private key to get the account, - // but you can also use a Mnemonic account - see https://viem.sh/docs/accounts/mnemonic - const account = privateKeyToAccount(privateKey) - - let client = createWalletClient({ - account, - chain: initialChain, - transport: http(), - }) - - if (usePublicActions) { - client = client.extend(publicActions) - } - - createConfig({ - integrator: 'lifi-sdk-example', - providers: [ - EVM({ - getWalletClient: () => Promise.resolve(client), - ...(switchChains - ? { - // in some example we need to perform operations on multiple chains - // The switch chain function below helps to facilitates this - switchChain: (chainId) => - Promise.resolve( - createWalletClient({ - account, - chain: switchChains.find((chain) => { - if (chain.id == chainId) { - return chain - } - }) as Chain, - transport: http(), - }) - ), - } - : {}), - }), - ], - }) - - return { account, client } -} diff --git a/examples/node/package.json b/examples/node/package.json index f7498d9..bfe7cea 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -21,10 +21,7 @@ "example:bridge": "tsx examples/bridge.ts", "example:multihop": "tsx examples/multihop.ts", "example:klima": "tsx examples/klimaRetireExactCarbon.ts", - "example:polynomial": "tsx examples/polynomialDeposit.ts", - "test:klima": "tsx temp-test-examples/read-contract-klima-example.ts", - "test:polynomial": "tsx temp-test-examples/read-contract-polynomial-example.ts", - "test:viem": "tsx temp-test-examples/read-contract-viem-example.ts" + "example:polynomial": "tsx examples/polynomialDeposit.ts" }, "devDependencies": { "@types/node": "^20.12.12", diff --git a/examples/node/temp-test-examples/read-contract-klima-example.ts b/examples/node/temp-test-examples/read-contract-klima-example.ts deleted file mode 100644 index 8695168..0000000 --- a/examples/node/temp-test-examples/read-contract-klima-example.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - http, - getContract, - createWalletClient, - publicActions, - parseAbi, - parseEther, - encodeFunctionData, -} from 'viem' -import { polygon } from 'viem/chains' -import type { Address } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import 'dotenv/config' - -const USDC_POL = '0x2791bca1f2de4661ed88a30c99a7a9449aa84174' -const BCT_POL = '0x2F800Db0fdb5223b3C3f354886d907A671414A7F' - -// https://docs.klimadao.finance/developers/contracts/retirement/v2-diamond/generalized-retirement -const KLIMA_ETHEREUM_CONTRACT_OPT = '0x8cE54d9625371fb2a068986d32C85De8E6e995f8' - -const KLIMA_ABI = [ - 'function getSourceAmountDefaultRetirement(address,address,uint256) external view returns (uint256 amountIn)', - 'function retireExactCarbonDefault(address, address, uint256, uint256, string, address, string, string, uint8)', -] - -// TODO: this was file should be removed - create to help me figure out what the contract call looks like with viem -const run = async () => { - console.info('Klima example') - - const privateKey = process.env.PRIVATE_KEY as Address - - const account = privateKeyToAccount(privateKey) - - const walletClient = createWalletClient({ - account, - chain: polygon, - transport: http(), - }).extend(publicActions) - - const abi = parseAbi(KLIMA_ABI) - - console.log('ABI parsed:', JSON.stringify(abi, null, 2)) - - const contract = getContract({ - address: KLIMA_ETHEREUM_CONTRACT_OPT, - abi, - client: walletClient as any, - }) - - console.log('contract recieved:', contract) - - const retireAmount = parseEther('1').toString() - - const sourceAmountDefaultRetirement = await ( - contract as any - ).read.getSourceAmountDefaultRetirement([ - USDC_POL, // address sourceToken, - BCT_POL, // address poolToken, - parseEther('1').toString(), // uint256 retireAmount, - ]) - - const usdcAmount = sourceAmountDefaultRetirement.toString() - console.log('sourceAmountDefaultRetirement read:', usdcAmount) - - const data = encodeFunctionData({ - abi, - functionName: 'retireExactCarbonDefault', - args: [ - USDC_POL, // address sourceToken, - BCT_POL, // address poolToken, - usdcAmount, // uint256 maxAmountIn, - retireAmount, // uint256 retireAmount, - 'LI.FI', // string memory retiringEntityString, - '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0', // address beneficiaryAddress, - 'LI.FI', // string memory beneficiaryString, - 'Cross Chain Contract Calls', // string memory retirementMessage, - 0, // LibTransfer.From fromMode], - ], - }) - - console.log('data recieve', data) -} - -run() diff --git a/examples/node/temp-test-examples/read-contract-polynomial-example.ts b/examples/node/temp-test-examples/read-contract-polynomial-example.ts deleted file mode 100644 index 840633c..0000000 --- a/examples/node/temp-test-examples/read-contract-polynomial-example.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - http, - getContract, - createWalletClient, - publicActions, - parseAbi, - parseEther, - encodeFunctionData, - Chain, -} from 'viem' -import { optimism } from 'viem/chains' -import type { Address } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import 'dotenv/config' - -const POLYNOMIAL_ETHEREUM_CONTRACT_OPT = - '0x2D46292cbB3C601c6e2c74C32df3A4FCe99b59C7' -const POLYNOMIAL_ABI = [ - 'function initiateDeposit(address user, uint amount) external', -] - -// TODO: this was file should be removed - create to help me figure out what the contract call looks like with viem -const run = async () => { - console.info('Polynomial example') - - const privateKey = process.env.PRIVATE_KEY as Address - - const account = privateKeyToAccount(privateKey) - - const walletClient = createWalletClient({ - account, - chain: optimism as Chain, - transport: http(), - }).extend(publicActions) - - const abi = parseAbi(POLYNOMIAL_ABI) - - console.log('ABI parsed:', JSON.stringify(abi, null, 2)) - - const contract = getContract({ - address: POLYNOMIAL_ETHEREUM_CONTRACT_OPT, - abi, - client: walletClient as any, - }) - - console.log('contract recieved:', contract) - - const data = encodeFunctionData({ - abi, - functionName: 'initiateDeposit', - args: [account.address, parseEther('0.04').toString()], - }) - - console.log('data recieve', data) -} - -run() diff --git a/examples/node/temp-test-examples/read-contract-viem-example.ts b/examples/node/temp-test-examples/read-contract-viem-example.ts deleted file mode 100644 index 75a7150..0000000 --- a/examples/node/temp-test-examples/read-contract-viem-example.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - createPublicClient, - http, - getContract, - createWalletClient, - publicActions, -} from 'viem' -import { mainnet } from 'viem/chains' -import type { Address } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import 'dotenv/config' - -export const wagmiAbi = [ - { - inputs: [], - name: 'totalSupply', - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - name: 'Transfer', - type: 'event', - inputs: [ - { - indexed: true, - name: 'from', - type: 'address', - }, - { indexed: true, name: 'to', type: 'address' }, - { - indexed: true, - name: 'tokenId', - type: 'uint256', - }, - ], - }, -] as const - -const publicClient = createPublicClient({ - chain: mainnet, - transport: http(), -}) - -// TODO: this was file should be removed - create to help me figure out what the contract call looks like with viem -// example taken from -// https://viem.sh/docs/contract/getContract#usage -const run = async () => { - console.info('Viem example - https://viem.sh/docs/contract/getContract#usage') - const privateKey = process.env.PRIVATE_KEY as Address - - const account = privateKeyToAccount(privateKey) - - const walletClient = createWalletClient({ - account, - chain: mainnet, - transport: http(), - }).extend(publicActions) - - const contract = getContract({ - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi: wagmiAbi, - client: walletClient as any, // NOTE: viem types don't seem to be working so well - }) - - const result = await (contract as any).read.totalSupply() - - console.log(result.toString()) -} - -run()