Skip to main content

ExecuteTransaction

Submit Transactions to Sui Blockchain#

The ExecuteTransaction method submits cryptographically signed transactions to the Sui network for execution and consensus. This is the primary method for committing state changes to the blockchain, from simple token transfers to complex smart contract interactions.

Overview#

Every state modification on Sui requires a signed transaction. The ExecuteTransaction method takes your prepared and signed transaction, broadcasts it to validators, processes it through consensus, and returns the execution results. Understanding this method is essential for any application that modifies blockchain state.

Transaction Lifecycle#

  1. Build: Construct transaction with desired operations
  2. Sign: Cryptographically sign with private key
  3. Submit: Send to network via ExecuteTransaction
  4. Consensus: Validators reach agreement
  5. Execute: Smart contracts run, state changes
  6. Finalize: Transaction included in checkpoint

Method Signature#

Service: sui.rpc.v2beta2.TransactionService Method: ExecuteTransaction Type: Unary RPC

Parameters#

ParameterTypeRequiredDescription
transactionTransactionYesBCS-encoded transaction data
signaturesrepeated UserSignatureYesCryptographic signatures
read_maskFieldMaskNoResponse fields to include

Transaction Structure#

message Transaction {
bytes bcs = 1; // Binary Canonical Serialization of transaction
}

UserSignature Structure#

message UserSignature {
SignatureScheme scheme = 1; // ED25519 = 0, SECP256K1 = 1, SECP256R1 = 2
bytes signature = 2; // Signature bytes
bytes public_key = 3; // Public key bytes
}

Field Mask Options#

PathDescription
finalityExecution finality status
transactionTransaction data with digest
effectsState changes and events
checkpointCheckpoint inclusion
timestampExecution time
balance_changesBalance modifications

Response Structure#

message ExecuteTransactionResponse {
Finality finality = 1;
ExecutedTransaction transaction = 2;
string checkpoint = 3;
uint64 timestamp_ms = 4;
}

Finality Values#

  • CERTIFIED: Transaction signed by quorum (fast)
  • CHECKPOINT: Included in finalized checkpoint
  • QUORUM_EXECUTED: Executed by validator quorum

Code Examples#

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import { TransactionBlock } from '@mysten/sui.js/transactions';

const ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com';
const API_TOKEN = 'your_api_token_here';

const packageDefinition = protoLoader.loadSync(
'./protos/transaction.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['./protos']
}
);

const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const credentials = grpc.credentials.createSsl();
const client = new protoDescriptor.sui.rpc.v2beta2.TransactionService(
ENDPOINT,
credentials
);

const metadata = new grpc.Metadata();
metadata.add('x-api-key', API_TOKEN);

// Execute a signed transaction
async function executeTransaction(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array
): Promise<any> {
return new Promise((resolve, reject) => {
const request = {
transaction: {
bcs: transactionBytes
},
signatures: [{
scheme: 0, // ED25519
signature: signature,
public_key: publicKey
}],
read_mask: {
paths: [
'finality',
'transaction',
'effects',
'checkpoint',
'timestamp_ms',
'balance_changes'
]
}
};

client.ExecuteTransaction(request, metadata, (error: any, response: any) => {
if (error) {
console.error('Execution failed:', error.message);
reject(error);
return;
}

resolve(response);
});
});
}

// Complete example: Build, sign, and execute
async function transferSUI(
sender: string,
recipient: string,
amount: string,
keypair: Ed25519Keypair
): Promise<void> {
// 1. Build transaction
const tx = new TransactionBlock();
const [coin] = tx.splitCoins(tx.gas, [tx.pure(amount)]);
tx.transferObjects([coin], tx.pure(recipient));
tx.setSender(sender);
tx.setGasPrice(495);
tx.setGasBudget(10000000);

// 2. Get transaction bytes
const txBytes = await tx.build({ client: suiClient });

// 3. Sign transaction
const signatureData = await keypair.signTransactionBlock(txBytes);

// 4. Execute via gRPC
console.log('Submitting transaction...');
const result = await executeTransaction(
txBytes,
signatureData.signature,
keypair.getPublicKey().toBytes()
);

console.log('\n✓ Transaction Executed!');
console.log('==================');
console.log('Digest:', result.transaction.digest);
console.log('Finality:', ['CERTIFIED', 'CHECKPOINT', 'QUORUM_EXECUTED'][result.finality]);
console.log('Checkpoint:', result.checkpoint || 'Pending');
console.log('Status:', result.transaction.effects.status.status);

if (result.balance_changes) {
console.log('\nBalance Changes:');
result.balance_changes.forEach((change: any) => {
const addr = change.owner.AddressOwner || change.owner.ObjectOwner;
const amount = parseInt(change.amount) / 1_000_000_000;
console.log(` ${addr}: ${amount > 0 ? '+' : ''}${amount} SUI`);
});
}
}

// Error handling wrapper
async function safeExecuteTransaction(
txBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
maxRetries: number = 3
): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await executeTransaction(txBytes, signature, publicKey);
} catch (error: any) {
if (attempt === maxRetries - 1) throw error;

if (error.code === grpc.status.UNAVAILABLE) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}

Use Cases#

1. Token Transfers#

Execute SUI or custom token transfers:

async function executeTokenTransfer(
from: string,
to: string,
amount: string,
coinType: string,
keypair: any
): Promise<string> {
const tx = new TransactionBlock();

if (coinType === '0x2::sui::SUI') {
const [coin] = tx.splitCoins(tx.gas, [tx.pure(amount)]);
tx.transferObjects([coin], tx.pure(to));
} else {
// Custom token transfer logic
}

tx.setSender(from);
const txBytes = await tx.build({ client });
const { signature, publicKey } = await keypair.signTransactionBlock(txBytes);

const result = await executeTransaction(txBytes, signature, publicKey.toBytes());

return result.transaction.digest;
}

2. Smart Contract Interaction#

Call Move functions:

async function callSmartContract(
packageId: string,
module: string,
function_name: string,
args: any[],
keypair: any
): Promise<any> {
const tx = new TransactionBlock();

tx.moveCall({
target: `${packageId}::${module}::${function_name}`,
arguments: args.map(arg => tx.pure(arg))
});

const txBytes = await tx.build({ client });
const { signature, publicKey } = await keypair.signTransactionBlock(txBytes);

return await executeTransaction(txBytes, signature, publicKey.toBytes());
}

3. NFT Minting#

Mint and transfer NFTs:

async function mintNFT(
name: string,
description: string,
imageUrl: string,
recipient: string,
keypair: any
): Promise<string> {
const tx = new TransactionBlock();

tx.moveCall({
target: `${NFT_PACKAGE}::nft::mint`,
arguments: [
tx.pure(name),
tx.pure(description),
tx.pure(imageUrl),
tx.pure(recipient)
]
});

const txBytes = await tx.build({ client });
const { signature, publicKey } = await keypair.signTransactionBlock(txBytes);

const result = await executeTransaction(txBytes, signature, publicKey.toBytes());

console.log(`✓ NFT minted: ${result.transaction.digest}`);

return result.transaction.digest;
}

Best Practices#

1. Always Verify Signatures#

Ensure signatures are valid before submission:

function validateSignature(
txBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array
): boolean {
// Implement signature verification
// Use cryptographic library to verify
return true; // Placeholder
}

2. Handle Gas Appropriately#

Set reasonable gas budgets:

tx.setGasBudget(10_000_000);  // 0.01 SUI
tx.setGasPrice(495); // Current reference gas price

3. Implement Retry Logic#

Handle transient network failures:

async function executeWithRetry(
txBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
maxAttempts: number = 3
): Promise<any> {
let lastError;

for (let i = 0; i < maxAttempts; i++) {
try {
return await executeTransaction(txBytes, signature, publicKey);
} catch (error) {
lastError = error;
if (i < maxAttempts - 1) {
await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1)));
}
}
}

throw lastError;
}

4. Monitor Finality#

Track transaction finality status:

async function waitForFinality(
digest: string,
desiredFinality: 'CERTIFIED' | 'CHECKPOINT' = 'CHECKPOINT'
): Promise<void> {
const maxWait = 60000; // 60 seconds
const start = Date.now();

while (Date.now() - start < maxWait) {
const tx = await getTransaction(digest);

if (desiredFinality === 'CERTIFIED' && tx.finality >= 0) {
return;
}

if (desiredFinality === 'CHECKPOINT' && tx.finality >= 1) {
return;
}

await new Promise(resolve => setTimeout(resolve, 1000));
}

throw new Error('Finality timeout');
}

Error Handling#

Common execution errors:

async function safeExecute(
txBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array
): Promise<{ success: boolean; result?: any; error?: string }> {
try {
const result = await executeTransaction(txBytes, signature, publicKey);

if (result.transaction.effects.status.status !== 'success') {
return {
success: false,
error: result.transaction.effects.status.error || 'Transaction failed'
};
}

return { success: true, result };

} catch (error: any) {
switch (error.code) {
case grpc.status.INVALID_ARGUMENT:
return {
success: false,
error: 'Invalid transaction or signature format'
};

case grpc.status.RESOURCE_EXHAUSTED:
return {
success: false,
error: 'Insufficient gas or rate limit exceeded'
};

case grpc.status.UNAVAILABLE:
return {
success: false,
error: 'Network temporarily unavailable. Retry later.'
};

default:
return {
success: false,
error: `Execution failed: ${error.message}`
};
}
}
}

Need help with transaction execution? Contact support@dwellir.com or check the gRPC overview.