CLI for buying & selling tokenized stocks and ETFs (Ondo Global Markets) on Solana via Jupiter. 264 tokens — TSLA, AAPL, NVDA, SPY, QQQ, and more.
⚠️ Beta (pre-v1). Breaking changes possible. Not financial advice — use at your own risk, and always preview with--dry-runbefore trading real funds.
curl -fsSL https://raw.githubusercontent.com/outputlayer/rwa_cli/main/install.sh | shBinary-first: downloads a pre-built binary for Linux/macOS/Windows from Releases, falling back to a source build. Pin a version with RWA_VERSION=v0.2.9 sh, or build directly: cargo install --git https://github.com/outputlayer/rwa_cli --bin rwa.
Already installed? Update in place: rwa update (--check to preview, -y to skip the prompt).
rwa keys generate --encrypt # 1. Create an encrypted Solana wallet
rwa keys show # 2. Show your address — fund it with SOL + USDC
rwa gm hours # 3. Check the market session
rwa gm buy TSLA 100 --dry-run # 4. Preview a $100 TSLA buy
rwa gm buy TSLA 100 -y # 5. Execute it
rwa gm portfolio # 6. See your holdingsFund your wallet with SOL (network fees) and USDC (trading) before your first trade.
| Command | Description |
|---|---|
rwa gm hours [--tradable] |
Current session + tradable count (or list tradable tokens) |
rwa gm list |
All 264 tokens with tradable status |
rwa gm search --search <kw> |
Search by name/symbol/sector |
rwa gm search --tradable-only --sector <S> --type <stock|etf> |
Bulk filtering |
rwa gm tradable [SYM ...] |
Check tradable status for one or many symbols |
rwa gm buy <SYM> <AMT> [-y] [--dry-run] [--slippage <BPS>] |
Buy with USDC |
rwa gm sell <SYM> <AMT> [-y] [--dry-run] |
Sell for USDC (exact, 50%, or all) |
rwa gm close-all [<PCT>] [-y] [--parallel] |
Sell every position (or a % of each) |
rwa gm buy-basket <SYM AMT ...> [-y] [--parallel] |
Buy multiple tokens at once |
rwa gm sell-basket <SYM AMT ...> [-y] [--parallel] |
Sell multiple tokens at once |
rwa gm portfolio [WALLET] |
Holdings + allocation + 24h change |
rwa gm history <SYM> [-r RANGE] |
Price history (1D/1W/1M/3M/1Y/ALL) |
rwa gm send <TOKEN> <AMT> <TO> [-y] [--dry-run] |
Send SOL/USDC/tokens to another wallet |
rwa gm reclaim [--token <SYM>] |
Close empty token accounts, reclaim SOL rent |
rwa keys generate | import | show | encrypt | decrypt |
Wallet management |
rwa update [--check] [-y] |
Update rwa to the latest release |
-
Preview first. There's no
quotecommand — use--dry-runonbuy/sellto validate and see a quote without executing. -
-yskips the confirmation prompt (required for non-interactive/agent use). -
--quote-only(onbuy) previews a quote for any size, skipping the wallet-balance check — useful to size a trade before funding. It never executes (combining with-yis rejected) and still blocks on a closed market or >3% slippage. Output uses the samedry_runshape. -
--max-bps <N>(buy/sell, baskets, close-all) rejects the trade if the quoted all-in cost (spread + Jupiter fee) exceeds N basis points — a tunable ceiling tighter than the 3% slippage block. Set a global default withRWA_MAX_BPS. Applies in--dry-run/--quote-onlytoo, so agents can use it as a pass/fail cost check. -
Amounts: exact (
100), percentage (50%), orall. Converted with exact on-chain precision — too many decimals is rejected, never silently rounded. -
send≠sell.sellswaps a token to USDC;sendtransfers assets to another wallet.send USDC allsends your entire USDC balance, not just recent proceeds. -
close-allis the canonical way to exit many positions; it skips dust (< $1.50) and reports it. Use--parallelto run all swaps at once. -
Slippage: default 1% (100 bps), tunable with
--slippage; swaps above 3% are blocked. -
Trading sessions are ET-based and not every token trades in every session:
Session Hours (ET) Pre-Market 4:00 – 9:29 AM Regular 9:30 AM – 3:59 PM Post-Market 4:00 – 7:59 PM Overnight 8:00 PM – 3:59 AM Closed Weekends / NYSE holidays
Install the skill pack, then talk to your agent in plain language:
npx skills add outputlayer/rwa_skills -g -yBuy $100 of TSLAon · Show my portfolio · Create a wallet · Send all USDC to <ADDRESS>
| Skill | Covers |
|---|---|
rwa-trade |
buy, sell, hours, tradable, close-all, baskets |
rwa-portfolio |
holdings, allocation, 24h change, history |
rwa-wallet |
create/import/encrypt, send/withdraw, reclaim, update |
Every command supports --json for machine-readable output — treat it as a stable contract. Agent rules: prefer --json, use -y only for real execution, --dry-run for previews, and never run wallet-changing commands as parallel shell processes (use the built-in --parallel flag instead). See outputlayer/rwa_skills for manual install (Cursor, Claude Code, OpenCode, …).
The default public Solana endpoints are rate-limited with no SLA. Under load you may hit Solana RPC error [unavailable] ... HTTP 429. The CLI retries transient errors with backoff, but the real fix is a dedicated endpoint — a free API key from Alchemy, Helius, QuickNode, Chainstack, or dRPC:
export RWA_RPC_URL="https://<your-endpoint>" # or pass --rpc-urlWhen set, the CLI uses only that endpoint (no silent fallback).
Jupiter calls use api.jup.ag (the public lite-api.jup.ag host is deprecated and throttled to ~1 request/second). Under heavy load, set a free Jupiter API key for higher limits:
export RWA_JUPITER_API_KEY="<your-jupiter-key>"If the Solana RPC read fails entirely, gm portfolio automatically falls back to Jupiter's Ultra holdings API (api.jup.ag/ultra/v1/holdings) so it still returns your balances; the JSON output then includes "source":"jupiter".
Performance & parallel speedup
| Command | Time |
|---|---|
keys show / --help |
~7 ms (local) |
gm hours / history |
~0.5 s |
gm buy --dry-run |
~0.8 s |
gm portfolio |
~1.0 s (RPC + Ondo, parallel) |
gm list |
~1.3 s |
gm buy / sell |
~22 s (swap confirmation) |
close-all / baskets, sequential |
N×22 s + (N−1)×3 s |
close-all / baskets, --parallel |
~22 s flat |
--parallel runs all swaps of different tokens concurrently from one wallet at normal spread (live-tested: 4-token parallel buy → immediate parallel sell, all 8 swaps at 0.2–0.7%):
| Tokens | Sequential | Parallel | Speedup |
|---|---|---|---|
| 2 | ~47 s | ~22 s | 2.1× |
| 4 | ~97 s | ~22 s | 4.4× |
| 6 | ~147 s | ~22 s | 6.7× |
| 8 | ~197 s | ~22 s | 9.0× |
| 10 | ~247 s | ~22 s | 11.2× |
How it works (tokens, routing, JSON contract)
- Ondo GM tokens are total-return trackers (dividends reinvested, so 1 token ≠ 1 share). Both
TSLAandTSLAonsymbol forms are accepted. - Swaps route through Jupiter:
lite-api.jup.ag/swap/v2first, thenultra-api.jup.ag, thenlite-api.jup.ag/ultra/v1, withlite-api.jup.ag/swap/v1as the final fallback. Jupiter covers gas for many swaps; you still need SOL for transfers. portfolioJSON separates cash from positions:cash.{sol,usdc}andgm_positions.{value_usd, change_24h_usd, change_24h_pct, positions[].gm_alloc_pct}. Symbols with unavailable market data are listed underunavailable[].- Trade/send/close-all JSON shapes are covered by regression tests and kept stable. Surfaced
error_kindvalues includemarket_closed,not_tradable,slippage_too_high,cost_too_high,confirmation_timeout,on_chain_failure,execute_unavailable,route_unfillable,rpc_unavailable,amount_below_minimum,insufficient_funds,no_position. Exit code 75 marks transient failures worth retrying; 1 everything else.
Architecture
bin/rwa/ → binary entry point
crates/cli/ → clap parsing, human/JSON output, process lock, command orchestration
crates/ondo/ → protocol layer
amounts.rs → exact amount parsing/formatting/percent math
audit.rs → persistent JSONL audit log for swaps
usecases/ → buy/sell/close-all flows
solana/ → rpc/ (retry + URL race), fee, transaction, transfer, balance
jupiter.rs + jupiter/ → swap backends (order, execute, types)
api/ → Ondo session/pricing/history
wallet/ → Ed25519 wallet (SLIP-10, BIP39) + tx verification
~13k lines of Rust, ~3 MB binary, 270+ tests. Pure Rust, no native C deps, no unsafe. Uses rustls-tls and ed25519-dalek (with zeroize).
cargo build
cargo test --workspace
cargo clippy --all-targets
cargo run -- gm hoursMIT