Skip to main content

Overview

Transaction execution is the process of sending operations to the Starknet blockchain. Starkzap provides multiple ways to execute transactions, from simple contract calls to complex batched operations. What is a transaction? A transaction is a request to execute a function on a smart contract deployed on Starknet. Think of it like calling an API endpoint—you’re invoking a function on a deployed contract that performs some action (like transferring tokens, updating state, or executing business logic). Requirements:
  • A smart contract must be deployed on Starknet before you can execute transactions against it
  • Your wallet account must be deployed (the SDK handles this automatically with deploy: "if_needed")
  • Sufficient balance to pay for gas fees (unless using a paymaster for sponsored transactions)
Want to deploy your own smart contracts? This guide focuses on executing transactions against existing contracts. If you need to create and deploy your own smart contracts, check out the Starknet Quickstart Guide for comprehensive tutorials on smart contract development with Cairo.
This guide covers all transaction execution patterns for interacting with deployed smart contracts.

Direct Execution

Use wallet.execute() for arbitrary contract calls:
import type { Call } from "starknet";

const call: Call = {
  contractAddress: "0xCONTRACT_ADDRESS",
  entrypoint: "transfer",
  calldata: ["0xRECIPIENT", "1000000", "0"],
};

const tx = await wallet.execute([call]);
await tx.wait();

Multiple Calls (Atomic Batching)

Multiple calls execute atomically in a single transaction:
const tx = await wallet.execute([approveCall, swapCall, transferCall]);
await tx.wait();
All calls succeed or fail together — no partial execution.

Transaction Tracking

Every execute(), deploy(), or transfer() call returns a Tx object:
const tx = await wallet.execute(calls);

// Transaction hash
console.log(tx.hash); // "0x..."

// Block explorer link
console.log(tx.explorerUrl); // "https://voyager.online/tx/0x..."

// Wait for confirmation (L2 acceptance)
await tx.wait();

// Wait for L1 finality
await tx.wait({
  successStates: [TransactionFinalityStatus.ACCEPTED_ON_L1],
});

// Get full receipt
const receipt = await tx.receipt();
console.log(receipt.actual_fee);

// Watch status changes in real-time
const unsubscribe = tx.watch(({ finality, execution }) => {
  console.log(`Status: ${finality} (${execution})`);
  // "RECEIVED" → "ACCEPTED_ON_L2" → "ACCEPTED_ON_L1"
});

// Stop watching early if needed
unsubscribe();
If the SDK is configured with a paymaster, pass feeMode: "sponsored":
// Per-transaction sponsorship
const tx = await wallet.execute([call], { feeMode: "sponsored" });

// Or set as default when connecting
const wallet = await sdk.connectWallet({
  account: { signer },
  feeMode: "sponsored",
});

// Now all transactions are sponsored by default
const tx = await wallet.execute([call]); // sponsored automatically
The SDK works seamlessly with AVNU Paymaster for sponsored transactions. See the AVNU Paymaster Integration guide for setup instructions.
Cartridge Controller: If you’re using Cartridge Controller as your wallet strategy, transactions are automatically sponsored by Cartridge’s built-in paymaster. You don’t need to configure AVNU Paymaster separately or set feeMode: "sponsored"—Cartridge handles all gas fees automatically when users approve policies. See the Cartridge Controller Integration guide for more details.

Preflight Simulation

Simulate transactions before sending to check for errors:
const result = await wallet.preflight({ calls });
if (!result.ok) {
  console.error(result.reason);
} else {
  const tx = await wallet.execute(calls);
  await tx.wait();
}
Always preflight non-trivial batches before submitting to catch errors early.

Transaction Builder

For batching multiple operations into a single atomic transaction, use the TxBuilder. This is especially useful for complex operations that need to execute together.
For detailed TxBuilder documentation, see the Tx Builder guide which covers all builder methods, preflight simulation, fee estimation, and best practices.
Quick example:
const tx = await wallet
  .tx()
  .transfer(USDC, { to: alice, amount })
  .stake(poolAddress, stakeAmount)
  .send();
await tx.wait();

Fee Modes

User Pays (Default)

const tx = await wallet.execute(calls, { feeMode: "user_pays" });
The user’s account pays for transaction fees.
const tx = await wallet.execute(calls, { feeMode: "sponsored" });
The paymaster covers transaction fees. Requires paymaster configuration in SDK.
Cartridge Controller users: If you’re using Cartridge Controller as your wallet strategy, transactions are automatically sponsored by Cartridge’s built-in paymaster. You don’t need to configure AVNU Paymaster separately—Cartridge handles all gas fees automatically when users approve policies. See the Cartridge Controller Integration guide for more details.

Transaction Status

Transactions go through several states:
  1. RECEIVED - Transaction received by the sequencer
  2. ACCEPTED_ON_L2 - Transaction accepted on L2 (most common success state)
  3. ACCEPTED_ON_L1 - Transaction finalized on L1
// Wait for L2 acceptance (default)
await tx.wait();

// Wait for L1 finality
await tx.wait({
  successStates: [TransactionFinalityStatus.ACCEPTED_ON_L1],
});

Error Handling

Always handle transaction errors:
try {
  const tx = await wallet.execute(calls);
  await tx.wait();
  console.log("Transaction successful!");
} catch (error) {
  if (error instanceof TransactionRejectedError) {
    console.error("Transaction rejected:", error.reason);
  } else {
    console.error("Unexpected error:", error);
  }
}

Best Practices

  1. Always preflight non-trivial batches before submitting
  2. Use the transaction builder for complex operations to save gas
  3. Handle errors gracefully and provide user feedback
  4. Standardize fee behavior (user_pays vs sponsored) per flow
  5. Wait for appropriate confirmation based on your use case

Next Steps