Skip to main content

RGB Rust SDK

The RGB Rust SDK provides the core libraries for building RGB applications. This guide covers installation, basic usage, and the complete Contract API for Rust developers.

Installation​

Adding Dependencies​

Add RGB libraries to your Cargo.toml:

[dependencies]
# Line A (RGB-WG v0.12): only release candidates exist, so pin them exactly.
# Don't add `rgb = ...`: the crate called `rgb` is an unrelated pixel-colour library.
rgb-runtime = "=0.12.0-rc.3"
rgb-std = "=0.12.0-rc.3"
rgb-core = "=0.12.0"
rgb-persist-fs = { version = "=0.12.0-rc.3", optional = true }
rgb-psbt = "=0.12.0-rc.3"

[features]
fs = ["rgb-persist-fs"]
serde = ["rgb-std/serde"]

Platform Requirements​

  • Rust 1.75 or later
  • Bitcoin Core (for testnet/mainnet)
  • 64-bit architecture (required for strict types)

Core Types and Concepts​

Contract Type​

The central type for working with RGB contracts is Contract<S, P>:

use rgb::{Contract, Stock, Pile, RgbSeal};

pub struct Contract<S: Stock, P: Pile> {
contract_id: ContractId,
ledger: Ledger<S>,
pile: P,
}

Type Parameters:

  • S: Stock - Persistent storage backend for contract state
  • P: Pile - Seal and witness management

Stock​

Storage abstraction for contract operations and state:

use rgbstd::persistence::Stock;

// In-memory stock (for testing)
let stock = Stock::in_memory();

// File-backed stock (for production)
use rgb_persist_fs::StockpileDir;
let stock = Stock::load(StockpileDir::new("/path/to/data")?)?;

Pile​

Manages UTXO bindings and witnesses:

pub trait Pile {
type Seal: RgbSeal;
type Conf;
type Error: Error;

fn seal(&self, addr: CellAddr) -> Option<Self::Seal>;
fn add_seals(&mut self, opid: Opid, seals: SmallOrdMap<u16, Self::Seal::Definition>);
fn witness_status(&self, wid: Self::Seal::WitnessId) -> WitnessStatus;
// ... more methods
}

Complete Contract API​

Initialization and Loading​

Create from Consignment​

use rgb::{Contract, Articles, Consignment};

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn with(
articles: Articles,
consignment: Consignment<P::Seal>,
conf: S::Conf,
) -> Result<Self, MultiError<ConsumeError<P::Seal::Definition>, S::Error, P::Error>>
where
P::Conf: From<S::Conf>,
<P::Seal as RgbSeal>::Definition: StrictDecode,
<P::Seal as RgbSeal>::Client: StrictDecode,
<P::Seal as RgbSeal>::Published: StrictDecode,
{
// Create contract from articles and consignment
// Validates entire history during import
}
}

Example:

let articles = load_articles("contract_articles.rgb")?;
let consignment = load_consignment("transfer.consignment")?;
let config = StockConfig::default();

let contract = Contract::with(articles, consignment, config)?;
println!("Contract loaded: {}", contract.contract_id());

Issue New Contract​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn issue(
issuer: Issuer,
params: CreateParams<P::Seal::Definition>,
conf: impl FnOnce(&Articles) -> Result<S::Conf, S::Error>,
) -> Result<Self, MultiError<IssuerError, S::Error, P::Error>>
where
P::Conf: From<S::Conf>,
{
// Issue new contract genesis
}
}

Example:

use rgb::{Issuer, CreateParams, Consensus};

let issuer = Issuer::new(identity, private_key);
let params = CreateParams::new_testnet(
"ssi:issuer",
Consensus::Bitcoin,
"RGB20Token",
);

let contract = Contract::issue(
issuer,
params,
|articles| Ok(StockConfig::for_contract(articles)),
)?;

Load Existing Contract​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn load(
stock_conf: S::Conf,
pile_conf: P::Conf,
) -> Result<Self, MultiError<S::Error, P::Error>> {
// Load contract from persistent storage
}
}

Example:

let stock_config = StockConfig::load("/path/to/stock")?;
let pile_config = PileConfig::load("/path/to/pile")?;

let contract = Contract::load(stock_config, pile_config)?;

Contract Information​

Contract ID​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn contract_id(&self) -> ContractId {
self.contract_id
}
}

Example:

let contract_id = contract.contract_id();
println!("Contract: {}", contract_id.to_string());
// Output: rgb:pOIzGFyQ-mA!yQq2-QH8vB5!-5fAplY!-x2lW!vz-JHDbYPg

Articles​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn articles(&self) -> &Articles {
self.ledger.articles()
}
}

Articles contain:

  • Schema definition
  • Interface implementations
  • Genesis operation
  • Issuer signature

Example:

let articles = contract.articles();
println!("Schema: {}", articles.schema().name);
println!("Genesis: {}", articles.genesis_opid());

State Queries​

Get Contract State​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn state(&self) -> ContractState<P::Seal> {
// Returns current contract state with seal resolution
}
}

ContractState Structure:

pub struct ContractState<Seal> {
pub immutable: BTreeMap<StateName, Vec<ImmutableState>>,
pub owned: BTreeMap<StateName, Vec<OwnedState<Seal>>>,
pub aggregated: BTreeMap<StateName, StrictVal>,
}

Example:

let state = contract.state();

// Query global state
for (name, states) in state.immutable {
for state in states {
println!("Global {}: {:?}", name, state.data);
}
}

// Query owned state
for (name, states) in state.owned {
for owned in states {
println!("Owned {}: seal={}, value={:?}, status={:?}",
name,
owned.assignment.seal,
owned.assignment.data,
owned.status
);
}
}

// Query aggregated state
for (name, value) in state.aggregated {
println!("Aggregated {}: {:?}", name, value);
}

Full State (Raw)​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn full_state(&self) -> &EffectiveState {
self.ledger.state()
}
}

Example:

let full_state = contract.full_state();
println!("State hash: {}", full_state.state_hash());

Seal Queries​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn seal(&self, seal: &P::Seal::Definition) -> Option<CellAddr> {
// Find state assignment for a seal
}
}

Example:

let seal_def = TxoSeal::new(outpoint, method);
if let Some(addr) = contract.seal(&seal_def) {
println!("Seal controls state at: opid={}, output={}",
addr.opid, addr.ty);
}

Operations​

List Operations​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn operations(
&self,
) -> impl Iterator<Item = (Opid, Operation, OpRels<P::Seal>)> + use<'_, S, P> {
// Iterate all operations (excluding genesis)
}
}

Example:

for (opid, operation, rels) in contract.operations() {
println!("Operation {}", opid);
println!(" Inputs: {}", operation.inputs.len());
println!(" Outputs: {}", operation.owned.len());
println!(" Seals: {:?}", rels.seals);
}

Trace History​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn trace(&self) -> impl Iterator<Item = (Opid, Transition)> + use<'_, S, P> {
// Trace transition history
}
}

Example:

for (opid, transition) in contract.trace() {
println!("Transition {}: method={}", opid, transition.method);
}

Operation Relations​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn op_seals(&self, opid: Opid, up_to: u16) -> OpRels<P::Seal> {
// Get seal relationships for operation
}
}

Witnesses​

List Witness IDs​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn witness_ids(
&self,
) -> impl Iterator<Item = P::Seal::WitnessId> + use<'_, S, P> {
// All witness IDs in contract
}
}

List Witnesses​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn witnesses(&self) -> impl Iterator<Item = Witness<P::Seal>> + use<'_, S, P> {
// All witnesses with status
}
}

Example:

for witness in contract.witnesses() {
println!("Witness {}: status={:?}",
witness.id(),
witness.status
);
}

Query by Witness​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn ops_by_witness_id(
&self,
wid: P::Seal::WitnessId,
) -> impl Iterator<Item = Opid> + use<'_, S, P> {
// All operations using this witness
}
}

Example:

let witness_id = TxWitness::from(txid);
for opid in contract.ops_by_witness_id(witness_id) {
println!("Operation {} uses witness {}", opid, witness_id);
}

State Transitions​

Create State Transition (Call)​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn call(
&mut self,
call: CallParams,
seals: SmallOrdMap<u16, P::Seal::Definition>,
) -> Result<Operation, MultiError<AcceptError, S::Error>> {
// Create and apply new state transition
}
}

CallParams Structure:

pub struct CallParams {
pub method: MethodName, // Transition method name
pub global: Vec<NamedState<StateAtom>>, // Global state updates
pub owned: Vec<NamedState<DataCell>>, // Owned state assignments
pub valencies: Vec<Valency>, // Valency declarations
}

Example:

use rgb::CallParams;

let call = CallParams {
method: MethodName::from("transfer"),
global: vec![],
owned: vec![
NamedState {
name: StateName::from("assetOwner"),
state: DataCell {
auth: recipient_seal.auth_token(),
data: StrictVal::from(1000u64),
lock: None,
},
}
],
valencies: vec![],
};

let seals = small_bmap! {
0u16 => recipient_seal
};

let operation = contract.call(call, seals)?;
println!("Created operation: {}", operation.opid());

Include Operation​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn include(
&mut self,
opid: Opid,
anchor: P::Seal::Client,
published: &P::Seal::Published,
) {
// Include operation with witness
}
}

Example:

// After broadcasting Bitcoin transaction
let opid = operation.opid();
let anchor = ClientAnchor::from(psbt);
let published = PublishedWitness::from(tx);

contract.include(opid, anchor, &published);

Synchronization​

Sync Witness Status​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn sync(
&mut self,
changed: impl IntoIterator<Item = (P::Seal::WitnessId, WitnessStatus)>,
) -> Result<(), MultiError<AcceptError, S::Error>> {
// Update witness status and recompute state
}
}

WitnessStatus Enum:

pub enum WitnessStatus {
Genesis, // Genesis operation
Tentative, // Unconfirmed
Mined { depth: u32 }, // Confirmed
Invalid, // Rejected/double-spent
}

Example:

// After Bitcoin confirmations
let updates = vec![
(witness_id1, WitnessStatus::Mined { depth: 1 }),
(witness_id2, WitnessStatus::Mined { depth: 6 }),
];

contract.sync(updates)?;

// State automatically recomputes
let state = contract.state();

Consignments​

Export Full Contract​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn export(&self, writer: StrictWriter<impl WriteRaw>) -> io::Result<()>
where
P::Seal::Client: StrictDumb + StrictEncode,
P::Seal::Published: StrictDumb + StrictEncode,
P::Seal::WitnessId: StrictEncode,
{
// Export complete contract
}
}

Example:

use std::fs::File;
use strict_encoding::StrictWriter;

let file = File::create("contract.rgb")?;
let writer = StrictWriter::new(file);
contract.export(writer)?;

Create Consignment​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn consign(
&self,
terminals: impl IntoIterator<Item = impl Borrow<AuthToken>>,
writer: StrictWriter<impl WriteRaw>,
) -> io::Result<()>
where
P::Seal::Client: StrictDumb + StrictEncode,
P::Seal::Published: StrictDumb + StrictEncode,
P::Seal::WitnessId: StrictEncode,
{
// Export consignment for specific terminals
}
}

Example:

// Create consignment for transfer
let terminals = vec![recipient_auth_token];
let file = File::create("transfer.consignment")?;
let writer = StrictWriter::new(file);

contract.consign(terminals, writer)?;

Consume Consignment​

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn consume<E>(
&mut self,
reader: &mut StrictReader<impl ReadRaw>,
seal_resolver: impl FnMut(&Operation) -> BTreeMap<u16, P::Seal::Definition>,
sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
) -> Result<(), MultiError<ConsumeError<P::Seal::Definition>, S::Error>>
where
P::Seal::Client: StrictDecode,
P::Seal::Published: StrictDecode,
P::Seal::WitnessId: StrictDecode,
{
// Import and validate consignment
}
}

Example:

let file = File::open("transfer.consignment")?;
let mut reader = StrictReader::new(file);

// Seal resolver matches blinded seals to wallet seals
let seal_resolver = |op: &Operation| {
wallet.resolve_seals_for_operation(op)
};

// Signature validator checks issuer signature
let sig_validator = |hash, identity, sig| {
identity.verify_signature(hash, sig)
};

contract.consume(&mut reader, seal_resolver, sig_validator)?;

File Operations​

When the fs feature is enabled:

impl<S: Stock, P: Pile> Contract<S, P> {
pub fn export_to_file(&self, path: impl AsRef<Path>) -> io::Result<()>
where
P::Seal::Client: StrictDumb + StrictEncode,
P::Seal::Published: StrictDumb + StrictEncode,
P::Seal::WitnessId: StrictEncode,
{
// Export to file
}

pub fn consign_to_file(
&self,
path: impl AsRef<Path>,
terminals: impl IntoIterator<Item = impl Borrow<AuthToken>>,
) -> io::Result<()>
where
P::Seal::Client: StrictDumb + StrictEncode,
P::Seal::Published: StrictDumb + StrictEncode,
P::Seal::WitnessId: StrictEncode,
{
// Create consignment file
}
}

Example:

// Export full contract
contract.export_to_file("contract.rgb")?;

// Create consignment
contract.consign_to_file("transfer.consignment", terminals)?;

Working with RGB20​

Creating Fungible Tokens​

use schemata::NonInflatableAsset;
use ifaces::Rgb20;
use rgbstd::persistence::Stock;

// Simple token creation
let contract = NonInflatableAsset::testnet(
"ssi:issuer",
"TOKEN",
"My Token",
None,
Precision::CentiMicro,
[(Method::TapretFirst, beneficiary, 1_000_000_00000000u64)]
)?;

// Import to stock
let mut stock = Stock::in_memory();
stock.import_contract(contract, NoResolver)?;

// Query via RGB20 interface
let rgb20 = stock.contract_iface_class::<Rgb20>(contract_id)?;

Interface Queries​

// Get token spec
let spec = rgb20.spec();
println!("Ticker: {}", spec.ticker);
println!("Name: {}", spec.name);
println!("Precision: {}", spec.precision);

// Get supply
let total_supply = rgb20.total_supply();
println!("Total Supply: {}", total_supply);

// Get allocations
use rgbstd::interface::{FilterIncludeAll, FungibleAllocation};

for FungibleAllocation { seal, state, witness, .. } in rgb20.allocations(&FilterIncludeAll) {
println!("Allocation: amount={}, seal={}", state, seal);
}

Working with RGB21​

Creating NFTs​

use schemata::UniqueDigitalAsset;
use ifaces::Rgb21;
use ifaces::rgb21::{TokenData, EmbeddedMedia};

// Define token data
let token_data = TokenData {
index: TokenIndex::from_inner(1),
preview: Some(EmbeddedMedia {
ty: MediaType::with("image/png"),
data: SmallBlob::try_from_iter(image_bytes)?,
}),
..Default::default()
};

// Create contract with builder
let mut stock = Stock::in_memory();
let kit = Kit::load_file("UniqueDigitalAsset.rgb")?.validate()?;
stock.import_kit(kit)?;

let contract = stock.contract_builder(
"ssi:issuer",
UniqueDigitalAsset::schema().schema_id(),
"RGB21",
)?
.add_global_state("tokens", token_data)?
.add_global_state("spec", spec)?
.add_data("assetOwner", seal, Allocation::with(index, 1))?
.issue_contract()?;

// Query via RGB21 interface
let rgb21 = stock.contract_iface_class::<Rgb21>(contract_id)?;

Advanced Patterns​

Custom Schemas​

Define custom contract logic:

use rgbstd::schema::{Schema, GenesisSchema, TransitionSchema};
use aluvm::library::Lib;

fn custom_schema() -> Schema {
let types = StandardTypes::new();

Schema {
ffv: zero!(),
flags: none!(),
name: tn!("CustomContract"),
timestamp: 1713343888,
developer: Identity::from("ssi:developer"),
meta_types: none!(),
global_types: tiny_bmap! {
0u16 => GlobalStateSchema::once(types.get("CustomData")),
},
owned_types: tiny_bmap! {
0u16 => OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
},
valency_types: none!(),
genesis: GenesisSchema {
metadata: none!(),
globals: tiny_bmap! { 0u16 => Occurrences::Once },
assignments: tiny_bmap! { 0u16 => Occurrences::OnceOrMore },
valencies: none!(),
validator: Some(LibSite::with(0, validation_lib_id)),
},
extensions: none!(),
transitions: tiny_bmap! {
0u16 => TransitionSchema {
metadata: none!(),
globals: none!(),
inputs: tiny_bmap! { 0u16 => Occurrences::OnceOrMore },
assignments: tiny_bmap! { 0u16 => Occurrences::OnceOrMore },
valencies: none!(),
validator: Some(LibSite::with(4, validation_lib_id)),
}
},
reserved: none!(),
}
}

Validation Scripts​

Create AluVM validation scripts:

use aluvm::library::Lib;
use rgbstd::rgbasm;

fn validation_lib() -> Lib {
let code = rgbasm! {
// Custom validation logic
put a8[0], 1; // Error code
pcvs 0u16; // Verify commitments
test;
ret;
};

Lib::assemble(&code).expect("invalid validation script")
}

Error Handling​

Common Error Types​

#[derive(Debug, Display, Error)]
pub enum ConsumeError<Seal: RgbSealDef> {
Io(IoError),
UnknownContract(ContractId),
Semantics(SemanticError),
Decode(DecodeError),
Verify(VerificationError<Seal::Src>),
Issue(IssuerError),
}

Error Handling Pattern​

match contract.consume(&mut reader, resolver, validator) {
Ok(_) => {
println!("Consignment accepted");
}
Err(MultiError::A(ConsumeError::UnknownContract(id))) => {
eprintln!("Unknown contract: {}", id);
// Import genesis first
}
Err(MultiError::A(ConsumeError::Verify(err))) => {
eprintln!("Validation failed: {}", err);
// Invalid consignment
}
Err(e) => {
eprintln!("Error: {}", e);
}
}

Testing​

Unit Tests​

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_contract_creation() {
let contract = NonInflatableAsset::testnet(
"ssi:test",
"TEST",
"Test Token",
None,
Precision::CentiMicro,
[(Method::TapretFirst, test_outpoint(), 1000_00000000)]
).unwrap();

assert_eq!(contract.contract_id().to_string().len() > 0, true);
}

#[test]
fn test_state_query() {
let contract = create_test_contract();
let state = contract.state();

assert!(state.owned.contains_key(&StateName::from("assetOwner")));
}

#[test]
fn test_transfer() {
let mut contract = create_test_contract();

let call = CallParams {
method: MethodName::from("transfer"),
global: vec![],
owned: vec![create_transfer_output(1000)],
valencies: vec![],
};

let result = contract.call(call, test_seals());
assert!(result.is_ok());
}
}

Integration Tests​

#[test]
fn test_full_transfer_workflow() {
// 1. Create genesis
let contract = create_genesis_contract();

// 2. Export consignment
let terminals = vec![recipient_token];
let mut consignment_data = Vec::new();
contract.consign(terminals, StrictWriter::new(&mut consignment_data)).unwrap();

// 3. Import on recipient side
let mut recipient_contract = Contract::with(
contract.articles().clone(),
Consignment::decode(&consignment_data).unwrap(),
config,
).unwrap();

// 4. Verify state
let state = recipient_contract.state();
assert!(state.owned.len() > 0);
}

Best Practices​

Resource Management​

// Use RAII for automatic cleanup
{
let contract = Contract::load(config)?;
// Contract automatically saved on drop
} // <- Resources freed here

Thread Safety​

use std::sync::{Arc, Mutex};

// Share contracts across threads
let contract = Arc::new(Mutex::new(contract));

let contract_clone = contract.clone();
std::thread::spawn(move || {
let mut c = contract_clone.lock().unwrap();
// Use contract
});

Performance Tips​

// Cache state queries
let state = contract.state();
// Reuse state for multiple queries

// Batch witness updates
let updates = vec![
(wid1, status1),
(wid2, status2),
(wid3, status3),
];
contract.sync(updates)?; // Single batch update

// Use iterators efficiently
for (opid, op, rels) in contract.operations().take(10) {
// Process only first 10 operations
}

Source Code: See /tmp/rgb-repos/rgb-std/src/contract.rs for complete Contract implementation.