Quickstart
Run the SODA v0 demo end-to-end: a Solana program signs a real Sepolia
transaction, or, with DEMO_CHAIN=sui-testnet, a Sui testnet transfer from
the same wallet’s Sui address. The whole flow takes about 10 seconds once
funds are in place.
Just want to use the SDK in your own project? You don’t need to clone
this repo. Run pnpm add @soda-sdk/core and follow the
Sign an Ethereum tx guide. This page is for the
full local demo (validator + Anchor build + signer daemon).
v0 ships with a single dev signing key (no real t-of-n committee yet). That key persists across runs, so the derived ETH address is stable: fund it once on Sepolia, demo many times.
Sepolia / devnet only. Don’t send real ETH (mainnet) or other valuable assets to a SODA-derived address while v0 is the deployed committee. A single-key signer means anyone with the keystore can move every derived balance — fine for a hackathon demo, not for production. See Concepts → Signing committee for the v1 design.
Prerequisites
- WSL (Ubuntu) or Linux.
- Rust 1.95+, Solana CLI 3.1+, Anchor 0.32.1, Node 24+, pnpm 10+.
- A Solana keypair at
~/.config/solana/id.json. - A
.envfile at the repo root withSEPOLIA_RPC_URL=...(Alchemy / Infura). The Sui path needs no RPC key: it uses Mysten’s public GraphQL endpoint.
Setup
Clone and install
git clone https://github.com/JingYuan0926/frontier
cd frontier
pnpm installSet up .env
cat > .env <<EOF
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
SOLANA_DEVNET_RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_KEY
# Optional sponsors that top the derived address up with gas just in time.
# 32-byte secp256k1 hex secrets holding testnet money only.
SEPOLIA_FUNDER_KEY=
SUI_FUNDER_KEY=
EOFFund the derived address
The first time you run the demo, it prints the derived Sepolia address (stable
across runs as long as keyshare.dev.json exists). Send around 0.001 ETH of
Sepolia testnet ETH to that address from a faucet.
For Sui, the derived address is different (the chain tag is a derivation
input) and needs at least 0.02 SUI. The demo asks the public faucet itself,
or sends from SUI_FUNDER_KEY if set; if both fail it prints the address
and polls until you fund it from faucet.sui.io.
Run the demo
The CLI is the canonical demo path.
./demo.shDefaults to Solana devnet and an Aave V3 depositETH of 0.0001 ETH: the
derived address ends up holding aWETH, a lending position owned by a Solana
account. After a successful broadcast, demo.sh automatically runs
pnpm verify <hash>, which prints the cryptographic audit trail (the checks
tying the Solana SigRequest PDA to the broadcast EVM transaction).
DEMO_ACTION=borrow ./demo.sh # Pool.borrow 0.1 USDC against the aWETH
DEMO_CHAIN=base-sepolia ./demo.sh # destination chain: sepolia | base-sepolia
DEMO_CHAIN=sui-testnet ./demo.sh # Sui: buy DEEP on DeepBook from the derived Sui address
DEMO_ACTION=sell DEMO_CHAIN=sui-testnet ./demo.sh # sell that DEEP back for SUI
SODA_DRY_RUN=1 ./demo.sh # skip the foreign-chain broadcast, verify on-chain only
SOLANA_CLUSTER=local ./demo.sh # against a local validatorThe borrow is the proof of control: a deposit could be read as “sent ETH to a contract”, but only the position’s owner can borrow against it, and the derived address ends up holding USDC it never had. Both actions go through the same pipeline; only the calldata differs.
DEMO_CHAIN=sui-testnet (or sui-devnet) runs the same pipeline against a
non-EVM chain: sui_demo BCS-encodes the Sui transaction on-chain, derives
the sender itself, and commits sha256(blake2b(intent || tx)) instead of
keccak(rlp). Sui accepts the committee’s secp256k1 signature natively, so
soda and finalize_signature are untouched.
The Sui actions are DeepBook V3 trades, and they are the Sui counterpart of
the Aave pair: swap buys DEEP with SUI off Sui’s on-chain central limit
order book, sell spends that DEEP back. The sell is the proof of control —
it moves coin objects the address acquired itself, not the SUI it was
funded with. DEMO_ACTION=transfer is a plain transfer, kept as a fallback.
demo.sh chains into pnpm verify:sui <digest> afterwards. See
Sign a Sui tx.
What you should see
- The CLI prints the derived ETH address and waits for funding (skipped if already funded).
- A
SigRequestedevent fires on Solana devnet. Visible on Solscan. - The signer signs the payload off-chain with
k256. - A
finalize_signatureinstruction runssecp256k1_recoveron-chain and stores the signature. - The signed RLP is broadcast to Sepolia. Etherscan link is printed.
pnpm verify <hash>runs automatically and prints the six audit checks.
Verifying any past run
pnpm verify 0x<sepolia_tx_hash>
DEMO_CHAIN=sui-testnet pnpm verify:sui <sui_tx_digest>Reads only public state. Re-derives the ETH (or Sui) address from the
on-chain SigRequest PDA and confirms the broadcast from / sender
address matches. Both tools look the SigRequest up under your CLI wallet;
set VERIFY_REQUESTER=<pubkey> to audit a run signed by another account.
Troubleshooting
| Symptom | Fix |
|---|---|
insufficient funds | Fund the derived address from a Sepolia faucet. |
AlreadyCompleted (custom error 0x1770) | Idempotent: another process (relayer / daemon) already signed. Safe to ignore. |
| Anchor build fails on BPF stack | You enabled on-chain ProjectivePoint ops. v0 keeps derivation off-chain. |
| Web UI shows blank balance | Set SEPOLIA_RPC_URL in apps/web/.env.local (mirrors repo-root .env). |
Sui faucet 429 Wait for Ns | The public faucet rate-limits per IP. Use faucet.sui.io, the Sui Discord, or set SUI_FUNDER_KEY; the demo keeps polling the balance. |
no SUI coin objects at 0x… | Unfunded, or the GraphQL indexer has not seen the faucet transfer yet. Check Suiscan and rerun in a few seconds. |
Sui dry-run FAILURE after a top-up | A gas coin’s version changed between reading it and building the tx. Rerun; the demo refetches coins and the payload is new. |
DEMO_CHAIN=sui-testnet still runs the EVM demo | An older demo.sh let .env override the environment. Current demo.sh keeps what you set on the command line. |