The EVM Transaction Signer Ability enables Vincent Apps to sign Ethereum Virtual Machine (EVM) transactions on behalf of Vincent Users using their Vincent Wallets. This enables Vincent Apps to interact with contracts even if there isn't an explicit Vincent Ability made for interacting with that contract.
This Vincent Ability also supports the Contract Whitelist Policy, which allows Vincent Users to restrict which contracts and functions can be called before this Ability signs transactions on their behalf.
The EVM Transaction Signer Ability is built using the Vincent Ability SDK and operates in two phases:
Precheck Phase: Validates the transaction structure and runs policy checks
Execution Phase: If permitted by the evaluated Policies, signs the serialized 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:
When defining your Vincent App, you select which Abilities you want to be able to execute on behalf of your users. If you want to enable your App Delegatees to be able to sign transactions on behalf of your Vincent App Users, allowing them to interact with contracts that don't have an explicit Vincent Ability made for interacting with them, you can add this Ability to your App.
Adding Abilities to your Vincent App is done using the Vincent App Dashboard, or while creating the App. 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.
Vincent App Users configure the Policies that govern Ability execution while consenting to the Vincent App.
If the Vincent App you're a Delegatee for has enabled the Contract Whitelist Policy for this Ability, then what contracts and functions that can be called will be restricted to what the Vincent App User has whitelisted. To learn more about how the Policy works, and how it affects your execution of this Ability, see the Contract Whitelist Policy documentation.
Note
To learn more about executing Vincent Abilities, see the Executing Abilities guide.
Before executing an EVM transaction signature, 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.
You must provide a complete EVM transaction object with all required fields including to
, value
, data
, chainId
, nonce
, gasLimit
, and appropriate gas pricing (gasPrice
for legacy transactions, or maxFeePerGas
and maxPriorityFeePerGas
for EIP-1559 transactions).
The Vincent App User's Vincent Wallet must have sufficient native tokens (ETH, MATIC, etc.) to pay for the transaction gas fees specified in the transaction object.
If the Vincent App User has enabled the Contract Whitelist Policy, the transaction must target a whitelisted contract and function. The transaction will be rejected if it attempts to interact with non-whitelisted contracts or functions.
precheck
FunctionThis Ability's precheck
function is used to check if the provided unsigned serialized transaction is valid in structure and contains all the required fields to sign the transaction for submission to the blockchain network.
Before executing the precheck
function, you'll need to create the complete EVM transaction object (which must contain all required properties such as to
, value
, data
, chainId
, nonce
, gasLimit
, and gas pricing) you want the user's Vincent Wallet to sign, and serialize it into a hex string.
The Ability expects this serialized transaction as the only parameter, and is required to execute the precheck
function:
{
/**
* The serialized transaction to be evaluated and signed.
* This is the transaction object serialized into a hex string.
* The transaction object must contain all required properties such as
* `to`, `value`, `data`, `chainId`, `nonce`, `gasLimit`, and gas pricing.
*/
serializedTransaction: string;
}
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-evm-transaction-signer
)ethersSigner
you'll be using to sign the request to Lit with your Delegatee private keyethers.utils.serializeTransaction
precheck
function on the VincentAbilityClient
instance, passing in the serialized transaction and the Vincent App User's Vincent Wallet addressimport { getVincentAbilityClient } from '@lit-protocol/vincent-app-sdk/abilityClient';
import { bundledVincentAbility } from '@lit-protocol/vincent-ability-evm-transaction-signer';
// Create ability client
const abilityClient = getVincentAbilityClient({
bundledVincentAbility: bundledVincentAbility,
ethersSigner: yourEthersSigner,
});
// Create a transaction
const transaction = {
to: '0x4200000000000000000000000000000000000006', // Base WETH
value: '0x00',
data: '0xa9059cbb...', // ERC20 transfer function call
chainId: 8453,
nonce: 0,
gasPrice: '0x...',
gasLimit: '0x...',
};
// Serialize the transaction
const serializedTx = ethers.utils.serializeTransaction(transaction);
const precheckResult = await abilityClient.precheck(
{
serializedTransaction: serializedTx,
},
{
delegatorPkpEthAddress: '0x...', // The Vincent App User's Vincent Wallet address that will sign the transaction
},
);
if (precheckResult.success) {
const { deserializedUnsignedTransaction } = precheckResult.result;
console.log('Transaction validated:', deserializedUnsignedTransaction);
} 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('Transaction validation failed:', precheckResult.result.error);
}
}
A successful precheck
response will contain the deserialized unsigned transaction object, which you can use to inspect the validated transaction details before signing:
{
deserializedUnsignedTransaction: {
to?: string;
nonce?: number;
gasLimit: string;
gasPrice?: string;
data: string;
value: string;
chainId: number;
type?: number;
accessList?: any[];
maxPriorityFeePerGas?: string;
maxFeePerGas?: string;
}
}
A failure precheck
response will contain:
{
/**
* A string containing the error message if the precheck failed.
*/
error: string;
}
execute
FunctionThis Ability's execute
function signs the serialized transaction if permitted by the evaluated Policies.
The execute
function expects a single parameter which is the serialized unsigned transaction created above, and you can use the same Vincent Ability Client to execute the functions like so:
const executeResult = await abilityClient.execute(
{
serializedTransaction: serializedTx,
},
{
delegatorPkpEthAddress: '0x...', // The Vincent App User's Vincent Wallet address that will sign the transaction
},
);
if (executeResult.success) {
const { signedTransaction, deserializedSignedTransaction } = executeResult.result;
console.log('Transaction signed successfully:', signedTransaction);
console.log('Transaction details:', deserializedSignedTransaction);
} 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('Transaction signing failed:', executeResult.result.error);
}
}
A successful execute
response will contain the signed transaction hex and the deserialized signed transaction object, which you can use to inspect the signed transaction details before broadcasting the signed transaction:
{
/**
* The signed transaction ready for broadcast.
*/
signedTransaction: string;
/**
* The deserialized signed transaction object.
*/
deserializedSignedTransaction: {
hash?: string;
to: string;
from: string;
nonce: number;
gasLimit: string;
gasPrice?: string;
data: string;
value: string;
chainId: number;
v: number;
r: string;
s: string;
type?: number;
accessList?: any[];
maxPriorityFeePerGas?: string;
maxFeePerGas?: string;
}
}
A failure execute
response will contain:
{
/**
* A string containing the error message if the execution failed.
*/
error: string;
}
Both the precheck
and execute
functions require a complete unsigned serialized transaction to be provided. This Ability does not handle the nonce
or gas related fields, so you'll need to provide these values in the transaction object you're serializing.