Skip to content

Sorokit/contracts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sorokit-contracts

Soroban smart contracts for the sorokit ecosystem.

Three independently deployable Rust contracts for Stellar Soroban:
Escrow, Splitter, and Registry.

License: MIT Rust 1.70+ Soroban SDK 21.0

Part of the sorokit ecosystem.



Overview

sorokit-contracts provides three production-ready Soroban smart contracts designed to be invoked through sorokit-core's Soroban module. Each contract is independently deployable and fully tested.

This repository is a Rust workspace containing:

  • Escrow — Hold and release (or refund) funds with two-party coordination and dispute resolution
  • Splitter — Distribute payments proportionally among multiple recipients with admin controls
  • Registry — A general-purpose key-value store with versioning, TTL expiry, and access control

Table of Contents


Installation

Prerequisites

Clone the Repository

git clone https://github.com/Sorokit/contracts.git
cd contracts

Build

Build all contracts:

make build          # Build all contracts
make build-wasm     # Build WASM targets
cargo build --release

Build a specific contract:

cargo build --release -p escrow
cargo build --release -p splitter
cargo build --release -p registry

The compiled WASM binaries will be in target/wasm32-unknown-unknown/release/.


Test

Run all tests:

make test           # Run all unit and integration tests
make test-verbose   # Run tests with output
cargo test --all

Run tests for a specific contract:

cargo test -p escrow
cargo test -p splitter
cargo test -p registry

Code quality checks:

make fmt            # Format code
make lint           # Lint code
make check          # Format, lint, and test all

Architecture

┌─────────────────────────────────────────────────────┐
│                  sorokit-core                       │
│          (TypeScript/JavaScript SDK)                │
│                                                     │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │   wallet    │  │  account    │  │ transaction │ │
│  └─────────────┘  └─────────────┘  └─────────────┘ │
│                                                     │
│              ┌────────────────────┐                │
│              │    soroban module   │                │
│              └────────────────────┘                │
└─────────────────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────┐
│           Stellar Soroban Network                  │
│                                                     │
│  ┌──────────────┐ ┌──────────────┐ ┌────────────┐  │
│  │    Escrow    │ │   Splitter   │ │  Registry  │  │
│  │  Contract    │ │  Contract    │ │ Contract   │  │
│  └──────────────┘ └──────────────┘ └────────────┘  │
│                                                     │
│  ┌────────────────────────────────────────────┐   │
│  │     sorokit-shared (utilities)              │   │
│  │  • Error types                              │   │
│  │  • Math utilities                           │   │
│  └────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

Contracts

Escrow

Purpose: Two-party escrow for conditional fund release with dispute resolution.

State:

pub struct Escrow {
    pub depositor: Address,
    pub recipient: Address,
    pub arbitrator: Address,
    pub amount: u128,
    pub status: EscrowStatus,  // Pending | Released | Refunded | Disputed | Resolved
    pub token: Address,
}

Public Functions:

deposit(env, token, sender, amount, recipient, arbitrator)

Deposit funds into escrow.

Parameters:

  • token — Token contract address (e.g., USDC)
  • sender — Depositor account (must be caller)
  • amount — Amount in stroops
  • recipient — Recipient account
  • arbitrator — Arbitrator for disputes

Example (via sorokit-core):

const depositResult = await client.soroban.invoke(
  {
    contractId: escrowContractAddress,
    publicKey: depositorAddress,
    method: "deposit",
    args: [
      "CDD7...token", // token address
      "1000000", // 0.1 XLM in stroops
      "recipientAddr",
      "arbitratorAddr",
    ],
    contractAbi: escrowAbi,
  },
  async (xdr) => {
    const signed = await client.wallet.signTransaction(adapter, {
      transactionXdr: xdr,
      networkPassphrase: client.networkConfig.networkPassphrase,
    });
    return signed.status === "ok" ? signed.data : null;
  },
);

release(env, sender)

Release funds to recipient (depositor only).

refund(env, sender)

Refund funds to depositor (depositor only).

dispute(env, sender)

Flag escrow as disputed (depositor or recipient).

resolve(env, arbitrator, winner)

Resolve dispute and release funds to winner (arbitrator only).

get_escrow(env) -> Option<Escrow>

Get full escrow details.

get_status(env) -> Option<u32>

Get escrow status.

Events:

  • ("escrow", "deposited") — emitted on deposit
  • ("escrow", "released") — emitted on release
  • ("escrow", "refunded") — emitted on refund
  • ("escrow", "disputed") — emitted on dispute
  • ("escrow", "resolved") — emitted on resolution

Splitter

Purpose: Proportional payment distribution to multiple recipients with admin controls.

State:

pub struct SplitterConfig {
    pub admin: Address,
    pub splits: Vec<Split>,
}

pub struct Split {
    pub recipient: Address,
    pub basis_points: u32,  // e.g., 5000 = 50%
}

pub struct DistributionRecord {
    pub ledger: u32,
    pub token: Address,
    pub total_distributed: u128,
}

Public Functions:

set_splits(env, admin, recipients)

Configure payment splits (admin only).

Parameters:

  • admin — Admin account (must be caller)
  • recipients — Vector of (recipient, basis_points) pairs
  • Validation: Basis points must sum to exactly 10000

distribute(env, token, sender, amount)

Distribute tokens according to configured splits.

Parameters:

  • token — Token contract address
  • sender — Payer (must be caller)
  • amount — Total amount to distribute

update_splits(env, admin, recipients)

Update split configuration (admin only).

transfer_admin(env, admin, new_admin)

Transfer admin role to a new address.

get_splits(env) -> Option<Vec<Split>>

Get current split configuration.

get_admin(env) -> Option<Address>

Get current admin address.

get_history(env) -> Option<Vec<DistributionRecord>>

Get last 10 distribution records.

Events:

  • ("splitter", "splits_set") — emitted on configuration
  • ("splitter", "distributed") — emitted per recipient
  • ("splitter", "admin_transferred") — emitted on admin transfer
  • ("splitter", "splits_updated") — emitted on split update

Example (via sorokit-core):

const distributeResult = await client.soroban.invoke(
  {
    contractId: splitterContractAddress,
    publicKey: senderAddress,
    method: "distribute",
    args: [
      "CDD7...token", // token address
      "100000000", // 10 XLM in stroops
    ],
    contractAbi: splitterAbi,
  },
  signTransaction,
);

Registry

Purpose: General-purpose on-chain key-value store with versioning, TTL expiry, and access control.

State:

pub struct Entry {
    pub value: String,
    pub version: u64,
    pub expiry: Option<u32>,  // Ledger number when entry expires
}

Public Functions:

set(env, admin, key, value, ttl)

Set a key-value pair (admin only).

Parameters:

  • admin — Admin account (must be caller)
  • key — Registry key
  • value — Registry value
  • ttl — Optional TTL in ledgers; None means no expiry

get(env, reader, key) -> Option<String>

Get a value by key (with access control).

Parameters:

  • reader — Reader account (must be authorized if whitelist active)
  • key — Registry key

remove(env, admin, key)

Remove a key-value pair (admin only).

batch_set(env, admin, entries)

Set multiple key-value pairs in one transaction (admin only).

Parameters:

  • entries — Vector of (key, value, optional_ttl) tuples

batch_remove(env, admin, keys)

Remove multiple keys in one transaction (admin only).

add_reader(env, admin, reader)

Add an address to the read whitelist (admin only).

remove_reader(env, admin, reader)

Remove an address from the read whitelist (admin only).

get_readers(env) -> Option<Vec<Address>>

Get all authorized readers (None if no whitelist).

is_whitelist_active(env) -> bool

Check if read access is restricted to whitelist.

get_version(env) -> u64

Get version counter (increments on each write).

list(env) -> Option<Map<String, Entry>>

Get entire registry (entries with expiry metadata).

has(env, reader, key) -> bool

Check if a key exists (with access control).

Events:

  • ("registry", "set") — emitted on set
  • ("registry", "removed") — emitted on remove
  • ("registry", "batch_set") — emitted on batch set
  • ("registry", "batch_remove") — emitted on batch remove
  • ("registry", "reader_added") — emitted on whitelist add
  • ("registry", "reader_removed") — emitted on whitelist remove

Example (via sorokit-core):

const setResult = await client.soroban.invoke(
  {
    contractId: registryContractAddress,
    publicKey: adminAddress,
    method: "set",
    args: [
      "contract_address", // key
      "CA123...XYZ", // value
      100, // TTL in ledgers
    ],
    contractAbi: registryAbi,
  },
  signTransaction,
);

API Reference

Full API Documentation

See CONTRACTS.md for deployed contract addresses and endpoint documentation.


Integration with sorokit-core

Each contract is designed to be invoked via sorokit-core's client.soroban module.

Typical workflow:

import { createSorokitClient, FreighterAdapter } from "sorokit-core";

// 1. Create client
const result = createSorokitClient({ network: "testnet" });
if (result.status === "error") throw new Error(result.error.message);
const client = result.data;

// 2. Connect wallet
const adapter = new FreighterAdapter(swkInstance);
const conn = await client.wallet.connect(adapter);
if (conn.status === "error") throw new Error(conn.error.message);

// 3. Invoke contract
const invokeResult = await client.soroban.invoke(
  {
    contractId: "CA123...XYZ",
    publicKey: conn.data.publicKey,
    method: "contractMethod",
    args: [arg1, arg2, arg3],
    contractAbi: contractAbi,
  },
  async (xdr) => {
    const signed = await client.wallet.signTransaction(adapter, {
      transactionXdr: xdr,
      networkPassphrase: client.networkConfig.networkPassphrase,
    });
    return signed.status === "ok" ? signed.data : null;
  },
);

if (invokeResult.status === "ok") {
  console.log("Success! Tx hash:", invokeResult.data);
} else {
  console.error("Failed:", invokeResult.error.message);
}

For more details, see sorokit-core README.


Deployment

Deploy to Testnet

  1. Install the Soroban CLI:

    cargo install soroban-cli
  2. Set your network:

    soroban network add --global testnet \
      --rpc-url "https://soroban-testnet.stellar.org" \
      --network-passphrase "Test SDF Network ; September 2015"
    soroban config identity create --global myaccount
  3. Deploy a contract:

    soroban contract deploy \
      --wasm target/wasm32-unknown-unknown/release/escrow.wasm \
      --id 1 \
      --network testnet
  4. Verify deployment:

    soroban contract invoke \
      --id CA123...XYZ \
      --network testnet \
      -- get_escrow

For production deployments to mainnet, see Soroban deployment guide.


Development

Running Tests

# All tests
make test

# Specific contract
cargo test -p escrow

# With output
make test-verbose

# Code coverage (requires cargo-tarpaulin)
cargo tarpaulin --all

Code Quality

# Format
make fmt

# Lint
make lint

# All checks
make check

Building WASM

make build-wasm
ls target/wasm32-unknown-unknown/release/*.wasm

Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make your changes and add tests
  4. Ensure all checks pass (make check)
  5. Commit with clear messages
  6. Submit a pull request

For significant changes, open an issue first to discuss.


Security

Responsible Disclosure

If you discover a security vulnerability, please email security@sorokit.org instead of opening a public issue.

For full security policy, see SECURITY.md.

Audit Status

  • v0.1.0 — Initial release, community review ongoing

License

MIT


References


Questions? Open an issue or start a discussion on GitHub.

About

sorokit-contracts is a Rust/Soroban smart contract library that serves as the on-chain layer of the sorokit ecosystem. Three contracts — Escrow, Splitter, and Registry — each independently deployable, each with a corresponding TypeScript integration example in sorokit-core.

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages