The Uniswap Swap Ability enables Vincent Apps to execute token swaps using Uniswap V3 on behalf of Vincent Users. This allows Vincent Apps to utilize decentralized exchanges without requiring Vincent App Users having to manually approve each swap transaction.
The Uniswap Swap Ability is built using the Vincent Ability SDK and operates in three phases:
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 execute token swaps 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.
Before executing the precheck
or execute
functions, you must first generate a signed swap quote using the getSignedUniswapQuote
function. This function uses Uniswap's Alpha Router to find the optimal swap path and signs it with a dedicated PKP to prevent tampering.
import { LitNodeClient } from '@lit-protocol/lit-node-client';
import { getSignedUniswapQuote } from '@lit-protocol/vincent-ability-uniswap-swap';
import { ethers } from 'ethers';
// Initialize Lit Node Client
const litNodeClient = new LitNodeClient({
litNetwork: 'datil',
debug: true,
});
await litNodeClient.connect();
// Your delegatee signer (one of the delegatee signers for the Vincent App)
const delegateeSigner = new ethers.Wallet('YOUR_DELEGATEE_PRIVATE_KEY', provider);
// Generate the signed quote
const signedUniswapQuote = await getSignedUniswapQuote({
quoteParams: {
rpcUrl: 'https://mainnet.base.org',
tokenInAddress: '0x4200000000000000000000000000000000000006', // WETH on Base
tokenInAmount: '0.001',
tokenOutAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
recipient: delegatorPkpEthAddress, // The Vincent App User's Vincent Wallet address
slippageTolerance: 100, // Optional: 100 basis points = 1% (defaults to 50 basis points = 0.5%)
},
ethersSigner: delegateeSigner,
litNodeClient,
});
The signed quote contains:
precheck
FunctionBefore executing a Uniswap swap, 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 approved the Uniswap V3 Router to spend sufficient amount of the input token.
If your Vincent App has enabled the ERC20 Approval Ability, you can use it to handle submitting the approval transaction using the App User's Vincent Wallet.
The Vincent App User's Vincent Wallet must have sufficient balance of the input token to perform the swap, and sufficient native tokens (ETH, MATIC, etc.) to pay for the swap transaction gas fees.
A valid signed Uniswap quote created by this Ability's getSignedUniswapQuote
function which will have a valid route with a valid Uniswap V3 pool for the specified token pair on the target network.
This Ability's precheck
function validates all prerequisites for executing a Uniswap swap using the signed quote, without actually performing the swap.
The precheck
function requires the following parameters:
{
/**
* An RPC endpoint for the chain where the swap will be executed.
*/
rpcUrlForUniswap: string;
/**
* The signed Uniswap quote generated from Step 1.
*/
signedUniswapQuote: {
quote: UniswapQuote;
signature: string;
}
}
To execute precheck
:
import { getVincentAbilityClient } from '@lit-protocol/vincent-app-sdk/abilityClient';
import { bundledVincentAbility } from '@lit-protocol/vincent-ability-uniswap-swap';
// Create ability client
const abilityClient = getVincentAbilityClient({
bundledVincentAbility,
ethersSigner: delegateeSigner,
});
// Run precheck with the signed quote
const precheckResult = await abilityClient.precheck(
{
rpcUrlForUniswap: 'https://mainnet.base.org',
signedUniswapQuote: {
quote: signedUniswapQuote.quote,
signature: signedUniswapQuote.signature,
},
},
{
delegatorPkpEthAddress: '0x...', // The Vincent App User's Vincent Wallet address
},
);
if (precheckResult.success) {
console.log('Swap precheck passed, ready 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('Swap precheck failed:', precheckResult.result.reason);
}
}
A successful precheck
response indicates that all prerequisites for the swap have been validated (there is no return value from this Ability's precheck
function).
A failure precheck
response will contain:
{
/**
* The reason the precheck failed, such as insufficient balance,
* missing allowance, or non-existent pool.
*/
reason?: string;
}
execute
FunctionThis Ability's execute
function performs the actual Uniswap swap using the signed quote.
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(
{
rpcUrlForUniswap: 'https://mainnet.base.org',
signedUniswapQuote: {
quote: signedUniswapQuote.quote,
signature: signedUniswapQuote.signature,
},
},
{
delegatorPkpEthAddress: '0x...', // The Vincent App User's Vincent Wallet address
},
);
if (executeResult.success) {
const { swapTxHash, spendTxHash } = executeResult.result;
console.log('Swap transaction:', swapTxHash);
if (spendTxHash) {
console.log('Spending limit tracking transaction:', spendTxHash);
}
} 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('Swap execution failed:', executeResult.result.reason);
if (executeResult.result.spendingLimitCommitFail) {
console.error('Spending limit commit failed:', executeResult.result.spendingLimitCommitFail);
}
}
}
A successful execute
response will contain:
{
/**
* The hash of the Uniswap swap transaction
*/
swapTxHash: string;
}
A failure execute
response will contain:
{
/**
* The reason the execution failed, such as insufficient balance,
* missing allowance, or non-existent pool.
*/
reason?: string;
}
The Ability uses a default slippage tolerance of 0.5% (50 basis points). The delegatee can override this by passing the optional slippageTolerance
parameter when calling getSignedUniswapQuote
. The slippage is specified in basis points (e.g., 50 for 0.5%, 100 for 1%, 500 for 5%). The signed quote includes slippage protection built into the route calculation. For large swaps, the Alpha Router will automatically account for price impact across multiple pools to find the best execution path.
The Ability works on networks supported by the @uniswap/sdk-core package where Uniswap V3 is deployed. Ensure that the rpcUrl
provided to getSignedUniswapQuote
corresponds to one of the supported networks.
Common failure scenarios include:
For a complete working example showing the full workflow from quote generation to swap execution, including ERC20 approvals, see the swap.spec.ts end-to-end test in the abilities-e2e package.