Soroban smart contracts for Mux Protocol — core logic for account abstraction, batching, and automation on Stellar.
This repository contains the core Soroban smart contracts that power Mux. Contracts handle:
- Account abstraction logic
- Transaction batching
- Permissions and delegation
- Automated workflows for Stellar accounts
| Contract | Description |
|---|---|
contracts/mux-account |
Account abstraction: owner, delegates, spend limits, guardian set |
contracts/mux-batcher |
Atomic multi-operation batching with optional per-op failure handling |
contracts/mux-permissions |
RBAC registry — roles, permissions, grant/revoke |
Pre-built clients for every contract live in bindings/.
Install from npm:
npm install @mux-protocol/contractsTo regenerate bindings from local WASM (after editing contracts):
bash scripts/generate-bindings.shThe CI pipeline (.github/workflows/bindings.yml) regenerates, type-checks, and tests bindings on every PR and publishes to npm on tagged releases.
- Soroban smart contracts (Rust)
- Stellar Soroban SDK v21
- TypeScript SDK bindings (
@stellar/stellar-sdk) - Docker & Docker Compose for local Soroban development
- GitHub Actions CI
git clone https://github.com/mux-labs/mux-contracts.git
cd mux-contracts
# Build all contracts
cargo build --target wasm32-unknown-unknown --release --workspace
# Run unit tests
cargo test --workspace --all-features
# Generate TypeScript bindings
bash scripts/generate-bindings.sh
# Build TypeScript package
cd bindings && npm ci && npm run buildIntegration tests connect to a live Soroban RPC endpoint (localnet, testnet, or mainnet) and verify contract deployment.
Run integration tests:
cd bindings
# Against localnet (requires docker-compose to be running)
SOROBAN_NETWORK=localnet npm test
# Against testnet
SOROBAN_NETWORK=testnet npm test
# Tests gracefully skip if the network is unavailable
npm testConfiguration:
Network endpoints are configured in bindings/src/network.ts via environment variables:
SOROBAN_NETWORK- Which network to use (default:localnet)LOCALNET_RPC_URL- RPC endpoint for localnet (default:http://localhost:8000)LOCALNET_NETWORK_PASSPHRASE- Network ID for localnetLOCALNET_MUX_*_ID- Contract addresses on localnet
Setting up localnet locally:
See docker-compose.yml for spinning up a local Stellar/Soroban node.
Contract addresses are managed per network via config/addresses.json and environment variables.
Configuration structure:
{
"localnet": {
"muxAccount": "CADDRESS...",
"muxBatcher": "CADDRESS...",
"muxPermissions": "CADDRESS..."
},
"testnet": { ... },
"mainnet": { ... }
}Using contract addresses in your application:
import { getNetworkConfig } from "@mux-protocol/contracts";
// Get active network from SOROBAN_NETWORK env var (default: localnet)
const config = getNetworkConfig();
console.log(config.contracts.muxAccount); // Contract address
console.log(config.rpcUrl); // RPC endpointEnvironment variable overrides:
Override addresses per network using environment variables:
SOROBAN_NETWORK=testnet
TESTNET_MUX_ACCOUNT_ID=CADDRESS...
TESTNET_MUX_BATCHER_ID=CADDRESS...
TESTNET_MUX_PERMISSIONS_ID=CADDRESS...The pattern is {NETWORK}_MUX_*_ID. Environment variables take precedence over config/addresses.json.
Validating addresses at startup:
import { getValidatedAddresses, DEFAULT_ADDRESSES } from "@mux-protocol/contracts";
// Fails fast if any required addresses are missing for the active network
const addresses = getValidatedAddresses("testnet", DEFAULT_ADDRESSES);Contract errors are mapped to HTTP status codes for API/gateway implementations.
Using error mapping in your API:
import {
contractErrorToHttp,
ERROR_HTTP_MAP,
type HttpErrorResponse,
} from "@mux-protocol/contracts";
// Convert a contract error to HTTP response
const httpError: HttpErrorResponse = contractErrorToHttp("Unauthorized");
// { statusCode: 401, message: "Unauthorized", errorType: "Unauthorized" }
// Use in Express middleware example:
async function handleContractCall(req, res) {
try {
const result = await muxAccount.transfer(/*...*/);
res.json(result);
} catch (error) {
const httpError = contractErrorToHttp(String(error));
res.status(httpError.statusCode).json({
error: httpError.errorType,
message: httpError.message,
});
}
}Status code mappings:
- 401 Unauthorized —
Unauthorized,Expired - 404 Not Found —
*NotFound,*NotInRole,*NotInitialized(when expected to exist) - 400 Bad Request — Invalid input, validation failures, constraint violations
- 409 Conflict —
AlreadyInitialized - 500 Internal Server Error — Unexpected or initialization errors
Run a complete local Stellar/Soroban node for offline development and testing:
# Start the localnet
docker-compose up --wait
# Verify the node is ready
curl -X POST http://localhost:8000 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getNetwork","params":[]}'
# In another terminal, run tests against localnet
cd bindings
SOROBAN_NETWORK=localnet npm test
# Stop the localnet
docker-compose down
# Remove persisted data and start fresh
docker-compose down -vEnvironment Configuration:
Copy .env.localnet.example to .env.localnet to customize:
cp .env.localnet.example .env.localnet
# Edit .env.localnet and set contract addresses after deploymentDeploying Contracts to Localnet:
After starting the localnet, build and deploy contracts:
# Build contracts
cargo build --target wasm32-unknown-unknown --release --workspace
# Use Stellar CLI to deploy (requires `stellar` CLI installed)
stellar contract deploy --wasm target/wasm32-unknown-unknown/release/mux_account.wasm
# ... repeat for other contracts and save the contract IDs to .env.localnet- Account Abstraction Design — Goals, architecture, session key design, and transaction flows
- Threat Model — assets, trust boundaries, and mitigations
- Access Control Review Checklist — pre-deployment and pre-audit checklist
- Storage Griefing Notes — collection caps, TTL management, keeper runbook
- External Audit Prep — scope, entry points, known limitations, auditor checklist
To report a vulnerability, open a private security advisory on GitHub.
- Breaking Change Policy — guidelines for backward compatibility, deprecation periods, and versioning