Skip to main content

State Transitions

State transitions are the fundamental operations in RGB contracts that modify state ownership and update global data. This guide covers how transitions work, validation rules, and advanced state management patterns.

What are State Transitions?​

A state transition represents a valid change in contract state - transferring ownership, updating global values, or executing contract logic. Each transition:

  • Consumes Inputs: Spends existing state (sealed UTXOs)
  • Creates Outputs: Generates new state (new seals)
  • Updates Globals: Modifies shared contract state
  • Validates Rules: Proves compliance with schema

Transitions are client-side validated and anchored to Bitcoin without publishing data on-chain.

Transition Anatomy​

transition:
transition_type: Transfer
contract_id: rgb:2wHxKf...

inputs:
- seal: btc:bc1q...:0
amount: 1000

outputs:
- seal: <blinded-utxo>
amount: 600
- seal: <blinded-utxo>
amount: 400

globals:
total_supply: 1000000 # Unchanged

validation_proof:
schema_check: passed
amount_conservation: passed
signature: valid

bitcoin_anchor:
txid: abc123...
block: 800000

Transition structure to be expanded

Transition Types​

Transfer Transitions​

Move owned state from one party to another:

import { RgbContract } from '@rgbjs/core';

const transfer = await contract.createTransition({
type: 'Transfer',
inputs: [
{ seal: mySeal, amount: 1000n }
],
outputs: [
{ seal: recipientSeal, amount: 600n },
{ seal: changeSeal, amount: 400n }
]
});

await transfer.validate();
await transfer.anchor();
await transfer.finalize();

Transfer transitions to be expanded

State Update Transitions​

Modify global contract state:

const update = await contract.createTransition({
type: 'UpdateMetadata',
inputs: [
{ seal: adminRightsSeal, rights: ['admin'] }
],
globals: {
description: 'Updated contract description',
website: 'https://newsite.com'
}
});

State updates to be expanded

Issuance Transitions​

Create new tokens (if permitted):

const issuance = await contract.createTransition({
type: 'Issue',
inputs: [
{ seal: issuerSeal, rights: ['issue'] }
],
outputs: [
{ seal: recipientSeal, amount: 100000n },
{ seal: newIssuerSeal, rights: ['issue'] } // Preserve rights
],
globals: {
total_supply: current_supply + 100000n
}
});

Issuance transitions to be expanded

Burn Transitions​

Permanently destroy tokens:

const burn = await contract.createTransition({
type: 'Burn',
inputs: [
{ seal: holderSeal, amount: 500n }
],
outputs: [], // No outputs = burned
globals: {
total_supply: current_supply - 500n
}
});

Burn transitions to be expanded

Validation Rules​

Conservation Laws​

Amount must be conserved (unless burning/issuing):

function validateConservation(inputs, outputs) {
const inputSum = inputs.reduce((sum, i) => sum + i.amount, 0n);
const outputSum = outputs.reduce((sum, o) => sum + o.amount, 0n);

if (inputSum !== outputSum) {
throw new Error('Amount conservation violated');
}
}

Conservation validation to be expanded

Schema Compliance​

Transition must match schema definition:

schema_rules:
Transfer:
inputs:
- assetOwner: Amount
outputs:
- assetOwner: Amount
validation:
- sum(inputs.assetOwner) == sum(outputs.assetOwner)
- all(outputs.assetOwner > 0)

Schema validation to be expanded

Custom Validation​

Schemas can define arbitrary validation logic:

// In schema definition
validation_script: |
// Check transfer limits
if (sum(outputs) > MAX_TRANSFER) {
require(has_admin_permission);
}

// Check recipient whitelist
for (output in outputs) {
require(is_whitelisted(output.owner));
}

// Time-based restrictions
if (current_time < unlock_time) {
reject("Tokens still locked");
}

Custom validation to be expanded

Creating Transitions​

Using RGB CLI (v0.12.0-rc.3)​

State transitions are created through the invoice/pay workflow:

# Step 1: Recipient generates invoice
rgb invoice --wallet recipient-wallet \
<CONTRACT_ID> \
1000

# Step 2: Sender pays invoice (creates transition + PSBT)
rgb pay --wallet sender-wallet \
<INVOICE> \
transfer.consignment \
transfer.psbt

# Step 3: Sender signs PSBT (using Bitcoin wallet)
# ... sign PSBT with Bitcoin signing tool ...

# Step 4: Sender completes the operation
rgb complete --wallet sender-wallet \
transfer.bundle \
signed.psbt

# Step 5: Sender shares consignment with recipient

# Step 6: Recipient validates and accepts the transition
rgb accept --wallet recipient-wallet \
transfer.consignment

# Step 7: Sender finalizes and broadcasts Bitcoin transaction
rgb finalize --wallet sender-wallet \
--broadcast \
signed.psbt

The transition is created automatically as part of the pay command, which:

  • Selects appropriate inputs
  • Constructs state transition
  • Creates Bitcoin PSBT
  • Generates consignment with validation proofs

Using RGB.js SDK​

import { RgbWallet, StateTransition } from '@rgbjs/core';

const wallet = new RgbWallet(config);

// Step 1: Build transition
const transition = await wallet.buildTransition({
contractId: contractId,
transitionType: 'Transfer',

// Inputs (what we're spending)
inputs: await wallet.selectInputs({
contractId: contractId,
amount: 1000n
}),

// Outputs (where it's going)
outputs: [
{
recipient: recipientInvoice,
amount: 1000n
}
]
});

// Step 2: Validate
const validationResult = await transition.validate();
if (!validationResult.valid) {
throw new Error('Invalid transition: ' + validationResult.errors);
}

// Step 3: Sign
await transition.sign(privateKey);

// Step 4: Anchor to Bitcoin
const bitcoinTx = await transition.createAnchorTx();
await wallet.broadcastTx(bitcoinTx);

// Step 5: Finalize
await transition.finalize();

SDK implementation to be expanded

State Management​

Global State Updates​

Tracking contract-wide state:

interface GlobalState {
// Immutable fields (set at genesis)
readonly ticker: string;
readonly precision: number;

// Mutable fields
total_supply: bigint;
total_burned: bigint;
last_update: number;
}

// Update in transition
const transition = await contract.updateGlobals({
total_supply: newSupply,
last_update: Date.now()
});

Global state management to be expanded

Owned State Tracking​

State attached to specific UTXOs:

interface OwnedState {
seal: Seal; // Bitcoin UTXO
amount: bigint; // Fungible amount
data?: AttachmentId; // NFT data
rights?: Right[]; // Special permissions
}

// Query owned state
const myState = await wallet.getOwnedState(contractId);
console.log('My balance:', myState.reduce((sum, s) => sum + s.amount, 0n));

Owned state management to be expanded

State History​

Complete state evolution over time:

const history = await contract.getStateHistory();

history.forEach(snapshot => {
console.log(`Block ${snapshot.height}:`);
console.log(` Total Supply: ${snapshot.global.total_supply}`);
console.log(` Holders: ${snapshot.owned_state.length}`);
});

State history to be expanded

Advanced Patterns​

Atomic Multi-Transitions​

Multiple contracts in single Bitcoin transaction:

const atomicTransitions = await wallet.createAtomicBatch([
{
contractId: contract1,
type: 'Transfer',
outputs: [{ seal: seal1, amount: 100n }]
},
{
contractId: contract2,
type: 'Transfer',
outputs: [{ seal: seal2, amount: 200n }]
}
]);

// All succeed or all fail together
await atomicTransitions.execute();

Atomic operations to be expanded

Conditional Transitions​

Transitions with external requirements:

const conditional = await contract.createTransition({
type: 'ConditionalTransfer',
inputs: [{ seal: mySeal, amount: 1000n }],
outputs: [{ seal: recipientSeal, amount: 1000n }],

conditions: {
// Require Bitcoin block height
min_height: 850000,

// Require oracle signature
oracle_data: {
price_feed: 'BTC/USD',
min_price: 50000,
oracle_pubkey: oraclePubkey
},

// Require time lock
unlock_time: future_timestamp
}
});

Conditional transitions to be expanded

State Aggregation​

Combining multiple small states:

// Consolidate dust amounts
const consolidate = await wallet.consolidateState({
contractId: contractId,
minAmount: 100n, // Only consolidate amounts > 100
targetOutputs: 1 // Combine into single UTXO
});

// Before: [50, 75, 25, 100, 30]
// After: [280]

State aggregation to be expanded

Validation Process​

Client-Side Validation​

Each party validates transitions independently:

class TransitionValidator {
async validate(transition: Transition): Promise<ValidationResult> {
// 1. Schema validation
await this.validateSchema(transition);

// 2. Input validation
await this.validateInputs(transition.inputs);

// 3. State conservation
await this.validateConservation(transition);

// 4. Custom rules
await this.validateCustomRules(transition);

// 5. Signature verification
await this.validateSignatures(transition);

// 6. Bitcoin anchor
await this.validateAnchor(transition);

return { valid: true };
}
}

Validation workflow to be expanded

Proof Generation​

Creating cryptographic proofs:

const proof = await transition.generateProof({
include: {
schema_compliance: true,
amount_conservation: true,
signature_validity: true,
merkle_path: true
}
});

// Proof can be verified independently
const verified = await ProofVerifier.verify(proof);

Proof generation to be expanded

Anchoring to Bitcoin​

Creating Anchor Transaction​

const anchorTx = await transition.createAnchorTransaction({
feeRate: 5, // sats/vbyte
changeAddress: myBitcoinAddress,

// Optional: Anchor multiple transitions
additionalTransitions: [transition2, transition3]
});

// Broadcast to Bitcoin network
const txid = await bitcoinClient.sendRawTransaction(anchorTx);

Anchoring process to be expanded

Commitment Structure​

RGB commitment in OP_RETURN:

OP_RETURN
<protocol_tag> # 'rgb1'
<commitment> # Hash(transition_data)
<extra_proof> # Optional: Multi-protocol proof

Commitment format to be expanded

Confirmation Handling​

// Wait for confirmations
await transition.waitForConfirmation({
required: 6,
timeout: 3600000 // 1 hour
});

// Handle reorganizations
transition.on('reorg', async (event) => {
console.warn('Reorg detected at height', event.oldHeight);
await transition.reanchor();
});

Confirmation management to be expanded

Error Handling​

Common Validation Errors​

try {
await transition.validate();
} catch (error) {
if (error instanceof AmountConservationError) {
console.error('Inputs and outputs don\'t match');
} else if (error instanceof SchemaValidationError) {
console.error('Transition violates schema rules');
} else if (error instanceof InsufficientRightsError) {
console.error('Missing required permissions');
}
}

Error handling to be expanded

Recovery Strategies​

// Retry with different parameters
async function transferWithRetry(params) {
try {
return await createTransfer(params);
} catch (error) {
if (error instanceof InsufficientBalanceError) {
// Reduce amount
params.amount = params.amount * 0.9n;
return transferWithRetry(params);
} else if (error instanceof FeeEstimationError) {
// Increase fee
params.feeRate *= 1.5;
return transferWithRetry(params);
}
throw error;
}
}

Recovery patterns to be expanded

Performance Optimization​

Batch Processing​

// Process multiple transitions efficiently
const transitions = await Promise.all(
invoices.map(invoice =>
wallet.createTransition({
recipient: invoice,
amount: 100n
})
)
);

await wallet.anchorBatch(transitions);

Batch optimization to be expanded

Caching Validation Results​

const validationCache = new Map();

async function validateCached(transition) {
const key = transition.id;
if (validationCache.has(key)) {
return validationCache.get(key);
}

const result = await transition.validate();
validationCache.set(key, result);
return result;
}

Caching strategies to be expanded