The ERC20 Transfer Ability enables Vincent Apps to send ERC20 tokens on behalf of Vincent Users using their Vincent Wallet (PKP). It performs thorough prechecks, enforces policy constraints, and executes transfers securely inside Lit Actions.
The ERC20 Transfer Ability is built using the Vincent Ability SDK and operates in two phases:
rpcUrl
to connect to the networkdecimals
from the contract and parses the amount accordinglychain
name to obtain an RPC via Lit.Actions.getRpcUrl({ chain })
decimals
and parses the amount to smallest unitstransfer(to, amount)
on the ERC-20 contract via laUtils
and returns the txHash
Depending on your role in the Vincent Ecosystem, you'll interact with this Ability differently:
Both precheck
and execute
expect the same parameters (as defined by the ability schema):
{
/** RPC URL used for precheck validations */
rpcUrl: string;
/** Chain name used during execute (e.g., 'base', 'ethereum', 'polygon') */
chain: string;
/** Recipient address */
to: string;
/** ERC-20 token contract address */
tokenAddress: string;
/** Amount in human-readable string (e.g., "1.23") */
amount: string;
}
Notes:
amount
must be a positive decimal string.tokenDecimals
.Note
To learn more about executing Vincent Abilities, see the Executing Abilities guide.
Use precheck
to validate parameters and balances before sending a transfer.
import { getVincentAbilityClient } from '@lit-protocol/vincent-app-sdk/abilityClient';
import { bundledVincentAbility } from '@lit-protocol/vincent-ability-erc20-transfer';
// Create ability client
const abilityClient = getVincentAbilityClient({
bundledVincentAbility,
ethersSigner: yourEthersSigner,
});
// Prepare parameters
const params = {
rpcUrl: 'https://base.llamarpc.com',
chain: 'base',
to: '0x1234...abcd',
tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
amount: '10.5',
};
const precheck = await abilityClient.precheck(params, {
delegatorPkpEthAddress: '0x...', // The Vincent Wallet (PKP) address
});
if (precheck.success) {
const { addressValid, amountValid, tokenAddressValid, estimatedGas, userBalance } =
precheck.result;
console.log(addressValid, amountValid, tokenAddressValid, estimatedGas, userBalance);
} else {
console.error(
'Precheck failed:',
precheck.result?.error || precheck.runtimeError || precheck.schemaValidationError,
);
}
Precheck success result:
{
addressValid: boolean;
amountValid: boolean;
tokenAddressValid: boolean;
estimatedGas: string;
userBalance: string;
}
Precheck failure result:
{
error: string;
}
Execute the transfer after a successful precheck.
const exec = await abilityClient.execute(params, {
delegatorPkpEthAddress: '0x...',
});
if (exec.success) {
const { txHash, to, amount, tokenAddress, timestamp } = exec.result;
console.log(
'Transfer tx:',
txHash,
'to:',
to,
'amount:',
amount,
'token:',
tokenAddress,
'at:',
timestamp,
);
} else {
console.error(
'Execute failed:',
exec.result?.error || exec.runtimeError || exec.schemaValidationError,
);
}
Execute success result:
{
txHash: string;
to: string;
amount: string;
tokenAddress: string;
timestamp: number;
}
Execute failure result:
{
error: string;
}
decimals
from the token contract; do not pass tokenDecimals
.chain
parameter is used to obtain an RPC during execution via
Lit.Actions.getRpcUrl
. Use supported EVM chain names (e.g., base
, ethereum
).This ability integrates with policy enforcement (e.g., send-counter). During execute
, allowed
policies are committed before the transfer is sent to prioritize policy updates.