Skip to content

Latest commit

 

History

History
170 lines (153 loc) · 6.03 KB

action-function-example.md

File metadata and controls

170 lines (153 loc) · 6.03 KB

Action Function Example

import { EVM, FunctionOptions, FunctionReturn, toResult } from '@heyanon/sdk';
import { Address, encodeFunctionData, erc20Abi, formatUnits, parseEther, parseEventLogs, parseUnits } from 'viem';
import { vBNBAbi } from '../abis/vBNBAbi';
import { vTokenAbi } from '../abis/vTokenAbi';
import { validateAndGetTokenDetails, validateWallet } from '../utils';

interface Props {
	chainName: string;
	account: Address;
	amount: string;
	tokenSymbol: string;
	pool: string;
}
const { checkToApprove } = EVM.utils;
/**
 * Mints vTokens by depositing/supplying assets to the Venus Protocol.
 *
 * @param props - Parameters for minting vTokens
 * @param options - System tools containing provider, transaction sender, and notification functions
 * @returns Result object with transaction status and details of minted vTokens
 */
export async function mintToken({ chainName, account, amount, tokenSymbol, pool }: Props, options: FunctionOptions): Promise<FunctionReturn> {
	const {
		evm: { getProvider, sendTransactions },
		notify,
	} = options;
	const wallet = validateWallet({ account });
	if (!wallet.success) {
		return toResult(wallet.errorMessage, true);
	}
	if (!amount || typeof amount !== 'string' || isNaN(Number(amount)) || Number(amount) <= 0) {
		return toResult('Amount must be a valid number greater than 0', true);
	}
	const tokenDetails = validateAndGetTokenDetails({ chainName, pool, tokenSymbol: tokenSymbol });
	if (!tokenDetails.success) {
		return toResult(tokenDetails.errorMessage, true);
	}

	const provider = getProvider(tokenDetails.data.chainId);
	const vToken = await provider.multicall({
		contracts: ['symbol', 'decimals'].map((functionName) => ({
			address: tokenDetails.data.tokenAddress,
			abi: erc20Abi,
			functionName,
		})),
	});

	const [symbol_vToken, decimals_vToken] = vToken.map((result) => result.result) as [string, bigint];
	if (!symbol_vToken || !decimals_vToken)
		throw new Error(
			`Invalid ERC20 token contract at address ${tokenDetails.data.tokenAddress}. Failed to fetch token details (symbol: ${symbol_vToken}, decimals: ${decimals_vToken})`,
		);

	try {
		await notify('Preparing to mint Token...');
		const transactions: EVM.types.TransactionParams[] = [];

		// Prepare mint transaction
		if (tokenDetails.data.isChainBased) {
			const mintTx: EVM.types.TransactionParams = {
				target: tokenDetails.data.tokenAddress,
				data: encodeFunctionData({
					abi: vBNBAbi,
					functionName: 'mint',
					args: [],
				}),
				value: parseEther(amount),
			};
			transactions.push(mintTx);
		} else {
			const underlyingAssetAddress = (await provider.readContract({
				abi: vTokenAbi,
				address: tokenDetails.data.tokenAddress,
				functionName: 'underlying',
				args: [],
			})) as Address;

			const uToken = await provider.multicall({
				contracts: ['symbol', 'decimals'].map((functionName) => ({
					address: underlyingAssetAddress,
					abi: erc20Abi,
					functionName,
				})),
			});

			const [symbol_uToken, decimals_uToken] = uToken.map((result) => result.result) as [string, bigint];
			if (!symbol_uToken || !decimals_uToken)
				throw new Error(
					`Invalid ERC20 token contract at address ${underlyingAssetAddress}. Failed to fetch token details (symbol: ${symbol_uToken}, decimals: ${decimals_uToken})`,
				);

			// console.log(underlyingAssetAddress);
			await checkToApprove({
				args: {
					account,
					target: underlyingAssetAddress,
					spender: tokenDetails.data.tokenAddress,
					amount: parseUnits(amount, Number(decimals_uToken)),
				},
				provider,
				transactions,
			});
			const mintTx: EVM.types.TransactionParams = {
				target: tokenDetails.data.tokenAddress,
				data: encodeFunctionData({
					abi: vTokenAbi,
					functionName: 'mint',
					args: [parseUnits(amount, Number(decimals_uToken))],
				}),
			};
			transactions.push(mintTx);
		}
		// console.log("transactions", transactions);

		// Send transactions (to mint)
		const result = await sendTransactions({
			chainId: tokenDetails.data.chainId,
			account,
			transactions: transactions,
		});

		const mintData = result.data[result.data.length - 1];
		if (result.isMultisig) {
			return toResult(mintData.message);
		}
		// Extract and verify transaction hash
		const txHash = mintData.hash;
		if (!txHash) return toResult('Transaction succeeded, but failed to receive tx hash', true);

		// Get transaction receipt and parse Deposit event
		const receipt = await provider.getTransactionReceipt({ hash: txHash });
		// console.log("receipt.logs", receipt.logs );

		// Look for Mint event specifically
		const mintLogs = parseEventLogs({
			abi: tokenDetails.data.isChainBased ? vBNBAbi : vTokenAbi,
			eventName: 'Mint',
			logs: receipt.logs,
		});

		if (mintLogs.length === 0) {
			return toResult(`Transaction succeeded but no Mint event found.  ${JSON.stringify(mintData)}`, true);
		}
		const { mintTokens } = mintLogs[0].args;
		const totalSupplyText = tokenDetails.data.isChainBased
			? ''
			: `Total ${formatUnits((mintLogs[0].args as { totalSupply: bigint }).totalSupply, Number(decimals_vToken))} ${symbol_vToken}.`;

		return toResult(
			`Successfully deposited ${amount} ${tokenSymbol}. Minted ${formatUnits(mintTokens, Number(decimals_vToken))} ${symbol_vToken}. ${totalSupplyText} ${JSON.stringify(mintData)}`,
		);
	} catch (error) {
		return toResult(`Failed to mint token: ${error instanceof Error ? error.message : 'Unknown error'}`, true);
	}
}

Key Points:

  • Validates input arguments.
  • Add additional checks of smart contract requirements (check for proper balance, time of invoke function) and return to user proper error if check failed.
  • Uses notify to inform the user.
  • Calls sendTransactions only once with the transaction array.
  • Returns the result using toResult.
  • Make sure that you not miss crucial info in events. For example if this function "stake" and it's not return anything except amount which static as user input - then read event doesn't necessary. But if "Stake" event show time when lock of stake will ends - it will be nice show it to user.
  • Includes JSDoc comments. </code_block_to_apply_changes_from>