Skip to main content

Action System

The Action System is a high-level, declarative API for building Chia blockchain transactions. Instead of manually constructing coin spends, puzzles, and solutions, you declare what you want to do using actions, and the system generates the appropriate spend bundles.

Overview

The action system solves several challenges in transaction construction:

  • Complexity reduction - No need to understand CLVM puzzles, conditions, and solutions directly
  • Transaction composition - Easily combine multiple operations in a single transaction
  • Asset tracking - Automatic handling of coin IDs, amounts, and asset types
  • Delta calculation - Automatic balancing of inputs and outputs across all assets

Key Types

TypeDescription
ActionA declarative operation (send, mint, issue, etc.)
SpendsOrchestrates actions and manages coin selection
DeltasTracks input/output requirements per asset
IdIdentifies assets (XCH, existing CAT/NFT, or newly created)
OutputsResults of a completed transaction

Asset Identification with Id

The Id type identifies assets in the action system:

use chia_wallet_sdk::prelude::*;

// Reference native XCH
let xch = Id::Xch;

// Reference an existing asset by its ID (CAT asset ID, NFT launcher ID, etc.)
let existing_cat = Id::Existing(asset_id);

// Reference a new asset created in the current transaction
// The index refers to the action that creates the asset
let new_asset = Id::New(0); // First action that creates an asset

Creating Actions

Actions are created using static factory methods on the Action class/enum.

Send Action

Send assets to a puzzle hash:

use chia_wallet_sdk::prelude::*;

// Send XCH
let send_xch = Action::send(Id::Xch, recipient_puzzle_hash, 1000, Memos::None);

// Send a CAT
let send_cat = Action::send(Id::Existing(asset_id), recipient_puzzle_hash, 500, memos);

// Send a newly created asset (from action at index 0)
let send_new = Action::send(Id::New(0), recipient_puzzle_hash, 100, memos);

// Burn assets (send to unspendable address)
let burn = Action::burn(Id::Xch, 1000, Memos::None);

Fee Action

Reserve XCH for transaction fees:

let fee = Action::fee(1000);

CAT Issuance Actions

Issue new Chia Asset Tokens:

// Single issuance CAT (genesis by coin ID - can only mint once)
let issue = Action::single_issue_cat(hidden_puzzle_hash, 1_000_000);

// Multi-issuance CAT with custom TAIL
let issue_with_tail = Action::issue_cat(tail_spend, hidden_puzzle_hash, 1_000_000);

NFT Actions

Mint and update NFTs:

// Mint an NFT
let mint = Action::mint_nft(
metadata, // NFT metadata (HashedPtr)
metadata_updater_puzzle, // Puzzle hash for metadata updates
royalty_puzzle_hash, // Where royalties are paid
royalty_basis_points, // Royalty percentage (300 = 3%)
amount, // Amount (usually 1)
);

// Mint an empty NFT with defaults
let mint_empty = Action::mint_empty_nft();

// Update an existing NFT's metadata
let update = Action::update_nft(
Id::Existing(launcher_id),
metadata_spends, // Spends that update metadata
transfer, // Optional transfer info
);

Using the Spends Orchestrator

The Spends struct orchestrates actions and manages the transaction building process.

Basic Workflow

use chia_wallet_sdk::prelude::*;

let ctx = &mut SpendContext::new();

// 1. Create Spends with a change puzzle hash
let mut spends = Spends::new(change_puzzle_hash);

// 2. Add coins to spend
spends.add(xch_coin);
spends.add_cat(cat);

// 3. Apply actions and get deltas
let deltas = spends.apply(ctx, &[
Action::send(Id::Xch, recipient, 500, Memos::None),
Action::fee(100),
])?;

// 4. Finish with signing keys
let outputs = spends.finish_with_keys(
ctx,
&deltas,
Relation::None,
&indexmap! { puzzle_hash => public_key },
)?;

// 5. Extract coin spends
let coin_spends = ctx.take();

Understanding Deltas

Deltas track the input/output requirements for each asset type in a transaction. This enables automatic coin selection and change calculation.

// Calculate deltas from actions
let deltas = Deltas::from_actions(&actions);

// Check requirements for a specific asset
if let Some(delta) = deltas.get(&Id::Xch) {
println!("XCH input needed: {}", delta.input);
println!("XCH output created: {}", delta.output);
}

// Check which assets are needed
for id in deltas.needed() {
println!("Need to provide: {:?}", id);
}

Working with Outputs

After completing a transaction, the Outputs struct provides access to the created assets:

// Get created XCH coins
for coin in &outputs.xch {
println!("Created XCH coin: {} mojos", coin.amount);
}

// Get created CATs by asset ID
for (id, cats) in &outputs.cats {
for cat in cats {
println!("Created CAT: {} of {:?}", cat.coin.amount, id);
}
}

// Access fee amount
println!("Fee: {} mojos", outputs.fee);

Complete Examples

Send XCH

use chia_wallet_sdk::prelude::*;

fn send_xch(
coin: Coin,
sender_pk: PublicKey,
recipient: Bytes32,
amount: u64,
fee: u64,
) -> Result<Vec<CoinSpend>, DriverError> {
let ctx = &mut SpendContext::new();
let sender_ph = StandardLayer::puzzle_hash(sender_pk);

let mut spends = Spends::new(sender_ph);
spends.add(coin);

let deltas = spends.apply(ctx, &[
Action::send(Id::Xch, recipient, amount, Memos::None),
Action::fee(fee),
])?;

let _outputs = spends.finish_with_keys(
ctx,
&deltas,
Relation::None,
&indexmap! { sender_ph => sender_pk },
)?;

Ok(ctx.take())
}

Issue and Send a CAT

use chia_wallet_sdk::prelude::*;

fn issue_and_send_cat(
coin: Coin,
sender_pk: PublicKey,
recipient: Bytes32,
issuance_amount: u64,
send_amount: u64,
) -> Result<(Bytes32, Vec<CoinSpend>), DriverError> {
let ctx = &mut SpendContext::new();
let sender_ph = StandardLayer::puzzle_hash(sender_pk);

let mut spends = Spends::new(sender_ph);
spends.add(coin);

// Issue CAT at index 0, then send from it
let deltas = spends.apply(ctx, &[
Action::single_issue_cat(None, issuance_amount),
Action::send(Id::New(0), recipient, send_amount, Memos::None),
])?;

let outputs = spends.finish_with_keys(
ctx,
&deltas,
Relation::None,
&indexmap! { sender_ph => sender_pk },
)?;

// Get the asset ID of the newly created CAT
let asset_id = outputs.cats.keys().next()
.and_then(|id| if let Id::New(0) = id { Some(*id) } else { None })
.expect("CAT should be created");

Ok((asset_id.into(), ctx.take()))
}

Mint and Update NFT

use chia_wallet_sdk::prelude::*;

fn mint_nft(
coin: Coin,
minter_pk: PublicKey,
metadata: NftMetadata,
royalty_puzzle_hash: Bytes32,
royalty_basis_points: u16,
) -> Result<(Bytes32, Vec<CoinSpend>), DriverError> {
let ctx = &mut SpendContext::new();
let minter_ph = StandardLayer::puzzle_hash(minter_pk);

let mut spends = Spends::new(minter_ph);
spends.add(coin);

let deltas = spends.apply(ctx, &[
Action::mint_nft(
ctx.alloc(&metadata)?.into(),
NFT_METADATA_UPDATER_PUZZLE_HASH.into(),
royalty_puzzle_hash,
royalty_basis_points,
1,
),
])?;

let outputs = spends.finish_with_keys(
ctx,
&deltas,
Relation::None,
&indexmap! { minter_ph => minter_pk },
)?;

let launcher_id = outputs.nfts.keys().next()
.and_then(|id| outputs.nfts.get(id))
.map(|nft| nft.info.launcher_id)
.expect("NFT should be created");

Ok((launcher_id, ctx.take()))
}

Action Types Reference

ActionDescriptionCreates Asset
sendTransfer assets to a puzzle hashNo
burnSend assets to unspendable addressNo
feeReserve XCH for transaction feesNo
single_issue_catIssue a single-issuance CATYes (Id::New)
issue_catIssue a multi-issuance CAT with TAILYes (Id::New)
run_tailExecute CAT TAIL logicNo
mint_nftMint a new NFTYes (Id::New)
update_nftUpdate NFT metadata or transferNo
create_didCreate a DIDYes (Id::New)
update_didUpdate DID metadataNo
settleSettle a payment with notarized paymentsNo
melt_singletonDestroy a singletonNo

Best Practices

  1. Use Id::New(index) for chained operations - When one action creates an asset and another action uses it, reference it by its action index
  2. Check deltas before applying - Use Deltas::from_actions() to verify you have sufficient coins before committing
  3. Handle change automatically - The action system calculates and creates change coins for you
  4. Batch related operations - Combine multiple actions in a single transaction to reduce fees
  5. Test with Simulator - Always validate transactions in the simulator before mainnet

Comparison with Low-Level API

FeatureAction SystemLow-Level (SpendContext)
ComplexityDeclarative, high-levelImperative, detailed
FlexibilityCovers common patternsFull control
Coin managementAutomaticManual
Change handlingAutomaticManual
Learning curveLowerHigher

Choose the action system for standard operations and the low-level API when you need precise control over puzzle construction.