The ERC20 Approval Ability enables Vincent Apps to manage ERC20 token allowances on behalf of Vincent Users. This is generally a prerequisite for many DeFi operations like swaps, lending, and liquidity provision.
The ERC20 Approval Ability is built using the Vincent Ability SDK and operates in two phases:
Precheck Phase: Validates the request and checks existing allowances
Execution Phase: If needed, creates and submits the approval transaction
Depending on your role in the Vincent Ecosystem, you'll be interacting with this Ability in different ways. Click on the link below that matches your role to see how to get started:
If you want to enable your App Delegatees to manage ERC20 token approvals on behalf of your Vincent App Users, you can add this Ability to your App.
Adding Abilities to your Vincent App is done using the Vincent App Dashboard. Visit the Create Vincent App guide to learn more about how to add Abilities to your App during creation, or check out the Upgrading Your App guide to learn how to add Abilities to an existing App.
Note
To learn more about executing Vincent Abilities, see the Executing Abilities guide.
Before executing an ERC20 approval, the following conditions must be met. You can use the Ability's precheck
function to check if these conditions are met, or you can check them manually.
The Vincent App User's Vincent Wallet must have sufficient native tokens (ETH, MATIC, etc.) to pay for the approval transaction gas fees.
The specified token address must be a valid ERC20 contract on the target network that implements the standard approve
function.
The RPC URL must be valid and accessible for the specified chain ID, and the network must support ERC20 token operations.
precheck
FunctionThis Ability's precheck
function is used to check if the Vincent Wallet has a non-zero native token balance for gas fees, and if the spender already has sufficient allowance for the requested amount.
Before executing the precheck
function, you'll need to provide the following parameters for the ERC20 token approval transaction:
{
/**
* The RPC URL to use for the transaction.
* Must support the chainId specified.
*/
rpcUrl: string;
/**
* The chain ID to execute the transaction on.
* For example: 8453 for Base.
*/
chainId: number;
/**
* The spender address to approve.
* For example 0x2626664c2603336E57B271c5C0b26F421741e481 for the Uniswap v3 Swap Router contract on Base.
*/
spenderAddress: string;
/**
* ERC20 Token address to approve.
* For example 0x4200000000000000000000000000000000000006 for WETH on Base.
*/
tokenAddress: string;
/**
* ERC20 Token to approve decimals.
* For example 18 for WETH on Base.
*/
tokenDecimals: number;
/**
* Amount of tokenIn to approve.
* Cannot be a negative number.
* For example 0.00001 for 0.00001 WETH.
*/
tokenAmount: number;
}
To execute precheck
, you'll need to:
VincentAbilityClient
using the getVincentAbilityClient
function (imported from @lit-protocol/vincent-app-sdk/abilityClient
)
bundledVincentAbility
object (imported from @lit-protocol/vincent-ability-erc20-approval
)ethersSigner
you'll be using to sign the request to Lit with your Delegatee private keyprecheck
functionVincentAbilityClient
instance to call the precheck
functionimport { getVincentAbilityClient } from '@lit-protocol/vincent-app-sdk/abilityClient';
import { bundledVincentAbility } from '@lit-protocol/vincent-ability-erc20-approval';
// Create ability client
const abilityClient = getVincentAbilityClient({
bundledVincentAbility: bundledVincentAbility,
ethersSigner: yourEthersSigner,
});
// Prepare approval parameters
const approvalParams = {
rpcUrl: 'https://mainnet.base.org', // RPC URL for the network
chainId: 8453, // Chain ID (e.g., 8453 for Base)
spenderAddress: '0x2626664c2603336E57B271c5C0b26F421741e481', // Uniswap v3 Router on Base
tokenAddress: '0x4200000000000000000000000000000000000006', // WETH on Base
tokenDecimals: 18, // Token decimals
tokenAmount: 0.1, // Amount to approve (in human-readable format)
};
const precheckResult = await abilityClient.precheck(approvalParams, {
delegatorPkpEthAddress: '0x...', // The Vincent App User's Vincent Wallet address
});
if (precheckResult.success) {
const { alreadyApproved, currentAllowance } = precheckResult.result;
if (alreadyApproved) {
console.log('Already approved with allowance:', currentAllowance);
} else {
console.log('Current allowance:', currentAllowance);
console.log('Approval needed, proceed to execute');
}
} else {
// Handle different types of failures
if (precheckResult.runtimeError) {
console.error('Runtime error:', precheckResult.runtimeError);
}
if (precheckResult.schemaValidationError) {
console.error('Schema validation error:', precheckResult.schemaValidationError);
}
if (precheckResult.result) {
console.error('ERC20 approval precheck failed:', precheckResult.result.reason);
}
}
A success precheck
response will contain:
{
/**
* Boolean indicating if the spender already has sufficient allowance.
*/
alreadyApproved: boolean;
/**
* The current allowance amount as a string
* in the smallest unit (e.g., "10000000000000000" for 0.01 ETH)
*/
currentAllowance: string;
}
A failure precheck
response will contain:
{
/**
* Boolean indicating if the user has enough native token to pay for gas fees.
*/
noNativeTokenBalance: boolean;
}
execute
FunctionThis Ability's execute
function creates and submits the ERC20 approval transaction only if the current allowance is less than the requested amount.
The execute
function expects the same parameters as the precheck
function, and can be executed using the same VincentAbilityClient
instance:
const executeResult = await abilityClient.execute(approvalParams, {
delegatorPkpEthAddress: '0x...', // The Vincent App User's Vincent Wallet address
});
if (executeResult.success) {
const { approvalTxHash, approvedAmount, tokenAddress, tokenDecimals, spenderAddress } =
executeResult.result;
if (approvalTxHash) {
console.log('Approval transaction:', approvalTxHash);
console.log('Approved amount:', approvedAmount);
} else {
console.log('Used existing approval, amount:', approvedAmount);
}
} else {
// Handle different types of failures
if (executeResult.runtimeError) {
console.error('Runtime error:', executeResult.runtimeError);
}
if (executeResult.schemaValidationError) {
console.error('Schema validation error:', executeResult.schemaValidationError);
}
if (executeResult.result) {
console.error('ERC20 approval execution failed:', executeResult.result.error);
}
}
A successful execute
response will contain:
{
/**
* Transaction hash if a new approval was created
* If the current allowance already satisfies the requested amount,
* this will be undefined
*/
approvalTxHash?: string;
/**
* The approved amount that is now active (either from existing or new approval)
* in the smallest unit (e.g., "10000000000000000" for 0.01 ETH)
*/
approvedAmount: string;
/**
* The token address that was approved
*/
tokenAddress: string;
/**
* The number of decimals for the token that was approved
* For example 18 for WETH on Base.
*/
tokenDecimals: number;
/**
* The address approved to spend approvedAmount of tokens
*/
spenderAddress: string;
}
A failure execute
response will contain:
{
/**
* A string containing the error message if the execution failed.
*/
error: string;
}
The Vincent App User's Vincent Wallet must have sufficient native tokens (ETH, MATIC, etc.) to pay for the approval transaction gas fees. The precheck function will verify this and return an error if the balance is insufficient.
rpcUrl
provided supports the specified chainId
The ability package exports helper functions that can be used independently:
import {
getCurrentAllowance,
checkNativeTokenBalance,
} from '@lit-protocol/vincent-ability-erc20-approval';
// Check current allowance
const allowance = await getCurrentAllowance({
provider: ethersProvider,
tokenAddress: '0x...',
owner: '0x...', // Token owner address
spender: '0x...', // Spender address
});
// Check native token balance
const hasBalance = await checkNativeTokenBalance({
provider: ethersProvider,
pkpEthAddress: '0x...',
});