Bun-native command-line interface for 1Sat Ordinals and BSV operations. Binary name: 1sat.
Usage
# Run any command directly — no install needed
bunx @1sat/cli <command>
# 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 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
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.
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
bunx @1sat/cli ordinals list # List ordinals in wallet
bunx @1sat/cli ordinals mint --file image.png # Inscribe a file
bunx @1sat/cli ordinals mint --file note.txt --type text/plain # Override detected MIME
bunx @1sat/cli ordinals mint --file image.png --map '{"name":"NFT 1"}' # Attach MAP metadata
bunx @1sat/cli ordinals mint --file image.png --sign-with-bap # Sign inscription with BAP identity
bunx @1sat/cli ordinals transfer --outpoint <op> --to <addr> # Transfer ordinal
Marketplace (OrdLock)
bunx @1sat/cli ordinals list-for-sale # List ordinal for sale
bunx @1sat/cli ordinals cancel-listing # Cancel an active listing
bunx @1sat/cli ordinals purchase # Purchase a listed ordinal
Tokens (BSV21)
bunx @1sat/cli tokens balances # Show all token balances
bunx @1sat/cli tokens list # List token UTXOs
bunx @1sat/cli tokens send # Send tokens to address or counterparty
Paymail recipients are not supported for BSV21 transfers — there is no ecosystem spec for paymail-delivered token outputs. Use --to <address> instead.
Locks (Timelock)
bunx @1sat/cli locks status # Show lock summary
bunx @1sat/cli locks create # Lock BSV until block height
bunx @1sat/cli locks unlock # Unlock all matured locks
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.
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
bunx @1sat/cli opns register # Register identity on OpNS name
bunx @1sat/cli opns deregister # Remove identity binding
Sweep / Import
bunx @1sat/cli sweep bsv # Sweep BSV from external WIF
bunx @1sat/cli sweep ordinals # Sweep ordinals from external WIF
bunx @1sat/cli sweep tokens # Sweep BSV21 tokens from external WIF
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 (no monitor loop)
1sat serve monitor # Monitor daemon only (no HTTP)
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... --amount 5000 --quiet
# Auto-confirm prompts (non-interactive)
bunx @1sat/cli wallet send --to 1A... --amount 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.