Skip to main content
Glama
transfer.ts8.49 kB
import { getProvider, getAccount, getContract, parseStarknetAddress } from './clients.js'; import { CallData, cairo, uint256, constants } from 'starknet'; import { utils } from './utils.js'; // Common token contract addresses (same for all networks) const TOKEN_ADDRESSES = { ETH: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', STRK: '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d' }; // Default decimals for common tokens const TOKEN_DECIMALS = { ETH: 18, STRK: 18 }; // Common interface for all transfer operations interface TransferResult { txHash: string; waitForConfirmation: () => Promise<any>; } // Common parameters for all transfer operations interface TransferBaseParams { privateKey: string; from: string; to: string; // Can be an address or a Starknet ID amount: string | bigint; maxFee?: string | bigint; } /** * Converts a human-readable amount to token units * @param amount The amount in human-readable form (e.g., "0.00001") * @param decimals The number of decimals for the token * @returns The amount in token units as BigInt */ function parseTokenAmount(amount: string | bigint, decimals: number): bigint { // If amount is already a bigint, assume it's already in token units if (typeof amount === 'bigint') { return amount; } // Handle decimal amounts const amountStr = amount.toString(); // Check if the amount contains a decimal point if (amountStr.includes('.')) { const [integerPart, fractionalPart = ''] = amountStr.split('.'); // Ensure the fractional part isn't longer than allowed decimals if (fractionalPart.length > decimals) { throw new Error(`Amount has too many decimal places. Maximum allowed: ${decimals}`); } // Pad the fractional part with zeros if needed const paddedFractionalPart = fractionalPart.padEnd(decimals, '0'); // Combine the integer and fractional parts without the decimal point const combinedAmount = `${integerPart}${paddedFractionalPart}`; // Remove leading zeros (if any) and convert to BigInt return BigInt(combinedAmount.replace(/^0+/, '') || '0'); } // No decimal point, multiply by 10^decimals return BigInt(amountStr) * BigInt(10) ** BigInt(decimals); } /** * Prepare a transfer transaction * @param params Common transfer parameters * @param tokenAddress The token contract address * @param decimals The number of decimals for the token * @param network Network name * @returns Prepared account, transaction, and addresses */ async function prepareTransfer( params: TransferBaseParams, tokenAddress: string, decimals: number, network: string ) { // Convert amount to token units, accounting for decimals const amount = parseTokenAmount(params.amount, decimals); const fromAddress = parseStarknetAddress(params.from); // Resolve the 'to' parameter which could be either an address or a Starknet ID const toAddress = parseStarknetAddress(await utils.resolveNameOrAddress(params.to, network)); // Create account instance const account = getAccount(params.privateKey, fromAddress, network); // Prepare transaction const tx = { contractAddress: tokenAddress, entrypoint: 'transfer', calldata: CallData.compile({ recipient: toAddress, amount: uint256.bnToUint256(amount) }) }; return { account, tx, amount, fromAddress, toAddress }; } /** * Execute a prepared transaction with proper fee estimation * @param account The account instance * @param tx The transaction object * @param maxFeeOverride Optional override for max fee * @param network Network name * @returns Transaction result */ async function executeTransaction( account: any, tx: any, maxFeeOverride: string | bigint | undefined, network: string ): Promise<TransferResult> { const provider = getProvider(network); try { // Get fee estimate const { suggestedMaxFee } = await account.estimateFee(tx); // Use provided maxFee or suggested fee from estimation const maxFee = maxFeeOverride ? (typeof maxFeeOverride === 'string' ? BigInt(maxFeeOverride) : maxFeeOverride) : suggestedMaxFee; // Execute transaction const response = await account.execute( tx, undefined, { maxFee } ); const txHash = response.transaction_hash; return { txHash, waitForConfirmation: () => provider.waitForTransaction(txHash) }; } catch (error) { console.error('Error executing transaction:', error); throw new Error(`Transaction failed: ${(error as Error).message}`); } } /** * Transfer ETH (native token) from one account to another * @param params Transfer parameters * @param network Network name (mainnet, sepolia) * @returns Transaction details */ export async function transferETH( params: TransferBaseParams, network = 'mainnet' ): Promise<TransferResult> { try { const { account, tx } = await prepareTransfer( params, TOKEN_ADDRESSES.ETH, TOKEN_DECIMALS.ETH, network ); return executeTransaction(account, tx, params.maxFee, network); } catch (error) { console.error('Error transferring ETH:', error); throw new Error(`ETH transfer failed: ${(error as Error).message}`); } } /** * Transfer STRK (fee token) from one account to another * @param params Transfer parameters * @param network Network name (mainnet, sepolia) * @returns Transaction details */ export async function transferSTRK( params: TransferBaseParams, network = 'mainnet' ): Promise<TransferResult> { try { const { account, tx } = await prepareTransfer( params, TOKEN_ADDRESSES.STRK, TOKEN_DECIMALS.STRK, network ); return executeTransaction(account, tx, params.maxFee, network); } catch (error) { console.error('Error transferring STRK:', error); throw new Error(`STRK transfer failed: ${(error as Error).message}`); } } /** * Transfer ERC20 tokens from one account to another * @param params Transfer parameters * @param network Network name (mainnet, sepolia) * @returns Transaction details */ export async function transferERC20( params: TransferBaseParams & { tokenAddress: string; decimals?: number }, network = 'mainnet' ): Promise<TransferResult> { try { const tokenAddress = parseStarknetAddress(params.tokenAddress); // If decimals not provided, fetch them from the token contract let decimals = params.decimals; if (decimals === undefined) { const provider = getProvider(network); const contract = await getContract(tokenAddress, provider, network); const decimalsResponse = await contract.call('decimals', []); decimals = Number(decimalsResponse.toString()); } const { account, tx } = await prepareTransfer( params, tokenAddress, decimals, network ); return executeTransaction(account, tx, params.maxFee, network); } catch (error) { console.error('Error transferring ERC20 token:', error); throw new Error(`ERC20 transfer failed: ${(error as Error).message}`); } } /** * Execute an arbitrary contract call * @param params Call parameters * @param network Network name (mainnet, sepolia) * @returns Transaction details */ export async function executeContract( params: { privateKey: string; accountAddress: string; contractAddress: string; // Can be an address or a Starknet ID entrypoint: string; calldata?: any[]; maxFee?: string | bigint; }, network = 'mainnet' ): Promise<TransferResult> { try { const accountAddress = parseStarknetAddress(params.accountAddress); // Resolve the contract address which could be either an address or a Starknet ID const contractAddress = parseStarknetAddress( await utils.resolveNameOrAddress(params.contractAddress, network) ); // Create account instance const account = getAccount(params.privateKey, accountAddress, network); // Prepare transaction const tx = { contractAddress, entrypoint: params.entrypoint, calldata: params.calldata ? CallData.compile(params.calldata) : [] }; return executeTransaction(account, tx, params.maxFee, network); } catch (error) { console.error('Error executing contract call:', error); throw new Error(`Contract execution failed: ${(error as Error).message}`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcpdotdirect/starknet-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server