Architecture
SODA splits responsibilities across three layers: an on-chain primitive
(soda Anchor program), an off-chain MPC committee (signing daemons),
and a permissionless relayer that broadcasts to the foreign chain.
End-to-end pipeline
The full request-to-broadcast flow, taking the v0 ETH demo as the example:
In v0 the demo CLI also broadcasts to Sepolia in parallel with the relayer. Both attempts converge on the same transaction hash; whichever lands first wins, the other observes “already known” and exits cleanly.
Components
On-chain (Anchor)
Two programs, both deployed on Solana devnet:
| Program | Devnet ID | Purpose |
|---|---|---|
soda | 99apYWpnoMWwA2iXyJZcTMoTEag6tdFasjujdhdeG8b4 | Core primitive: init_committee, request_signature, finalize_signature |
eth_demo | 9g9eAkNbjpkVLi692vhgcUapJKS26yQTgsLzKbXKJXWM | Demo harness: builds RLP, keccaks, CPIs into soda |
The soda program never imports k256 or any heavy curve crate. It uses only
the Solana runtime’s secp256k1_recover syscall (~25,000 CU) for verification.
That keeps the BPF stack budget intact.
Off-chain components
Signer daemon (contracts/signer/)
A Rust Tokio binary that:
- Subscribes to Solana logs filtered by the
sodaprogram ID. - Decodes
SigRequestedevents fromProgram data: <base64>log lines using borsh + thesha256("event:SigRequested")[..8]discriminator. - Computes
foreign_pk = group_pk + tweak·Gfor each known requester program and matches it against the storedforeign_pk_xyon the event. (See Concepts → Derivation.) - Signs the payload with
(sk + tweak) mod nviak256::ecdsa::SigningKey::sign_prehash_recoverable. - Submits a
finalize_signatureinstruction (manually built — noanchor-clientdep).
Run with pnpm signer.
Relayer (apps/relayer/)
A standalone Node service that:
onLogs-subscribes to both program IDs.- Manually decodes
EthTxRequestedandSigCompletedborsh events. - Caches
(sig_request, chain_id, unsigned_rlp)fromEthTxRequested. - On
SigCompleted, looks up the cached unsigned RLP, re-encodes with(v, r, s)via@soda-sdk/core, and POSTseth_sendRawTransaction.
Run with pnpm relayer:dev.
TypeScript SDK (packages/soda-sdk/)
Source-only ESM workspace package. Exports derivation, RLP encode / decode,
EIP-155 v calculation, and an EthRpc client. Consumed by the demo CLI,
the web app, and the relayer via workspace:*.
v0.5: real Lindell ‘17 2-of-2 MPC ECDSA — live on AWS
As of 2026-05-11 the demo runs against a real threshold ECDSA committee deployed across three EC2 instances in us-east-1:
| Role | Public IP | Holds |
|---|---|---|
mpc-node-p1 | 44.201.168.181 | share x1 |
mpc-node-p2 | 54.88.35.104 | share x2 |
mpc-coordinator | 32.198.7.34 (health) | nothing — only forwards bytes |
The on-chain Committee.group_pk was migrated from the v0 single key to
the AWS joint key via the new
update_committee ix.
./demo.sh now drives signing through the AWS coordinator without any
flag changes. Neither node sees the joint secret. Signing runs the
4-message Lindell ‘17 protocol; on-chain
secp256k1_recover verifies the result unchanged.
caller program → request_signature → SigRequested
│
▼
apps/mpc-subscriber (Solana)
│ POST /sign
▼
apps/mpc-coordinator (drives 4-msg protocol)
/ \
▼ ▼
mpc-node-p1 mpc-node-p2
(holds x1) (holds x2)
r,s,v ← combined signature
│
▼
soda::finalize_signature (secp256k1_recover, no change)Run with pnpm mpc:dkg (once) → pnpm mpc:up → pnpm mpc:update-committee
(swap on-chain group_pk) → pnpm mpc:subscribe. See
Deploy → AWS MPC for the production-shape walkthrough.
Trust model: v0 vs v0.5 vs v1
| Layer | v0 | v0.5 (today) | v1 (target) |
|---|---|---|---|
| Derivation | Off-chain by caller, on-chain bytes-compare | Same | On-chain group_pk + tweak·G (heap-allocated / syscall / zk) |
| Signing key | Single dev k256 on disk | Lindell ‘17 2-of-2, neither node sees group_sk | 2-of-3+ via GG18 / GG20 / CGG21 |
| Fault tolerance | 0 (one box) | 0 (both must be up) | Tolerates n - t losses |
| Committee membership | Hardcoded singleton | Hardcoded pair | Restaked SOL via Solayer / Jito, slashable |
| Replay protection | PDA seed uniqueness | Same | PDA + nonce + expiry |
See Concepts → Committee for the long-form v1 design.