System Design
Data Format Schemas
Complete type definitions for all onchain data structures, messages, and events across both Stellar Soroban and Fairyring CosmWasm contracts.
Quick reference
ElGamal ciphertext64 bytes (2 × 32)c1 and c2, each a Ristretto255 compressed point
ElGamal public key32 bytesCompressed Ristretto255 point
tx_idu64Monotonically increasing per account; replay guard
pending_credit_counteru64Increments on each outgoing transfer
DenomCipherstring + 64 bytesToken label + ElGamal ciphertext
Proof bundlevariableABI-packed equality + range + validity proofs
Cryptographic primitives
Shared between both contracts.
// ── Shared cryptographic primitives ────────────────────────────────
// 32-byte compressed Ristretto255 point representing the public key.
// Serialized as base64-encoded Binary in CosmWasm; Bytes in Soroban.
type ElGamalPublicKey = {
key: Uint8Array; // 32 bytes
};
// Twisted ElGamal ciphertext: two 32-byte Ristretto255 curve points.
// c1 = r · G (ephemeral)
// c2 = r · PK + m · G (encrypted value)
type ElGamalCiphertext = {
c1: Uint8Array; // 32 bytes
c2: Uint8Array; // 32 bytes
};Fairyring: state storage types
Defined in state.rs and crypto.rs. Stored in CosmWasm cw-storage-plus Maps and Items.
// ── Fairyring CosmWasm storage types ───────────────────────────────
// A denomination-scoped encrypted balance slot.
// One entry per token in available_balance / pending_balance.
type DenomCipher = {
denom: string; // e.g. "USDC" or contract address string
amount: ElGamalCiphertext;
};
// Full confidential account state (stored in ACCOUNTS map on Fairyring).
type ConfidentialAccount = {
elgamal_pubkey: ElGamalPublicKey;
// Spendable encrypted balances, one entry per denomination.
available_balance: DenomCipher[];
// Incoming transfers awaiting apply_pending, per denomination.
pending_balance: DenomCipher[];
// Incremented on each outgoing transfer; used as credit_id.
pending_credit_counter: number; // u64
// Monotonically increasing; replay protection.
last_processed_id: number; // u64
};
// A pending cross-account credit record, stored by (sender, credit_id).
type PendingTransfer = {
credit_id: number; // u64 sender-scoped sequence number
from: string; // sender address
to: string; // recipient address
amount_cipher: ElGamalCiphertext; // encrypted under recipient's pubkey
fee_cipher: ElGamalCiphertext | null;
timestamp: number; // Unix seconds at time of transfer
};
// Global contract configuration
type Config = {
allowed_addresses: string[]; // BTreeSet<Addr> serialized
};Fairyring: messages & queries
Defined in msg.rs. ExecuteMsg variants are submitted by FairyPort; QueryMsg variants are used by the SDK and relayer for state inspection.
// ── Fairyring ExecuteMsg variants ───────────────────────────────────
type InstantiateMsg = {
allowed_addresses: string[];
};
// Proof bundles for transfer (sent by FairyPort after relaying from Stellar)
type TransferProofData = {
equality_proof_data: Uint8Array;
transfer_amount_ciphertext_validity_proof_data_with_ciphertext: Uint8Array;
range_proof_data: Uint8Array;
};
type WithdrawProofData = {
equality_proof: Uint8Array;
range_proof: Uint8Array;
};
type ExecuteMsg =
| {
create_confidential_account: {
owner: string;
elgamal_pubkey: ElGamalPublicKey;
tx_id: number;
};
}
| {
deposit: {
owner: string;
items: { denom: string; plain_amount: string }[]; // exactly one item
tx_id: number;
available_c1: Uint8Array; // current balance c1 (for idempotency)
available_c2: Uint8Array; // current balance c2 (for idempotency)
};
}
| {
transfer_confidential: {
sender: string;
recipient: string;
denom: string;
transfer_proof_data: TransferProofData;
tx_id: number;
use_offchain_verify: boolean;
// Required only when use_offchain_verify = true:
sender_encrypted_amount?: ElGamalCiphertext;
recipient_encrypted_amount?: ElGamalCiphertext;
available_c1: Uint8Array;
available_c2: Uint8Array;
};
}
| { apply_pending: { owner: string; tx_id: number } }
| {
withdraw: {
owner: string;
denom: string;
amount: string; // Uint128 as decimal string
withdraw_proof_data: WithdrawProofData;
tx_id: number;
use_offchain_verify: boolean;
available_c1: Uint8Array;
available_c2: Uint8Array;
};
}
| {
update_allowed_addresses: {
add: string[];
remove: string[];
};
};
// ── Fairyring QueryMsg variants ──────────────────────────────────────
type QueryMsg =
| { get_config: {} }
| { get_account: { owner: string } }
| { get_pending_transfer: { sender: string; credit_id: number } }
| { list_pending_transfers: {} }
| { list_accounts: {} };
// ── Fairyring custom query (chain ZKP module) ────────────────────────
type FairyQuery =
| {
verify_transfer_proofs: {
equality_proof_data: Uint8Array;
range_proof_data: Uint8Array;
validity_proof_data: Uint8Array;
sender_pubkey: Uint8Array;
recipient_pubkey: Uint8Array;
current_balance_commitment: Uint8Array;
current_balance_handle: Uint8Array;
};
}
| {
verify_withdraw_proofs: {
equality_proof_data: Uint8Array;
range_proof_data: Uint8Array;
user_pubkey: Uint8Array;
ciphertext_commitment: Uint8Array;
ciphertext_handle: Uint8Array;
expected_nonce: number;
};
};Soroban: storage types
Defined in types.rs. All types are annotated #[contracttype] for Soroban XDR serialization. Persistent storage is extended to configured TTL on each write.
// ── Stellar Soroban contract types (Rust contracttype) ──────────────
// Core account state stored per user address.
type AccountCore = {
exists: boolean;
finalized: boolean; // true after create_account callback
pending_action: boolean; // true while any action is in flight
tx_id: number; // u64; monotonically increasing per account
elgamal_pubkey: Uint8Array; // 32 bytes; set after finalization
pending_credit_counter: number; // u64; mirrors Fairyring counter
minimum_pending_credit_counter: number; // u64; lower bound for apply
};
// Encrypted balance ciphertext (one per token per account)
type Ciphertext = {
c1: Uint8Array;
c2: Uint8Array;
};
// Pending state types (exactly one may exist per account at a time)
type PendingCreate = {
exists: boolean;
tx_id: number;
elgamal_pubkey: Uint8Array;
};
type PendingDeposit = {
exists: boolean;
token: string; // Stellar token contract address
amount: bigint; // i128; plaintext deposit amount
tx_id: number;
available_c1: Uint8Array; // snapshot of current ciphertext c1
available_c2: Uint8Array; // snapshot of current ciphertext c2
};
type PendingTransfer = {
exists: boolean;
tx_id: number;
recipient: string;
token: string;
fee_paid: bigint; // i128
use_offchain_verify: boolean;
proof: Uint8Array; // ABI-encoded ZK proof bundle
};
type PendingApply = {
exists: boolean;
tx_id: number;
};
type PendingWithdraw = {
exists: boolean;
tx_id: number;
token: string;
plain_amount: bigint; // i128
use_offchain_verify: boolean;
proof: Uint8Array;
};
// ── Soroban storage key variants (DataKey enum) ──────────────────────
// Storage is partitioned by key type:
// Instance storage: Admin, Operators, FeeAccount, FeeAmount, FeeToken,
// Paused, InstanceBumpAmount, InstanceLifetimeThreshold,
// PersistentBumpAmount, PersistentLifetimeThreshold
//
// Persistent storage (keyed by address):
// Account(owner) → AccountCore
// Available(owner, token) → Ciphertext (available balance)
// Pending(owner, token) → Ciphertext (pending balance)
// Locked(token) → (token lock guard)
// PendingDeposit(owner) → PendingDeposit
// PendingCreate(owner) → PendingCreate
// PendingTransfer(owner) → PendingTransfer
// PendingApply(owner) → PendingApply
// PendingWithdraw(owner) → PendingWithdraw
// PendingOwners → Vec<Address> (list of pending accounts)
// PendingKind(owner) → PendingKind enumSoroban: emitted events
Events emitted by the Soroban contract are the primary trigger for FairyPort. They follow Soroban's env.events().publish(topics, data) convention.
// ── Soroban events emitted by the contract ──────────────────────────
// All events follow (topic_tuple, data_tuple) structure.
// Emitted when a user requests account creation:
event CreateAccountRequested = {
topics: ["CreateAccountRequested", owner: Address],
data: [tx_id: u64, elgamal_pubkey: Bytes],
};
// Emitted when a user requests deposit:
event DepositRequested = {
topics: ["DepositRequested", owner: Address, token: Address],
data: [tx_id: u64, plain_amount: i128, avail_c1: Bytes, avail_c2: Bytes],
};
// Emitted when a user requests transfer:
event TransferRequested = {
topics: ["TransferRequested", sender: Address, recipient: Address],
data: [
tx_id: u64,
token: Address,
proof: Bytes, // ABI-encoded proof bundle
use_offchain_verify: bool,
sender_elgamal_pubkey: Bytes,
recipient_elgamal_pubkey: Bytes,
sender_avail_c1: Bytes,
sender_avail_c2: Bytes,
],
};
// Emitted when a user requests apply_pending:
event ApplyPendingRequested = {
topics: ["ApplyPendingRequested", owner: Address],
data: tx_id: u64,
};
// Emitted when a user requests withdraw:
event WithdrawRequested = {
topics: ["WithdrawRequested", owner: Address, token: Address],
data: [
tx_id: u64,
plain_amount: i128,
proof: Bytes,
use_offchain_verify: bool,
elgamal_pubkey: Bytes,
avail_c1: Bytes,
avail_c2: Bytes,
],
};Fairyring: wasm event attributes
Events emitted by the Fairyring CosmWasm contract are consumed by FairyPort to construct Stellar callback transactions. All numeric values are serialized as decimal strings; all binary fields are base64-encoded.
// ── Fairyring CosmWasm wasm events (response attributes) ────────────
// Each action emits a wasm event with these attributes.
// create_confidential_account response attributes:
{
action: "create_confidential_account",
owner: string,
sender: string, // relayer address
tx_id: string, // u64 as string
elgamal_pubkey_b64: string, // base64
tx_status: "ok" | "failed",
error?: string,
}
// deposit response attributes (+ "deposit_item" sub-event):
{
action: "deposit",
owner: string,
tx_id: string,
tx_status: "ok" | "failed",
// deposit_item sub-event:
denom: string,
pending_credit_counter: string,
new_avail_c1: string, // base64
new_avail_c2: string, // base64
}
// transfer_confidential response attributes:
{
action: "transfer_confidential",
from: string,
to: string,
denom: string,
credit_id: string,
sender_new_avail_c1: string, // base64
sender_new_avail_c2: string, // base64
recipient_new_pending_c1: string, // base64
recipient_new_pending_c2: string, // base64
sender_pending_credit_counter: string,
recipient_pending_credit_counter: string,
tx_id: string,
tx_status: "ok" | "failed",
}
// apply_pending response attributes (+ "apply_pending_item" sub-event per denom):
{
action: "apply_pending",
owner: string,
pending_credit_counter: string,
tx_id: string,
tx_status: "ok" | "failed",
// apply_pending_item sub-event:
denom: string,
new_avail_c1: string, // base64
new_avail_c2: string, // base64
}
// withdraw response attributes:
{
action: "withdraw",
owner: string,
denom: string,
amount: string,
pending_credit_counter: string,
new_avail_c1: string, // base64
new_avail_c2: string, // base64
tx_id: string,
tx_status: "ok" | "failed",
}Encoding notes
| Field type | Fairyring (CosmWasm) | Stellar (Soroban) |
|---|---|---|
| Byte arrays (keys, proofs) | cosmwasm_std::Binary (base64 JSON) | soroban_sdk::Bytes (XDR) |
| Integers (amounts, counters) | Uint128 / u64 (decimal string in JSON) | i128 / u64 (XDR native) |
| Addresses | cosmwasm_std::Addr (bech32 string) | soroban_sdk::Address (Stellar pubkey / contract) |
| Ciphertext in events | base64 attribute values | Bytes in event data tuple (XDR) |
| Proof bundle encoding | Binary (bytemuck zero-copy struct layout) | Bytes (ABI-packed layout for FFI verifier) |
