1Sat CLI
Bun-native command-line interface for 1Sat Ordinals and BSV operations. Binary name: 1sat.
Usage
# Run any command directly — no install needed (Node ≥ 22.13 or Bun ≥ 1.2)
npx @1sat/cli <command>
bunx --bun @1sat/cli <command> # force Bun when both are installed
# Optional: install globally for frequent use
bun add -g @1sat/cli
# Then use the short form: 1sat <command>
Quick Start
# Initialize wallet and configuration
bunx @1sat/cli init
# Check wallet balance
bunx @1sat/cli wallet balance
# List ordinals in wallet
bunx @1sat/cli ordinals list
# Send BSV
bunx @1sat/cli wallet send --to 1Address... --sats 50000
If installed globally (
bun add -g @1sat/cli), replacebunx @1sat/cliwith just1sat.
Configuration
Config directory: ~/.1sat/cli/ (file: config.json).
Config command
1sat config show # Render full nested tree
1sat config set <dotted.path> <value> # Set value; tries JSON.parse, falls back to string
1sat config unset <dotted.path> # Remove value at path
1sat config path # Print config directory
Values are stored in their native JSON type when the raw argument is valid JSON (numbers, booleans, arrays, objects, quoted strings); bare words fall back to string. Examples:
1sat config set chain main # "main" (string)
1sat config set server.port 8100 # 8100 (number)
1sat config set server.accounts.enabled true # true (boolean)
1sat config set server.accounts.freeIdentityKeys '["02aa...","02bb..."]'
Nested subtrees (like server / server.storage / server.accounts) are created automatically on set and persist as JSON objects in the file.
Monitor (Background Sync)
The CLI runs the wallet's transaction-lifecycle Monitor once on every invocation when local storage is active. The Monitor services pending broadcasts, merkle proofs, and related tasks. Individual tasks self-throttle on their own intervals, so running many CLI commands in rapid succession does not trigger repeated work — it's a cheap no-op once each task's interval is satisfied.
There is no user-facing monitor configuration. No interval knob, no disable flag — nothing to tune. If a remote is the active storage, the server runs its own monitor and the CLI skips firing one client-side.
Database Locations
- Wallet DB:
~/.1sat/data/wallet-main.db(orwallet-test.dbfor testnet) - Sync DB:
~/.1sat/data/sync-<identityKeyPrefix>.db - Config:
~/.1sat/config.json - Keys:
~/.1sat/keys.bep
Key Management
Keys can be provided in three ways:
- Secure Enclave (macOS arm64): Hardware-protected via
@1sat/vault— keys encrypted with SE P-256 key, decryption requires Touch ID. Used bybap touchid enableandclawnet setup-key. - Environment variable: Set
PRIVATE_KEY_WIFwith your WIF private key - Encrypted keystore: Stored at
~/.1sat/keys.bep(created duringbunx @1sat/cli init)
# Using env var
PRIVATE_KEY_WIF=L1abc... bunx @1sat/cli wallet balance
# Using an env file (file values override existing env for this run)
bunx @1sat/cli --env-file mint.env wallet balance
# Using encrypted keystore (created by init)
bunx @1sat/cli init
bunx @1sat/cli wallet balance
Commands
All examples below use
bunx @1sat/cli. If installed globally, use1satinstead.
Wallet
bunx @1sat/cli wallet balance # Show BSV balance
bunx @1sat/cli wallet address # Show primary deposit address
bunx @1sat/cli wallet address --count 5 --start-index 0 # Derive multiple addresses
bunx @1sat/cli wallet address --prefix mcp --count 3 # Custom BRC-29 prefix
bunx @1sat/cli wallet send --to 1A... --sats 5000 # Send BSV to address
bunx @1sat/cli wallet send --script <hex> --sats 5000 # Send to custom locking script
bunx @1sat/cli wallet send --data-asm "deadbeef cafe" # Publish OP_RETURN (0 sats)
bunx @1sat/cli wallet send-all --to 1A... # Send entire balance
bunx @1sat/cli wallet sync --prefix mcp --count 5 # Sync inbound payments at BRC-29 addresses
bunx @1sat/cli wallet info # Address, identity key, balance, network
wallet send modes are mutually exclusive: --to (P2PKH), --script (custom hex), or --data-asm (OP_RETURN, 0 sats — rejects --sats). The ASM string is passed straight to Script.fromASM('OP_0 OP_RETURN ' + asm), so multiple space-separated hex pushes are supported in one output.
BRC-100 interface
Lower-level BRC-100 wallet operations are also exposed:
bunx @1sat/cli wallet list-outputs --basket <name> [--tags <t1,t2>] [--limit <n>] [--include-tags]
bunx @1sat/cli wallet relinquish-output --basket <name> --output <txid.vout>
bunx @1sat/cli wallet list-actions [--labels <l1,l2>] [--limit <n>]
bunx @1sat/cli wallet create-action '<json>'
bunx @1sat/cli wallet sign-action '<json>'
bunx @1sat/cli wallet abort-action --reference <ref>
bunx @1sat/cli wallet list-certificates [--certifiers <c1,c2>] [--types <t1,t2>] [--limit <n>]
bunx @1sat/cli wallet relinquish-certificate --type <t> --serialNumber <s> --certifier <c>
Remote Storage
bunx @1sat/cli remote add <url> # Add remote as backup (no immediate validation)
bunx @1sat/cli remote list # Show all remotes and status
bunx @1sat/cli remote delete <url> # Remove a remote from backup list
bunx @1sat/cli remote set-active <url> # Migrate TO this remote as primary
bunx @1sat/cli remote set-active local # Switch back to local-primary
bunx @1sat/cli remote status [url] # Per-identity capacity + pricing snapshot
bunx @1sat/cli remote topup [url] # Manual BRC-29 top-up to `/account/payment`
Ordinals (id-first)
Wallet-owned spends use --id (from list / tags). External buy uses --outpoint and optional --beef.
bunx @1sat/cli ordinals list
bunx @1sat/cli ordinals inscribe --file image.png
bunx @1sat/cli ordinals inscribe --file note.txt --type text/plain
bunx @1sat/cli ordinals inscribe --file image.png --map '{"name":"NFT 1"}'
bunx @1sat/cli ordinals inscribe --file image.png --sign-with-bap
bunx @1sat/cli ordinals send --id <id> --to <addr|identityKey>
bunx @1sat/cli ordinals sell --id <id> --price <sats> [--pay-address <addr>]
bunx @1sat/cli ordinals cancel --id <id>
bunx @1sat/cli ordinals buy --outpoint <txid.vout> [--beef <file|hex|base64>]
bunx @1sat/cli ordinals burn --ids <id1,id2>
Breaking renames: mint→inscribe, transfer→send, wallet --outpoint→--id.
BSV21 (was tokens)
bunx @1sat/cli bsv21 balances
bunx @1sat/cli bsv21 list [--token-id <id>]
bunx @1sat/cli bsv21 send --token-id <id> --amount <n> --to <addr>
bunx @1sat/cli bsv21 deploy-mint --symbol <ticker> --amount <total-supply>
bunx @1sat/cli bsv21 deploy-auth --symbol <ticker>
bunx @1sat/cli bsv21 mint --token-id <id> --amount <n>
bunx @1sat/cli bsv21 buy --outpoint <txid.vout> --token-id <id> --amount <n> [--beef …]
bsv21 send destination: --to, --counterparty, or --locking-script. No paymail for BSV21.
Locks (Timelock)
bunx @1sat/cli locks list # UTXOs with id / until tags
bunx @1sat/cli locks bsv --sats <amount> --until <height> # Time-lock until block height
bunx @1sat/cli locks unlock [--ids <id1,id2>] # Specific or all matured
Breaking: info→list, lock→bsv, --blocks→--until.
Identity (BAP)
bunx @1sat/cli identity create # Create/publish BAP identity
bunx @1sat/cli identity info # Show identity key
bunx @1sat/cli identity update-profile --profile '{"name":"..."}' # Update profile (JSON blob)
bunx @1sat/cli identity sign --message "hello" # BSM sign with identity key
bunx @1sat/cli identity sign --message 68656c6c6f --encoding hex # Sign hex-encoded bytes
identity sign uses BSM (Bitcoin Signed Message) compact format. --encoding accepts utf8 (default), hex, or base64 — it only controls how --message is decoded to bytes; the same bytes always produce the same signature. identity verify exists in the command tree but is marked unavailable (not yet implemented).
Social (BSocial)
bunx @1sat/cli social post --content "hello world"
bunx @1sat/cli social post --content "# heading" --content-type text/markdown
bunx @1sat/cli social post --content "ordinals drop" --tags bsv,ordinals,nft
bunx @1sat/cli social post --content "..." --app my-app # MAP attribution
Tags (--tags a,b,c) land on the on-chain post's MAP payload and on the wallet output as tag:<value>, so wallet list-outputs --tags tag:bsv finds your own posts by tag. --content-type accepts text/plain (default) or text/markdown.
OpNS Names (id-first)
bunx @1sat/cli opns lookup [--limit] [--offset] [--ids] [--names] [--tags] …
bunx @1sat/cli opns register --id <id>
bunx @1sat/cli opns deregister --id <id>
bunx @1sat/cli opns sell --id <id> --price <sats> [--pay-address <addr>]
bunx @1sat/cli opns cancel-listing --id <id>
bunx @1sat/cli opns send --id <id> --to <addr|identityKey>
bunx @1sat/cli opns buy --outpoint <txid.vout> [--beef …]
bunx @1sat/cli opns internalize --beef <file|hex|base64> [--key-id 1sat 0]
Print id, name, outpoint on lookup. Wallet spends: --id only.
Sweep / Import
bunx @1sat/cli sweep scan --wif <key> # Scan an address for sweepable UTXOs (BSV, ordinals, BSV21)
bunx @1sat/cli sweep import --wif <key> # Sweep all UTXOs from a WIF into the wallet
sweep import branches internally by asset type — it sweeps funding (BSV), ordinals, and each active BSV21 token in separate transactions. RUN token outputs are detected and excluded (not sweepable).
Transaction Utilities
bunx @1sat/cli tx decode <hex> # Decode a raw transaction hex
Serve (Wallet Storage RPC Server)
The same binary can run a BRC-100 wallet storage RPC server backed by the same wallet instance the CLI commands use.
1sat serve # Wallet server + monitor daemon (single process)
1sat serve wallet # Wallet server only (BRC-100 HTTP, no monitor loop)
1sat serve monitor # Monitor daemon only (no HTTP)
1sat serve messagebox # BSV message-box server (port 8771 default; uses wallet identity)
Key properties:
- Same wallet instance —
servewraps the wallet created bycreateNodeWalletusing the exact same config inputs (chain,dataDir,storageIdentityKey,activeRemote,backups) as1sat wallet <command>. There's one wallet on disk at~/.1sat/data/wallet-${chain}.db; HTTP and CLI access the same one. - Server identity = CLI identity — loaded via
loadKey()(PRIVATE_KEY_WIFenv orkeys.bep+ONESAT_PASSWORD). No separate server key. - Storage provider — defaults to
bun-sqlite. Setserver.storage.providertopgfor Postgres deployments. - Accounts layer (opt-in) — per-identity capacity metering on billable writes. Reads always free. Over-capacity writes get an HTTP
507 Insufficient Storagewith a JSON body describing deficit + pricing. Top-up viaPOST /account/payment(BRC-29 payment body). Toggle via1sat config set server.accounts.enabled true.
Server-specific settings live under server.* in the config — edit via 1sat config set:
1sat config set server.port 8100
1sat config set server.host 0.0.0.0
1sat config set server.accounts.enabled true
1sat config set server.accounts.baselineBytes 1073741824 # free baseline per identity
1sat config set server.accounts.purchaseUnitBytes 1073741824 # chunk size (default 1 GB)
1sat config set server.accounts.satsPerUnit 1000000 # price per chunk
1sat config set server.accounts.durationBlocks 4383 # validity window (~1 month)
Pricing model: new payments charge unitsCharged × satsPerUnit (rounded up to a whole chunk) for durationBlocks from now, minus a prorated refund credit for unused time on the prior payment. One active payment row per account at a time.
Defaults when keys are unset:
server.host→127.0.0.1server.port→8100server.storage.provider→bun-sqliteserver.accounts.enabled→falseserver.accounts.baselineBytes→1073741824(1 GB)server.accounts.purchaseUnitBytes→1073741824(1 GB)server.accounts.satsPerUnit→1000000server.accounts.durationBlocks→4383
Action Escape Hatch
Any registered action can be invoked directly by name with a JSON input:
# Run any action from the action registry
bunx @1sat/cli action <name> <json>
# Examples
bunx @1sat/cli action sendBsv '{"requests":[{"address":"1A...","satoshis":5000}]}'
bunx @1sat/cli action lockBsv '{"requests":[{"satoshis":10000,"until":900000}]}'
bunx @1sat/cli action inscribe '{"base64Content":"SGVsbG8=","contentType":"text/plain"}'
This is the escape hatch for any operation supported by the @1sat/actions registry, even those without dedicated CLI subcommands.
Origin Tag Resolution
When working with ordinal outputs from ordinals list --json, the tags array indicates origin relationships for building content URLs:
"origin"(bare tag, no colon) — This output IS the origin. Use the output's ownoutpointfor content URLs."origin:<txid.vout>"(tag with colon and outpoint) — This is a transfer. Use the origin outpoint from the tag for content URLs.
Example:
{
"outpoint": "5148d8dae125c2851283ca90519eae787ab24baca5a008ee72c03ba3c290def4.1",
"tags": ["origin:04fc3e5f92004f89c7efe94fb97113bd672faa87708f9e0cd67fdef861767c2c.0", ...]
}
→ Content URL: .../content/04fc3e5f92004f89c7efe94fb97113bd672faa87708f9e0cd67fdef861767c2c.0 (from tag)
{
"outpoint": "a70be5c136d23ab10fbe1e4344552797faa5017ff7f5a58c4b9bf14e7a3e1006.0",
"tags": ["origin", ...]
}
→ Content URL: .../content/a70be5c136d23ab10fbe1e4344552797faa5017ff7f5a58c4b9bf14e7a3e1006.0 (own outpoint)
Outpoint format: Always use the outpoint as-is (txid.vout or txid_vout). Do NOT modify the separator.
Global Flags
| Flag | Description |
|---|---|
--json |
Output as JSON (for scripting/piping) |
--quiet, -q |
Suppress output |
--yes, -y |
Skip confirmations |
--chain <main|test> |
Network (default: main) |
--help, -h |
Show help |
--version, -v |
Show version |
Output Modes
# JSON output (for scripting/piping)
bunx @1sat/cli wallet balance --json
# Quiet mode (minimal output)
bunx @1sat/cli wallet send --to 1A... --sats 5000 --quiet
# Auto-confirm prompts (non-interactive)
bunx @1sat/cli wallet send --to 1A... --sats 5000 --yes
Init Flow
bunx @1sat/cli init
The init command:
- Prompts for network selection (mainnet/testnet)
- Generates or imports a private key (WIF)
- Sets a password and encrypts the key to
~/.1sat/keys.bep - On macOS arm64, optionally enables Touch ID to cache the password via
@1sat/vault - Optionally configures a primary remote storage URL (local becomes backup)
- Writes config to
~/.1sat/config.json(chain, generatedstorageIdentityKey,activeRemote)
Requirements
- Bun runtime (not Node.js)
- Network access to
api.1sat.app
Package
# Run without installing
bunx @1sat/cli
# Or install globally
bun add -g @1sat/cli
The CLI wraps @1sat/actions, @1sat/wallet-node, @1sat/client, and @1sat/wallet-server (for the serve subcommand) into a single command-line interface.