@@ -7,8 +7,18 @@ import {
77 type State ,
88 parseKeyValueXml ,
99 composePromptFromState ,
10+ elizaLogger ,
1011} from '@elizaos/core' ;
11- import { type Hex , formatEther , parseEther } from 'viem' ;
12+ import {
13+ type Hex ,
14+ formatEther ,
15+ parseEther ,
16+ parseAbi ,
17+ encodeFunctionData ,
18+ parseUnits ,
19+ type Address ,
20+ } from 'viem' ;
21+ import { getToken } from '@lifi/sdk' ;
1222
1323import { type WalletProvider , initWalletProvider } from '../providers/wallet' ;
1424import { transferTemplate } from '../templates' ;
@@ -19,37 +29,125 @@ export class TransferAction {
1929 constructor ( private walletProvider : WalletProvider ) { }
2030
2131 async transfer ( params : TransferParams ) : Promise < Transaction > {
22- if ( ! params . data ) {
23- params . data = '0x' ;
24- }
25-
2632 const walletClient = this . walletProvider . getWalletClient ( params . fromChain ) ;
2733
2834 if ( ! walletClient . account ) {
2935 throw new Error ( 'Wallet account is not available' ) ;
3036 }
3137
38+ const chainConfig = this . walletProvider . getChainConfigs ( params . fromChain ) ;
39+
3240 try {
33- const hash = await walletClient . sendTransaction ( {
41+ let hash : Hex ;
42+ let to : Address ;
43+ let value : bigint ;
44+ let data : Hex ;
45+
46+ // Check if this is a token transfer or native transfer
47+ if (
48+ params . token &&
49+ params . token !== 'null' &&
50+ params . token . toUpperCase ( ) !== chainConfig . nativeCurrency . symbol . toUpperCase ( )
51+ ) {
52+ // This is an ERC20 token transfer
53+ console . log (
54+ `Processing ${ params . token } token transfer of ${ params . amount } to ${ params . toAddress } `
55+ ) ;
56+
57+ // First, resolve the token address
58+ const tokenAddress = await this . resolveTokenAddress ( params . token , chainConfig . id ) ;
59+
60+ // Check if token was resolved properly
61+ if ( tokenAddress === params . token && ! tokenAddress . startsWith ( '0x' ) ) {
62+ throw new Error (
63+ `Token ${ params . token } not found on ${ params . fromChain } . Please check the token symbol.`
64+ ) ;
65+ }
66+
67+ // Get token decimals
68+ const decimalsAbi = parseAbi ( [ 'function decimals() view returns (uint8)' ] ) ;
69+ const decimals = await this . walletProvider . getPublicClient ( params . fromChain ) . readContract ( {
70+ address : tokenAddress as Address ,
71+ abi : decimalsAbi ,
72+ functionName : 'decimals' ,
73+ } ) ;
74+
75+ // Parse amount with correct decimals
76+ const amountInTokenUnits = parseUnits ( params . amount , decimals ) ;
77+
78+ // Encode the ERC20 transfer function
79+ const transferData = encodeFunctionData ( {
80+ abi : parseAbi ( [ 'function transfer(address to, uint256 amount)' ] ) ,
81+ functionName : 'transfer' ,
82+ args : [ params . toAddress , amountInTokenUnits ] ,
83+ } ) ;
84+
85+ // For token transfers, we send to the token contract with 0 ETH value
86+ to = tokenAddress as Address ;
87+ value = 0n ;
88+ data = transferData ;
89+ } else {
90+ // This is a native ETH transfer
91+ console . log (
92+ `Processing native ${ chainConfig . nativeCurrency . symbol } transfer of ${ params . amount } to ${ params . toAddress } `
93+ ) ;
94+
95+ to = params . toAddress ;
96+ value = parseEther ( params . amount ) ;
97+ data = params . data || ( '0x' as Hex ) ;
98+ }
99+
100+ const transactionParams = {
34101 account : walletClient . account ,
35- to : params . toAddress ,
36- value : parseEther ( params . amount ) ,
37- data : params . data as Hex ,
102+ to,
103+ value,
104+ data,
38105 chain : walletClient . chain ,
39- } ) ;
106+ } ;
107+
108+ hash = await walletClient . sendTransaction ( transactionParams ) ;
109+ console . log ( `Transaction sent successfully. Hash: ${ hash } ` ) ;
40110
41111 return {
42112 hash,
43113 from : walletClient . account . address ,
44- to : params . toAddress ,
45- value : parseEther ( params . amount ) ,
46- data : params . data as Hex ,
114+ to : params . toAddress , // Always return the recipient address, not the contract
115+ value : value ,
116+ data : data ,
47117 } ;
48118 } catch ( error : unknown ) {
49119 const errorMessage = error instanceof Error ? error . message : String ( error ) ;
50120 throw new Error ( `Transfer failed: ${ errorMessage } ` ) ;
51121 }
52122 }
123+
124+ private async resolveTokenAddress (
125+ tokenSymbolOrAddress : string ,
126+ chainId : number
127+ ) : Promise < string > {
128+ // If it's already a valid address (starts with 0x and is 42 chars), return as is
129+ if ( tokenSymbolOrAddress . startsWith ( '0x' ) && tokenSymbolOrAddress . length === 42 ) {
130+ return tokenSymbolOrAddress ;
131+ }
132+
133+ // If it's the zero address (native token), return as is
134+ if ( tokenSymbolOrAddress === '0x0000000000000000000000000000000000000000' ) {
135+ return tokenSymbolOrAddress ;
136+ }
137+
138+ try {
139+ // Use LiFi SDK to resolve token symbol to address
140+ const token = await getToken ( chainId , tokenSymbolOrAddress ) ;
141+ return token . address ;
142+ } catch ( error ) {
143+ elizaLogger . error (
144+ `Failed to resolve token ${ tokenSymbolOrAddress } on chain ${ chainId } :` ,
145+ error
146+ ) ;
147+ // If LiFi fails, return original value and let downstream handle the error
148+ return tokenSymbolOrAddress ;
149+ }
150+ }
53151}
54152
55153const buildTransferDetails = async (
@@ -112,7 +210,8 @@ const buildTransferDetails = async (
112210
113211export const transferAction : Action = {
114212 name : 'EVM_TRANSFER_TOKENS' ,
115- description : 'Transfer tokens between addresses on the same chain' ,
213+ description :
214+ 'Transfer native tokens (ETH, BNB, etc.) or ERC20 tokens (USDC, USDT, etc.) between addresses on the same chain' ,
116215 handler : async (
117216 runtime : IAgentRuntime ,
118217 message : Memory ,
@@ -132,13 +231,24 @@ export const transferAction: Action = {
132231
133232 try {
134233 const transferResp = await action . transfer ( paramOptions ) ;
234+
235+ // Determine token symbol for display
236+ const chainConfig = walletProvider . getChainConfigs ( paramOptions . fromChain ) ;
237+ const tokenSymbol =
238+ paramOptions . token &&
239+ paramOptions . token !== 'null' &&
240+ paramOptions . token . toUpperCase ( ) !== chainConfig . nativeCurrency . symbol . toUpperCase ( )
241+ ? paramOptions . token . toUpperCase ( )
242+ : chainConfig . nativeCurrency . symbol ;
243+
135244 if ( callback ) {
136245 callback ( {
137- text : `Successfully transferred ${ paramOptions . amount } tokens to ${ paramOptions . toAddress } \nTransaction Hash: ${ transferResp . hash } ` ,
246+ text : `Successfully transferred ${ paramOptions . amount } ${ tokenSymbol } to ${ paramOptions . toAddress } \nTransaction Hash: ${ transferResp . hash } ` ,
138247 content : {
139248 success : true ,
140249 hash : transferResp . hash ,
141- amount : formatEther ( transferResp . value ) ,
250+ amount : paramOptions . amount ,
251+ token : tokenSymbol ,
142252 recipient : transferResp . to ,
143253 chain : paramOptions . fromChain ,
144254 } ,
@@ -167,14 +277,14 @@ export const transferAction: Action = {
167277 name : 'assistant' ,
168278 content : {
169279 text : "I'll help you transfer 1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e" ,
170- action : 'SEND_TOKENS ' ,
280+ action : 'EVM_TRANSFER_TOKENS ' ,
171281 } ,
172282 } ,
173283 {
174284 name : 'user' ,
175285 content : {
176286 text : 'Transfer 1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e' ,
177- action : 'SEND_TOKENS ' ,
287+ action : 'EVM_TRANSFER_TOKENS ' ,
178288 } ,
179289 } ,
180290 ] ,
0 commit comments