Transaction execution
Basic Transaction Execution
Execute transactions from your MSA wallets with basic operations, batch transactions, and gas management.
Execute transactions from your Account Abstraction wallets using the MSA API. This guide covers basic operations, batch transactions, and common execution patterns.
Overview
Transaction execution in MSA works through UserOperations (UserOps), which are bundled and sent through the EntryPoint contract. This enables batch operations, gas abstraction, and advanced transaction features.
Basic Transaction
Single Operation
import { PKSigner, SmartWallet, AccountOperations, Operation } from '@omnes/smartwallet-ts-sdk';
// Load config variables
const privateKey = process.env.PRIVATE_KEY;
const rpcURL = process.env.RPC_URL as string;
const apiKey = process.env.API_KEY as string;
// Create a signer (private key, GCP HSM, etc.)
const signer = await PKSigner.create(privateKey as `0x${string}`);
// Create SmartWallet instance
const smartWallet = await SmartWallet.create(
signer.signMessage, // message sign method
signer.signMessage, // this is the method to sign the userOp hash.
signer.getEVMAddress(), // regular method defined for all signers provided by the SmartWallet SDK library
rpcURL,
apiKey
);
// build a user operation
const operations: Operation = {
to: "0x097d4Aed5924e2172451973153Fc0e03407eD1F9", // Token contract
value: BigInt(0), // SDK expects bigint. Value to send in wei.
funcSignature: "transfer(address,uint256)", // function signature as used in Solidity.
params: [
"0xRecipientAddress...",
1000000000000000000 // 1 token (18 decimals)
] // function parameters
};
const accountOperations: AccountOperations[] = [
{
account: {
walletCustody: 1, // ECDSA_VALIDATOR
salt: "user@example.com", // you can use any salt you want to create the account
publicKeys: []
},
operations: [operations],
settings: {} // Empty settings object - SDK will provide defaults
}
];
// Build and send UserOperations
const result = await smartWallet.buildAndRequestSendUserOperations(
accountOperations,
[], // useABI - you can define your own ABI here. This is used to parse errors and events.
[] // signers - if more than one signer, you can provide an array of signer addresses
);
console.log('Transaction hash:', result.response.txHash);
console.log('Status:', result.response.status);import requests
def execute_transaction():
url = "https://api.msa.omnes.tech/execute"
payload = {
"operations": [{
"walletCustody": 1,
"salt": "user@example.com",
"to": "0x097d4Aed5924e2172451973153Fc0e03407eD1F9",
"funcSignature": "transfer(address,uint256)",
"funcParams": [
"0xRecipientAddress...",
"1000000000000000000"
]
}],
"settings": {
"rpc": "https://rpc-amoy.polygon.technology/",
"factory": "0xb09Fd1134553a43A3E02182a6B04F4dEBa7476F4",
"validator": "0x9bD18Da66990F80d598dE02d5143dC9A4422eC3a",
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
"beaconAdminAddress": "0x99Ee7725D6a8f691d8B375e0aD33d1Aff2236618",
"signer": {
"clientId": "your-client-id",
"versionId": "1"
}
}
}
response = requests.post(url, json=payload)
result = response.json()
print(f'Transaction hash: {result["txHash"]}')
return result
execute_transaction()curl -X POST "https://api.msa.omnes.tech/execute" \
-H "Content-Type: application/json" \
-d '{
"operations": [{
"walletCustody": 1,
"salt": "user@example.com",
"to": "0x097d4Aed5924e2172451973153Fc0e03407eD1F9",
"funcSignature": "transfer(address,uint256)",
"funcParams": [
"0xRecipientAddress...",
"1000000000000000000"
]
}],
"settings": {
"rpc": "https://rpc-amoy.polygon.technology/",
"factory": "0xb09Fd1134553a43A3E02182a6B04F4dEBa7476F4",
"validator": "0x9bD18Da66990F80d598dE02d5143dC9A4422eC3a",
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
"beaconAdminAddress": "0x99Ee7725D6a8f691d8B375e0aD33d1Aff2236618",
"signer": {
"clientId": "your-client-id",
"versionId": "1"
}
}
}'Batch Operations
Execute multiple operations in a single transaction:
// Multiple operations in the same account
const operations1: Operation = {
to: "0x097d4Aed5924e2172451973153Fc0e03407eD1F9",
value: BigInt(0),
funcSignature: "approve(address,uint256)",
params: ["0xSpenderAddress...", 1000000000000000000]
};
const operations2: Operation = {
to: "0xDeFiProtocol...",
value: BigInt(0),
funcSignature: "deposit(uint256)",
params: [1000000000000000000]
};
const operations3: Operation = {
to: "0xAnotherContract...",
value: BigInt(0),
funcSignature: "someFunction(address,uint256)",
params: ["0xAddress...", 123456]
};
const accountOperations: AccountOperations[] = [{
account: {
walletCustody: 1, // ECDSA_VALIDATOR
salt: "user@example.com",
publicKeys: []
},
operations: [operations1, operations2, operations3],
settings: {}
}];
const result = await smartWallet.buildAndRequestSendUserOperations(
accountOperations,
[],
[]
);Operation Types
Value Transfer (ETH/MATIC)
const operations: Operation = {
to: "0xRecipientAddress...",
value: BigInt(1000000000000000000), // 1 ETH in wei
funcSignature: "", // Empty for value transfer
params: []
};
const accountOperations: AccountOperations[] = [{
account: {
walletCustody: 1,
salt: "user@example.com",
publicKeys: []
},
operations: [operations],
settings: {}
}];
const result = await smartWallet.buildAndRequestSendUserOperations(
accountOperations,
[],
[]
);ERC-20 Token Operations
// Transfer
const transferOperation: Operation = {
to: tokenAddress,
value: BigInt(0),
funcSignature: "transfer(address,uint256)",
params: [recipient, amount]
};
// Approve
const approveOperation: Operation = {
to: tokenAddress,
value: BigInt(0),
funcSignature: "approve(address,uint256)",
params: [spender, amount]
};
const accountOperations: AccountOperations[] = [{
account: {
walletCustody: 1,
salt: "user@example.com",
publicKeys: []
},
operations: [transferOperation], // or [approveOperation]
settings: {}
}];
const result = await smartWallet.buildAndRequestSendUserOperations(
accountOperations,
[],
[]
);DeFi Operations
// Swap on DEX
const swapOperation: Operation = {
to: dexRouterAddress,
value: BigInt(0),
funcSignature: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
params: [
amountIn,
amountOutMin,
path,
recipient,
deadline
]
};
const accountOperations: AccountOperations[] = [{
account: {
walletCustody: 1,
salt: "user@example.com",
publicKeys: []
},
operations: [swapOperation],
settings: {}
}];
const result = await smartWallet.buildAndRequestSendUserOperations(
accountOperations,
[],
[]
);Response Format
The response follows this structure:
interface Response {
status: TxStatus;
txHash: Hash;
logs: Record<string, any>[];
cummulatedGasUsed: number;
gasUsed: number;
effectiveGasPrice: bigint;
errors: Record<string, any>[];
returnData: Record<string, any>[];
credits: number;
}Error Handling
try {
const result = await smartWallet.buildAndRequestSendUserOperations(
accountOperations,
[],
[]
);
if (result.response.status === 1) {
console.log('Transaction successful:', result.response.txHash);
} else {
console.error('Transaction failed:', result.response.errors);
}
} catch (error) {
if (error.message.includes('insufficient funds')) {
console.error('Wallet needs more funds');
} else if (error.message.includes('Invalid signature')) {
console.error('Check your signer configuration');
} else {
console.error('Execution failed:', error.message);
}
}Best Practices
- Estimate Gas First: Use gas estimation before execution
- Handle Errors: Always check status and errors
- Use Batch Operations: Group related operations
- Set Time Limits: Use validAfter/validUntil for time-sensitive ops
- Monitor Transactions: Track execution results
Next Steps
- Encoded Execution - Execute pre-encoded UserOperations
- Passkey Execution - Execute with passkey signatures
- Gas Estimation - Estimate transaction costs
✅ Ready to Execute! Your transactions are ready to be sent. Use batch operations to save gas and improve user experience.