Skip to content

Deploying Contracts

This guide covers the complete workflow for deploying smart contracts to Ashen, from building to verification.

Terminal window
# 1. Build the contract
node contract build --manifest-path contracts/my_contract/Cargo.toml
# 2. Bundle with IDL
node contract bundle \
--elf target/riscv64imac-unknown-none-elf/release/my_contract \
--idl contracts/my_contract/my_contract.idl \
--out ./my_contract.bundle
# 3. Deploy
node contract deploy \
--bundle ./my_contract.bundle \
--key $ASHEN_PRIVATE_KEY \
--wait

  • Rust with the riscv64imac-unknown-none-elf target
  • A funded account for deployment gas fees
  • Your private key (ed25519 hex or keystore reference)
Terminal window
# Install RISC-V target
rustup target add riscv64imac-unknown-none-elf
# Generate a keypair if needed
node keygen > ./dev.key.json
export ASHEN_PRIVATE_KEY=@./dev.key.json

Terminal window
node contract build --manifest-path contracts/my_contract/Cargo.toml
# With custom output path
node contract build \
--manifest-path contracts/my_contract/Cargo.toml \
--out-elf ./my_contract.elf
# With debug info (for tracing)
node contract build \
--manifest-path contracts/my_contract/Cargo.toml \
--debuginfo
# Verbose output
node contract build \
--manifest-path contracts/my_contract/Cargo.toml \
--verbose

Output: target/riscv64imac-unknown-none-elf/release/<contract_name>

Zig contracts use the Ashen SDK and compile via zig build:

Terminal window
cd contracts/my_zig_contract
zig build -Doptimize=ReleaseSmall

Output: zig-out/bin/<contract_name>

OptionDescription
--manifest-pathPath to Cargo.toml
--targetTarget triple (default: riscv64imac-unknown-none-elf)
--releaseRelease build (default: true)
--debuginfoPreserve DWARF debug info for tracing
--binBinary name (if multiple in workspace)
--out-elfCustom output path
--verboseShow build commands

Every contract needs an IDL (Interface Definition Language) file describing its methods, types, and events.

my_contract.idl
interface MyContract {
// View method (read-only)
fn balance_of(account: Address) -> u128;
// Mutating method
fn transfer(to: Address, amount: u128) -> ();
// Events
event Transfer {
from: Address indexed,
to: Address indexed,
amount: u128,
}
}

If using the Rust SDK with #[contract] macros:

Terminal window
cargo run -p idl-abi-gen -- \
--idl contracts/my_contract/my_contract.idl \
--out-dir contracts/my_contract/src \
--rust-contract
Terminal window
ashen idl validate ./my_contract.idl

Bundle the compiled ELF with its IDL for deployment:

Terminal window
node contract bundle \
--elf target/riscv64imac-unknown-none-elf/release/my_contract \
--idl contracts/my_contract/my_contract.idl \
--out ./my_contract.bundle

Specify upgrade policy at bundle time:

Terminal window
# Immutable (cannot be upgraded)
node contract bundle ... --upgrade-policy immutable
# Owner-upgradeable (default)
node contract bundle ... --upgrade-policy owner
# Governance-upgradeable
node contract bundle ... --upgrade-policy governance

Terminal window
node contract deploy \
--bundle ./my_contract.bundle \
--key $ASHEN_PRIVATE_KEY
Terminal window
node contract deploy \
--bundle ./my_contract.bundle \
--key $ASHEN_PRIVATE_KEY \
--wait \
--wait-timeout-s 120
Terminal window
node contract deploy \
--bundle ./my_contract.bundle \
--key $ASHEN_PRIVATE_KEY \
--max-fee 5000000 \
--gas-limit 2000000 \
--rpc-url http://custom-node:3030 \
--wait
OptionDefaultDescription
--bundlePath to contract bundle
--elfAlternative: path to ELF (without bundle)
--idlAlternative: path to IDL (with —elf)
--key$ASHEN_PRIVATE_KEYSigning key
--max-fee1000000Maximum fee to pay
--gas-limit1000000Gas limit for deployment
--rpc-urlhttp://127.0.0.1:3030RPC endpoint
--auth-tokenOptional auth token
--waitfalseWait for inclusion
--wait-timeout-s60Timeout when waiting
--upgrade-policyownerUpgrade policy

The interactive TUI provides a visual deployment experience:

Terminal window
# Launch TUI
ashen
# Navigate to Deploy screen (press 'd')
# Select bundle file
# Review deployment parameters
# Submit and monitor progress
  • Bundle Selection — Browse and select bundle files
  • Parameter Editor — Adjust gas, fees, upgrade policy
  • Simulation — Preview deployment before submission
  • Progress Tracking — Watch deployment confirmation in real-time
  • History — Track all your deployments

For CI/CD pipelines and agent automation, use the CLI with structured output.

#!/bin/bash
set -e
CONTRACT_PATH="contracts/my_contract"
BUNDLE_PATH="./my_contract.bundle"
# Build
echo "Building contract..."
node contract build --manifest-path "$CONTRACT_PATH/Cargo.toml"
# Bundle
echo "Creating bundle..."
node contract bundle \
--elf "target/riscv64imac-unknown-none-elf/release/my_contract" \
--idl "$CONTRACT_PATH/my_contract.idl" \
--out "$BUNDLE_PATH"
# Deploy
echo "Deploying..."
RESULT=$(node contract deploy \
--bundle "$BUNDLE_PATH" \
--key "$ASHEN_PRIVATE_KEY" \
--wait 2>&1)
# Extract contract address from output
CONTRACT_ADDR=$(echo "$RESULT" | grep -oP 'Contract deployed at: \K0x[a-fA-F0-9]+')
echo "Contract deployed at: $CONTRACT_ADDR"
# Verify deployment
ashen abi "$CONTRACT_ADDR"
#!/bin/bash
# Deployment workflow using robot mode for structured output
# 1. Deploy and capture result
echo "Deploying contract..."
DEPLOY_RESULT=$(node contract deploy \
--bundle ./my_contract.bundle \
--key "$ASHEN_PRIVATE_KEY" \
--wait 2>&1)
# Parse deployment output for contract address
CONTRACT_ADDR=$(echo "$DEPLOY_RESULT" | grep -oP '0x[a-fA-F0-9]{64}' | head -1)
if [ -z "$CONTRACT_ADDR" ]; then
echo "Deployment failed"
exit 1
fi
echo "Deployed to: $CONTRACT_ADDR"
# 2. Verify the contract has code
ACCOUNT=$(ashen tui account "$CONTRACT_ADDR" --json)
if ! echo "$ACCOUNT" | jq -e '.ok' > /dev/null; then
echo "Failed to query contract account"
exit 1
fi
# 3. Test a view call
VIEW_RESULT=$(ashen tui view \
--contract "$CONTRACT_ADDR" \
--method total_supply \
--json)
if echo "$VIEW_RESULT" | jq -e '.ok' > /dev/null; then
SUPPLY=$(echo "$VIEW_RESULT" | jq -r '.data.result')
echo "Initial total supply: $SUPPLY"
else
echo "View call failed: $(echo "$VIEW_RESULT" | jq -r '.message')"
exit 1
fi
# 4. Execute initialization (if needed)
INIT_RESULT=$(ashen tui call \
--contract "$CONTRACT_ADDR" \
--method initialize \
--args '["MyToken", "MTK", 18]' \
--key "$ASHEN_PRIVATE_KEY" \
--submit --wait --json)
if echo "$INIT_RESULT" | jq -e '.ok' > /dev/null; then
TX_HASH=$(echo "$INIT_RESULT" | jq -r '.data.tx_hash')
echo "Initialized: $TX_HASH"
else
echo "Initialization failed: $(echo "$INIT_RESULT" | jq -r '.message')"
exit 1
fi
echo "Deployment complete!"
echo "Contract: $CONTRACT_ADDR"
#!/bin/bash
# Deploy multiple contracts with dependencies
declare -A CONTRACTS
CONTRACTS=(
["token"]="contracts/token"
["pool"]="contracts/amm_pool"
["router"]="contracts/dex_router"
)
declare -A ADDRESSES
for name in token pool router; do
path="${CONTRACTS[$name]}"
echo "Deploying $name..."
# Build
node contract build --manifest-path "$path/Cargo.toml"
# Bundle
node contract bundle \
--elf "target/riscv64imac-unknown-none-elf/release/$name" \
--idl "$path/$name.idl" \
--out "./$name.bundle"
# Deploy
RESULT=$(node contract deploy \
--bundle "./$name.bundle" \
--key "$ASHEN_PRIVATE_KEY" \
--wait 2>&1)
ADDR=$(echo "$RESULT" | grep -oP '0x[a-fA-F0-9]{64}' | head -1)
ADDRESSES[$name]="$ADDR"
echo "$name deployed at: $ADDR"
done
# Initialize router with token and pool addresses
ashen tui call \
--contract "${ADDRESSES[router]}" \
--method set_addresses \
--args "[\"${ADDRESSES[token]}\", \"${ADDRESSES[pool]}\"]" \
--key "$ASHEN_PRIVATE_KEY" \
--submit --wait --json
echo "Deployment complete!"
for name in "${!ADDRESSES[@]}"; do
echo " $name: ${ADDRESSES[$name]}"
done

Terminal window
# Check that contract has code
ashen account --address $CONTRACT_ADDR
# Fetch and inspect IDL
ashen abi $CONTRACT_ADDR
ashen abi $CONTRACT_ADDR --raw > deployed.idl
# Compare with source IDL
ashen idl diff ./my_contract.idl ./deployed.idl
Terminal window
# View call (read-only)
ashen view $CONTRACT_ADDR total_supply
# With JSON output for automation
ashen tui view --contract $CONTRACT_ADDR --method total_supply --json
Terminal window
# Get deployment tx details
ashen tx by-hash --tx-hash $DEPLOY_TX_HASH
# List recent transactions to the contract
ashen tui tx list --contract $CONTRACT_ADDR --json

“insufficient balance”

  • Fund your account before deployment
  • Check: ashen account --address $YOUR_ADDRESS

“gas limit exceeded”

  • Increase --gas-limit (default: 1,000,000)
  • Complex contracts may need 2-5 million gas

“max fee exceeded”

  • Increase --max-fee
  • Check current gas prices: ashen status

“contract already exists”

  • Contract addresses are deterministic (sender + nonce)
  • Use a different nonce lane or wait for previous tx

“invalid IDL”

  • Validate: ashen idl validate ./my_contract.idl
  • Check for syntax errors and type mismatches
Terminal window
# Trace the deployment transaction
ashen debug trace --tx-hash $FAILED_TX_HASH
# Check execution logs
ashen tx by-hash --tx-hash $FAILED_TX_HASH --wait

  1. Always test locally first

    Terminal window
    just devnet-node init --alloc "$ADDR=1000000000000"
    just node
    # Deploy to local node before testnet/mainnet
  2. Use --wait for reliable scripting

    • Without --wait, you only get the tx hash
    • With --wait, you confirm inclusion and get the contract address
  3. Version your IDLs

    • Commit IDL files alongside contract source
    • Use ashen idl diff to track breaking changes
  4. Set appropriate gas limits

    • Start with 1M gas, increase if deployment fails
    • Large contracts may need 5-10M gas
  5. Consider upgrade policy carefully

    • immutable: Most secure, no upgrades possible
    • owner: Flexible, owner can upgrade
    • governance: Requires on-chain governance approval