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#
- Build: Construct transaction with desired operations
- Sign: Cryptographically sign with private key
- Submit: Send to network via ExecuteTransaction
- Consensus: Validators reach agreement
- Execute: Smart contracts run, state changes
- Finalize: Transaction included in checkpoint
Method Signature#
Service: sui.rpc.v2beta2.TransactionService
Method: ExecuteTransaction
Type: Unary RPC
Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction | Transaction | Yes | BCS-encoded transaction data |
signatures | repeated UserSignature | Yes | Cryptographic signatures |
read_mask | FieldMask | No | Response 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#
| Path | Description |
|---|---|
finality | Execution finality status |
transaction | Transaction data with digest |
effects | State changes and events |
checkpoint | Checkpoint inclusion |
timestamp | Execution time |
balance_changes | Balance 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 checkpointQUORUM_EXECUTED: Executed by validator quorum
Code Examples#
- TypeScript
- Python
- Go
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;
}
}
}
}
import grpc
import base64
from typing import Tuple
import transaction_service_pb2
import transaction_service_pb2_grpc
ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com'
API_TOKEN = 'your_api_token_here'
class TransactionExecutor:
def __init__(self, endpoint: str, api_token: str):
self.endpoint = endpoint
self.api_token = api_token
self.channel = None
self.client = None
def connect(self):
"""Establish gRPC connection"""
credentials = grpc.ssl_channel_credentials()
self.channel = grpc.secure_channel(self.endpoint, credentials)
self.client = transaction_service_pb2_grpc.TransactionServiceStub(self.channel)
def execute_transaction(
self,
tx_bytes: bytes,
signature: bytes,
public_key: bytes,
signature_scheme: int = 0 # 0 = ED25519
):
"""
Execute a signed transaction
Args:
tx_bytes: BCS-encoded transaction
signature: Cryptographic signature
public_key: Signer's public key
signature_scheme: 0=ED25519, 1=SECP256K1, 2=SECP256R1
Returns:
Execution response with effects and finality
"""
if not self.client:
self.connect()
metadata = [('x-api-key', self.api_token)]
# Build request
request = transaction_service_pb2.ExecuteTransactionRequest(
transaction=transaction_service_pb2.Transaction(bcs=tx_bytes),
signatures=[
transaction_service_pb2.UserSignature(
scheme=signature_scheme,
signature=signature,
public_key=public_key
)
],
read_mask=transaction_service_pb2.google_dot_protobuf_dot_field__mask__pb2.FieldMask(
paths=[
'finality',
'transaction',
'effects',
'checkpoint',
'timestamp_ms',
'balance_changes'
]
)
)
try:
response = self.client.ExecuteTransaction(
request,
metadata=metadata,
timeout=30.0
)
print('\n✓ Transaction Executed!')
print('=' * 60)
print(f'Digest: {response.transaction.digest}')
print(f'Finality: {["CERTIFIED", "CHECKPOINT", "QUORUM_EXECUTED"][response.finality]}')
print(f'Checkpoint: {response.checkpoint or "Pending"}')
print(f'Status: {response.transaction.effects.status.status}')
if response.balance_changes:
print('\nBalance Changes:')
for change in response.balance_changes:
addr = (change.owner.AddressOwner or
change.owner.ObjectOwner or
'Shared')
amount = int(change.amount) / 1_000_000_000
print(f' {addr}: {amount:+.9f} SUI')
return response
except grpc.RpcError as e:
print(f'Execution failed: {e.code()}: {e.details()}')
raise
def execute_with_retry(
self,
tx_bytes: bytes,
signature: bytes,
public_key: bytes,
max_retries: int = 3
):
"""Execute with automatic retry on transient failures"""
import time
for attempt in range(max_retries):
try:
return self.execute_transaction(tx_bytes, signature, public_key)
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.UNAVAILABLE and attempt < max_retries - 1:
wait_time = 2 ** attempt
print(f'Retry {attempt + 1}/{max_retries} after {wait_time}s...')
time.sleep(wait_time)
else:
raise
def close(self):
if self.channel:
self.channel.close()
# Usage example
def main():
executor = TransactionExecutor(ENDPOINT, API_TOKEN)
try:
# Transaction bytes (from Sui SDK)
tx_bytes = b'...' # Your BCS-encoded transaction
signature = b'...' # Signature from signing
public_key = b'...' # Signer's public key
result = executor.execute_transaction(
tx_bytes,
signature,
public_key
)
print(f'\nExecution completed in {result.timestamp_ms}ms')
except Exception as e:
print(f'Error: {e}')
finally:
executor.close()
if __name__ == '__main__':
main()
package main
import (
"context"
"fmt"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/fieldmaskpb"
pb "sui-grpc-client/sui/rpc/v2beta2"
)
const (
endpoint = "api-sui-mainnet-full.n.dwellir.com"
apiToken = "your_api_token_here"
)
type TransactionExecutor struct {
conn *grpc.ClientConn
client pb.TransactionServiceClient
}
func NewTransactionExecutor(endpoint, token string) (*TransactionExecutor, error) {
creds := credentials.NewClientTLSFromCert(nil, "")
conn, err := grpc.Dial(
endpoint,
grpc.WithTransportCredentials(creds),
grpc.WithBlock(),
grpc.WithTimeout(10*time.Second),
)
if err != nil {
return nil, fmt.Errorf("connection failed: %w", err)
}
return &TransactionExecutor{
conn: conn,
client: pb.NewTransactionServiceClient(conn),
}, nil
}
func (te *TransactionExecutor) ExecuteTransaction(
ctx context.Context,
txBytes []byte,
signature []byte,
publicKey []byte,
) (*pb.ExecuteTransactionResponse, error) {
ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiToken)
request := &pb.ExecuteTransactionRequest{
Transaction: &pb.Transaction{
Bcs: txBytes,
},
Signatures: []*pb.UserSignature{
{
Scheme: pb.SignatureScheme_ED25519,
Signature: signature,
PublicKey: publicKey,
},
},
ReadMask: &fieldmaskpb.FieldMask{
Paths: []string{
"finality",
"transaction",
"effects",
"checkpoint",
"timestamp_ms",
"balance_changes",
},
},
}
response, err := te.client.ExecuteTransaction(ctx, request)
if err != nil {
return nil, fmt.Errorf("execution failed: %w", err)
}
te.printResult(response)
return response, nil
}
func (te *TransactionExecutor) printResult(resp *pb.ExecuteTransactionResponse) {
finalityStates := []string{"CERTIFIED", "CHECKPOINT", "QUORUM_EXECUTED"}
fmt.Println("\n✓ Transaction Executed!")
fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Digest: %s\n", resp.Transaction.Digest)
fmt.Printf("Finality: %s\n", finalityStates[resp.Finality])
fmt.Printf("Checkpoint: %s\n", resp.Checkpoint)
fmt.Printf("Status: %s\n", resp.Transaction.Effects.Status.Status)
if len(resp.BalanceChanges) > 0 {
fmt.Println("\nBalance Changes:")
for _, change := range resp.BalanceChanges {
var addr string
if change.Owner.AddressOwner != "" {
addr = change.Owner.AddressOwner
} else {
addr = "Shared"
}
amount := float64(change.Amount) / 1_000_000_000
fmt.Printf(" %s: %+.9f SUI\n", addr, amount)
}
}
}
func (te *TransactionExecutor) Close() error {
return te.conn.Close()
}
func main() {
executor, err := NewTransactionExecutor(endpoint, apiToken)
if err != nil {
log.Fatalf("Failed to create executor: %v", err)
}
defer executor.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Your transaction data
txBytes := []byte{} // BCS-encoded transaction
signature := []byte{} // Signature bytes
publicKey := []byte{} // Public key bytes
_, err = executor.ExecuteTransaction(ctx, txBytes, signature, publicKey)
if err != nil {
log.Fatalf("Execution failed: %v", err)
}
}
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}`
};
}
}
}
Related Methods#
- SimulateTransaction - Test before execution
- GetTransaction - Verify execution results
Need help with transaction execution? Contact support@dwellir.com or check the gRPC overview.