SUBTC-CLI V1.0.0 — Bitcoin Testnet
# SUBTC-CLI V1.0.0 — Bitcoin Testnet
Gateway V4.2 · Stateless · Idempotent · curl-first
---
## Quick Start
./subtc key create
./subtc wallet create
./subtc wallet receive <wid>
./subtc wallet balance <wid>
./subtc send <wid> tb1qXXX 50000
./subtc wait <wid> tb1qXXX 20000
./subtc poll <wid> tb1qXXX 20000 --loop
---
## Key Management
./subtc key create # Create & save API key
./subtc key status # Verify current key
---
## Wallet Management
./subtc wallet create # Create testnet wallet
./subtc wallet list # List all wallets
./subtc wallet receive <wid> # Generate receive address
./subtc wallet balance <wid> # Show balance (SAT + BTC)
---
## Send BTC
./subtc send <wid> <addr> <sat> [idem] # Idempotent send (min 50,000 sat)
---
## Inbox / Receive
./subtc inbox <wid> <expected_sat> # Pre-configured receive inbox
---
## Wait / Poll
./subtc wait <wid> <addr> <sat> [timeout_sec] [callback_url] # Long-poll or webhook on payment
./subtc poll <wid> <addr> <sat> # Single poll check
./subtc poll <wid> <addr> <sat> --loop # Loop poll until confirmed
---
## Config
./subtc config show # Show stored config
./subtc config set-key <key> # Store API key
./subtc health # Node health check
---
## Examples
./subtc key create
./subtc wallet create
./subtc wallet receive wlt_abc123
./subtc wallet balance wlt_abc123
./subtc send wlt_abc123 tb1qXXX 50000
./subtc wait wlt_abc123 tb1qXXX 20000
./subtc poll wlt_abc123 tb1qXXX 20000 --loop
---
## Units & Fees
1 BTC = 100,000,000 SAT
Fee: 3%
Minimum send: 50,000 SAT
---
## Documentation
https://subtc.net/api
---
## Test on Linux
Install tools and build CLI:
go build -o subtc subtc-cli.go
Example run:
root@subtc:~/subtc# ./subtc key create
Key Created
────────────────────────────────────────────
key: SUBTC-KEY-3f470eb8d4d0499c12178d5c8c348a66c0c33d7ec941cdd7
✓ Key saved → ~/.subtc.json
root@subtc:~/subtc# ./subtc wallet create
Wallet Created · testnet
Code : subtc-cli.go
// subtc-cli.go — SUBTC CLI V1 · Testnet · Single File
// Gateway V4.2 · Bitcoin Only · Zero external dependencies
//
// Build: go build -o subtc subtc-cli.go
// Usage: ./subtc help
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// ─────────────────────────────────────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────────────────────────────────────
const (
BaseURL = "https://api.subtc.net"
Network = "test"
ConfigFile = ".subtc.json"
MinSendSat = 50_000
Version = "1.0.0"
)
// ─────────────────────────────────────────────────────────────────────────────
// Terminal colors
// ─────────────────────────────────────────────────────────────────────────────
const (
cReset = "\033[0m"
cBold = "\033[1m"
cGreen = "\033[32m"
cYellow = "\033[33m"
cRed = "\033[31m"
cCyan = "\033[36m"
cGray = "\033[90m"
)
// ─────────────────────────────────────────────────────────────────────────────
// Config (~/.subtc.json)
// ─────────────────────────────────────────────────────────────────────────────
type Config struct {
Key string `json:"key"`
}
func configPath() string {
home, _ := os.UserHomeDir()
return filepath.Join(home, ConfigFile)
}
func loadConfig() Config {
f, err := os.Open(configPath())
if err != nil {
return Config{}
}
defer f.Close()
var c Config
json.NewDecoder(f).Decode(&c)
return c
}
func (c Config) save() error {
f, err := os.OpenFile(configPath(), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(c)
}
func (c Config) key() string {
if v := os.Getenv("SUBTC_KEY"); v != "" {
return v
}
return c.Key
}
func (c Config) requireKey() {
if c.key() == "" {
die("no API key — run: subtc key create")
}
}
// ─────────────────────────────────────────────────────────────────────────────
// HTTP client
// ─────────────────────────────────────────────────────────────────────────────
var httpClient = &http.Client{Timeout: 360 * time.Second}
type J = map[string]any
func apiGET(path string) J {
resp, err := httpClient.Get(BaseURL + path)
must(err)
defer resp.Body.Close()
return decode(resp.Body)
}
func apiPOST(cfg Config, mode string, body J, idempotency string) J {
if body == nil {
body = J{}
}
raw, _ := json.Marshal(body)
req, err := http.NewRequest(http.MethodPost,
fmt.Sprintf("%s/v1/btc?mode=%s", BaseURL, mode),
bytes.NewReader(raw),
)
must(err)
req.Header.Set("Content-Type", "application/json")
if k := cfg.key(); k != "" {
req.Header.Set("X-SUBTC-KEY", k)
}
if idempotency != "" {
req.Header.Set("X-SUBTC-IDEMPOTENCY", idempotency)
}
resp, err := httpClient.Do(req)
must(err)
defer resp.Body.Close()
return decode(resp.Body)
}
func decode(r io.Reader) J {
raw, err := io.ReadAll(r)
must(err)
var result J
if err := json.Unmarshal(raw, &result); err != nil {
die("bad JSON from server: " + string(raw))
}
return result
}
// ─────────────────────────────────────────────────────────────────────────────
// Display helpers
// ─────────────────────────────────────────────────────────────────────────────
func header(title string) {
fmt.Printf("\n%s%s%s\n%s\n", cBold, title, cReset, strings.Repeat("─", 44))
}
func info(key, val string) {
fmt.Printf("%s%-20s%s %s\n", cCyan, key+":", cReset, val)
}
func ok(msg string) {
fmt.Printf("%s✓ %s%s\n", cGreen, msg, cReset)
}
func warn(msg string) {
fmt.Printf("%s! %s%s\n", cYellow, msg, cReset)
}
func die(msg string) {
fmt.Fprintf(os.Stderr, "%s✗ %s%s\n", cRed, msg, cReset)
os.Exit(1)
}
func must(err error) {
if err != nil {
die(err.Error())
}
}
func printJSON(v J) {
b, _ := json.MarshalIndent(v, "", " ")
fmt.Println(cGray + string(b) + cReset)
}
func fmtSat(sat float64) string {
return fmt.Sprintf("%.0f sat (%.8f BTC)", sat, sat/1e8)
}
func str(v J, key string) string {
if x, ok := v[key]; ok {
switch t := x.(type) {
case string:
return t
case float64:
return fmt.Sprintf("%.0f", t)
}
}
return ""
}
// nested digs into v["result"][key] — SUBTC wraps responses in {"ok":true,"result":{...}}
func nested(v J, key string) string {
if r, ok := v["result"].(J); ok {
return str(r, key)
}
return str(v, key) // fallback: flat response
}
// resultMap returns v["result"] as J, or v itself as fallback.
func resultMap(v J) J {
if r, ok := v["result"].(J); ok {
return r
}
return v
}
// ─────────────────────────────────────────────────────────────────────────────
// Commands
// ─────────────────────────────────────────────────────────────────────────────
func cmdHealth() {
result := apiGET("/health")
header("Health · " + Network + "net")
printJSON(result)
}
// ── Key ───────────────────────────────────────────────────────────────────────
func cmdKeyCreate(cfg *Config) {
result := apiPOST(*cfg, "key_create", nil, "")
header("Key Created")
key := nested(result, "key")
if key == "" {
printJSON(result)
return
}
info("key", key)
cfg.Key = key
if err := cfg.save(); err != nil {
warn("could not save key: " + err.Error())
} else {
ok("Key saved → ~/.subtc.json")
}
}
func cmdKeyStatus(cfg Config) {
cfg.requireKey()
result := apiPOST(cfg, "key_status", nil, "")
header("Key Status")
printJSON(result)
}
// ── Wallet ────────────────────────────────────────────────────────────────────
func cmdWalletCreate(cfg Config) {
cfg.requireKey()
result := apiPOST(cfg, "wallet_create", J{"net": Network}, "")
header("Wallet Created · " + Network + "net")
if wid := nested(result, "wallet_id"); wid != "" {
info("wallet_id", wid)
}
printJSON(result)
}
func cmdWalletList(cfg Config) {
cfg.requireKey()
result := apiPOST(cfg, "wallet_list", nil, "")
header("Wallets")
printJSON(result)
}
func cmdWalletReceive(cfg Config, walletID string) {
cfg.requireKey()
result := apiPOST(cfg, "wallet_receive", J{"wallet_id": walletID}, "")
header("Receive Address")
if addr := nested(result, "address"); addr != "" {
info("address", addr)
}
printJSON(result)
}
func cmdWalletBalance(cfg Config, walletID string) {
cfg.requireKey()
result := apiPOST(cfg, "wallet_balance", J{"wallet_id": walletID}, "")
header("Balance · " + walletID[:min(12, len(walletID))] + "…")
res := resultMap(result)
if s, ok := res["balance_sat"].(float64); ok {
info("balance", fmtSat(s))
}
if s, ok := res["unconfirmed_sat"].(float64); ok {
info("unconfirmed", fmtSat(s))
}
printJSON(result)
}
// ── Send ──────────────────────────────────────────────────────────────────────
func cmdSend(cfg Config, walletID, toAddr string, amountSat int64, idem string) {
cfg.requireKey()
if amountSat < MinSendSat {
die(fmt.Sprintf("minimum send is %d sat (got %d)", MinSendSat, amountSat))
}
if idem == "" {
idem = fmt.Sprintf("send-%d", time.Now().Unix())
}
header("Send BTC · testnet")
info("wallet_id", walletID)
info("to", toAddr)
info("amount", fmtSat(float64(amountSat)))
info("idempotency", idem)
fmt.Println()
result := apiPOST(cfg, "wallet_send", J{
"wallet_id": walletID,
"to": toAddr,
"amount_sat": amountSat,
}, idem)
if txid := nested(result, "txid"); txid != "" {
ok("Transaction broadcast")
info("txid", txid)
}
printJSON(result)
}
// ── Inbox ─────────────────────────────────────────────────────────────────────
func cmdInbox(cfg Config, walletID string, expectedSat int64) {
cfg.requireKey()
result := apiPOST(cfg, "inbox_create", J{
"wallet_id": walletID,
"expected_sat": expectedSat,
}, "")
header("Inbox Created")
if addr := nested(result, "address"); addr != "" {
ok("Inbox ready")
info("address", addr)
info("expected", fmtSat(float64(expectedSat)))
}
printJSON(result)
}
// ── Wait (long-poll or webhook) ───────────────────────────────────────────────
func cmdWait(cfg Config, walletID, address string, expectedSat int64, timeoutSec int, callbackURL string) {
cfg.requireKey()
body := J{
"wallet_id": walletID,
"address": address,
"expected_sat": expectedSat,
"timeout_sec": timeoutSec,
}
if callbackURL != "" {
body["callback_url"] = callbackURL
}
header("Waiting for Funds")
info("wallet_id", walletID)
info("address", address)
info("expected_sat", fmtSat(float64(expectedSat)))
info("timeout", fmt.Sprintf("%d sec", timeoutSec))
if callbackURL != "" {
info("callback_url", callbackURL)
info("mode", "webhook")
} else {
info("mode", "long-poll (blocking…)")
}
fmt.Println()
result := apiPOST(cfg, "wallet_wait_event", body, "")
res := resultMap(result)
if reached, _ := res["reached"].(bool); reached {
ok("Payment confirmed!")
}
if s, ok2 := res["received_sat"].(float64); ok2 {
info("received", fmtSat(s))
}
printJSON(result)
}
// ── Poll ──────────────────────────────────────────────────────────────────────
func cmdPoll(cfg Config, walletID, address string, expectedSat int64, loop bool, intervalSec int) {
cfg.requireKey()
header("Poll")
info("wallet_id", walletID)
info("address", address)
info("expected_sat", fmtSat(float64(expectedSat)))
if loop {
info("mode", fmt.Sprintf("loop every %d sec — Ctrl+C to stop", intervalSec))
}
fmt.Println()
poll := func() bool {
result := apiPOST(cfg, "wallet_poll", J{
"wallet_id": walletID,
"address": address,
"expected_sat": expectedSat,
}, "")
res := resultMap(result)
reached, _ := res["reached"].(bool)
recSat, _ := res["received_sat"].(float64)
icon := "⏳ "
if reached {
icon = "✅ "
}
fmt.Printf("%s received: %-26s reached: %v\n", icon, fmtSat(recSat), reached)
return reached
}
if !loop {
poll()
return
}
for {
if poll() {
ok("Payment confirmed — stopping loop")
break
}
time.Sleep(time.Duration(intervalSec) * time.Second)
}
}
// ── Config sub-commands ───────────────────────────────────────────────────────
func cmdConfigShow(cfg Config) {
header("Config")
keyDisplay := cfg.Key
if len(keyDisplay) > 8 {
keyDisplay = keyDisplay[:8] + "••••••••"
}
if keyDisplay == "" {
keyDisplay = "(none — run: subtc key create)"
}
info("key", keyDisplay)
info("network", Network+" (testnet fixed in V1)")
fmt.Println("\n file: ~/.subtc.json")
fmt.Println(" env-override: SUBTC_KEY")
}
// ─────────────────────────────────────────────────────────────────────────────
// Help
// ─────────────────────────────────────────────────────────────────────────────
func printHelp() {
fmt.Printf(`
%sSUBTC CLI V%s — Bitcoin Testnet%s
Gateway V4.2 · Stateless · Idempotent · curl-first
%sUsage:%s
subtc <command> [args]
%sKey:%s
subtc key create Create & save API key
subtc key status Verify current key
%sWallet:%s
subtc wallet create Create testnet wallet
subtc wallet list List all wallets
subtc wallet receive <wid> Generate receive address
subtc wallet balance <wid> Show balance (SAT + BTC)
%sSend:%s
subtc send <wid> <addr> <sat> [idem] Idempotent send (min 50,000 sat)
%sInbox:%s
subtc inbox <wid> <expected_sat> Pre-configured receive inbox
%sWait / Poll:%s
subtc wait <wid> <addr> <sat> [timeout_sec] [callback_url]
Long-poll or webhook on payment
subtc poll <wid> <addr> <sat> Single poll check
subtc poll <wid> <addr> <sat> --loop Loop poll until confirmed
%sConfig:%s
subtc config show Show stored config
subtc config set-key <key> Store API key
subtc health Node health check
%sExamples:%s
subtc key create
subtc wallet create
subtc wallet receive wlt_abc123
subtc wallet balance wlt_abc123
subtc send wlt_abc123 tb1qXXX 50000
subtc wait wlt_abc123 tb1qXXX 20000 300
subtc poll wlt_abc123 tb1qXXX 20000 --loop
%sUnits:%s 1 BTC = 100,000,000 SAT · Fee: 3%% · Min send: 50,000 SAT
%sDocs:%s https://subtc.net/api
`,
cBold, Version, cReset,
cBold, cReset,
cCyan, cReset,
cCyan, cReset,
cCyan, cReset,
cCyan, cReset,
cCyan, cReset,
cCyan, cReset,
cGreen, cReset,
cGray, cReset,
cGray, cReset,
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Argument helpers
// ─────────────────────────────────────────────────────────────────────────────
func argSat(s, name string) int64 {
v, err := strconv.ParseInt(s, 10, 64)
if err != nil || v <= 0 {
die("invalid " + name + ": " + s)
}
return v
}
func argInt(s string, def int) int {
v, err := strconv.Atoi(s)
if err != nil {
return def
}
return v
}
func hasFlag(args []string, flag string) bool {
for _, a := range args {
if a == flag {
return true
}
}
return false
}
func flagVal(args []string, prefix string, def string) string {
for _, a := range args {
if strings.HasPrefix(a, prefix) {
return a[len(prefix):]
}
}
return def
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// ─────────────────────────────────────────────────────────────────────────────
// Main
// ─────────────────────────────────────────────────────────────────────────────
func main() {
cfg := loadConfig()
if len(os.Args) < 2 {
printHelp()
return
}
args := os.Args[1:]
cmd := args[0]
rest := args[1:]
switch cmd {
// ── Health ────────────────────────────────────────────────────────────────
case "health":
cmdHealth()
// ── Key ───────────────────────────────────────────────────────────────────
case "key":
if len(rest) == 0 {
die("usage: subtc key <create|status>")
}
switch rest[0] {
case "create":
cmdKeyCreate(&cfg)
case "status":
cmdKeyStatus(cfg)
default:
die("unknown key sub-command: " + rest[0])
}
// ── Wallet ────────────────────────────────────────────────────────────────
case "wallet":
if len(rest) == 0 {
die("usage: subtc wallet <create|list|receive|balance>")
}
switch rest[0] {
case "create":
cmdWalletCreate(cfg)
case "list":
cmdWalletList(cfg)
case "receive":
if len(rest) < 2 {
die("usage: subtc wallet receive <wallet_id>")
}
cmdWalletReceive(cfg, rest[1])
case "balance":
if len(rest) < 2 {
die("usage: subtc wallet balance <wallet_id>")
}
cmdWalletBalance(cfg, rest[1])
default:
die("unknown wallet sub-command: " + rest[0])
}
// ── Send ──────────────────────────────────────────────────────────────────
case "send":
if len(rest) < 3 {
die("usage: subtc send <wallet_id> <to_addr> <amount_sat> [idempotency_key]")
}
idem := ""
if len(rest) >= 4 {
idem = rest[3]
}
cmdSend(cfg, rest[0], rest[1], argSat(rest[2], "amount_sat"), idem)
// ── Inbox ─────────────────────────────────────────────────────────────────
case "inbox":
if len(rest) < 2 {
die("usage: subtc inbox <wallet_id> <expected_sat>")
}
cmdInbox(cfg, rest[0], argSat(rest[1], "expected_sat"))
// ── Wait ──────────────────────────────────────────────────────────────────
case "wait":
if len(rest) < 3 {
die("usage: subtc wait <wallet_id> <address> <expected_sat> [timeout_sec] [callback_url]")
}
timeout := 300
callbackURL := ""
if len(rest) >= 4 {
timeout = argInt(rest[3], 300)
}
if len(rest) >= 5 {
callbackURL = rest[4]
}
cmdWait(cfg, rest[0], rest[1], argSat(rest[2], "expected_sat"), timeout, callbackURL)
// ── Poll ──────────────────────────────────────────────────────────────────
case "poll":
if len(rest) < 3 {
die("usage: subtc poll <wallet_id> <address> <expected_sat> [--loop] [--interval=N]")
}
loop := hasFlag(rest[3:], "--loop") || hasFlag(rest[3:], "-l")
interval := argInt(flagVal(rest[3:], "--interval=", "3"), 3)
cmdPoll(cfg, rest[0], rest[1], argSat(rest[2], "expected_sat"), loop, interval)
// ── Config ────────────────────────────────────────────────────────────────
case "config":
sub := ""
if len(rest) > 0 {
sub = rest[0]
}
switch sub {
case "", "show":
cmdConfigShow(cfg)
case "set-key":
if len(rest) < 2 {
die("usage: subtc config set-key <key>")
}
cfg.Key = rest[1]
must(cfg.save())
ok("API key saved → ~/.subtc.json")
default:
die("unknown config sub-command: " + sub)
}
case "help", "--help", "-h":
printHelp()
default:
warn("unknown command: " + cmd)
printHelp()
}
}
# SUBTC-CLI Test Run Examples
root@subtc:~/subtc# ./subtc key create
Key Created
────────────────────────────────────────────
key: SUBTC-KEY-3f470eb8d4d0499c12178d5c8c348a66c0c33d7ec941cdd7
✓ Key saved → ~/.subtc.json
root@subtc:~/subtc# ./subtc wallet create
Wallet Created · testnet
────────────────────────────────────────────
wallet_id: w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
{
"ok": true,
"request_id": "66d6705f7e7a1c82",
"result": {
"coin": "btc",
"net": "test",
"wallet_id": "w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758"
}
}
root@subtc:~/subtc# ./subtc wallet receive
✗ usage: subtc wallet receive <wallet_id>
root@subtc:~/subtc# ./subtc wallet receive w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
Receive Address
────────────────────────────────────────────
address: tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y
{
"ok": true,
"request_id": "98683c2957cadb69",
"result": {
"address": "tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y",
"addresses": [
"tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y"
],
"coin": "btc",
"net": "test",
"wallet_id": "w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758"
}
}
root@subtc:~/subtc# ./subtc wallet balance w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
Balance · w_ce9e16e59a…
────────────────────────────────────────────
balance: 48500 sat (0.00048500 BTC)
unconfirmed: 0 sat (0.00000000 BTC)
{
"ok": true,
"request_id": "3843d95815b06e0a",
"result": {
"addresses": [
"tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y"
],
"balance_sat": 48500,
"balance_source": "getbalances",
"coin": "btc",
"confirmed_sat": 48500,
"immature_sat": 0,
"net": "test",
"unconfirmed_sat": 0,
"wallet_id": "w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758"
}
}
root@subtc:~/subtc# ./subtc send w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758 tb1qjcr7lcucmwudwqscew4r078gxkw549ruq5r97w 30000
✗ minimum send is 50000 sat (got 30000)
root@subtc:~/subtc# ./subtc send w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758 tb1qjcr7lcucmwudwqscew4r078gxkw549ruq5r97w 50000
Send BTC · testnet
────────────────────────────────────────────
wallet_id: w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
to: tb1qjcr7lcucmwudwqscew4r078gxkw549ruq5r97w
amount: 50000 sat (0.00050000 BTC)
idempotency: send-1773768995
{
"detail": {
"hint": "rpc error"
},
"error": "SEND_FAILED",
"ok": false
}
root@subtc:~/subtc# ./subtc wallet balance w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
Balance · w_ce9e16e59a…
────────────────────────────────────────────
balance: 97000 sat (0.00097000 BTC)
unconfirmed: 0 sat (0.00000000 BTC)
{
"ok": true,
"request_id": "52180cb3b9d20395",
"result": {
"addresses": [
"tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y"
],
"balance_sat": 97000,
"balance_source": "getbalances",
"coin": "btc",
"confirmed_sat": 97000,
"immature_sat": 0,
"net": "test",
"unconfirmed_sat": 0,
"wallet_id": "w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758"
}
}
root@subtc:~/subtc# ./subtc send w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758 tb1qjcr7lcucmwudwqscew4r078gxkw549ruq5r97w 50000
Send BTC · testnet
────────────────────────────────────────────
wallet_id: w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
to: tb1qjcr7lcucmwudwqscew4r078gxkw549ruq5r97w
amount: 50000 sat (0.00050000 BTC)
idempotency: send-1773769023
✓ Transaction broadcast
txid: 3d72d72aa9f43be50e4dfb35971107252143fdf3da859d97d74427336b5ef8fd
{
"ok": true,
"request_id": "359a5d64c67a4ee3",
"result": {
"coin": "btc",
"fee_addr": "tb1q2dn34v7l3jmn30zuwgkzry23td949qtrre46cw",
"fee_bps": 300,
"net": "test",
"network_fee_by": "service_fee",
"requested_sat": 50000,
"send_method": "sendmany",
"sent_sat": 48500,
"service_fee_sat": 1500,
"txid": "3d72d72aa9f43be50e4dfb35971107252143fdf3da859d97d74427336b5ef8fd",
"wallet_id": "w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758"
}
}
root@subtc:~/subtc# ./subtc wait w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758 tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y 20000
Waiting for Funds
────────────────────────────────────────────
wallet_id: w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
address: tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y
expected_sat: 20000 sat (0.00020000 BTC)
timeout: 300 sec
mode: long-poll (blocking…)
received: 97000 sat (0.00097000 BTC)
{
"ok": true,
"request_id": "ae34e901e5ca030f",
"result": {
"address": "tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y",
"coin": "btc",
"expected_sat": 20000,
"net": "test",
"received_sat": 97000,
"status": "confirmed",
"wallet_id": "w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758"
}
}
root@subtc:~/subtc# ./subtc poll w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758 tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y 20000 --loop
Poll
────────────────────────────────────────────
wallet_id: w_ce9e16e59a72cd59840ce2256662a2400bc114ea3758
address: tb1qwphc20fty0l42cn3tnwv32uz85zm9jm2thdl4y
expected_sat: 20000 sat (0.00020000 BTC)
mode: loop every 3 sec — Ctrl+C to stop
✅ received: 97000 sat (0.00097000 BTC) reached: true## https://github.com/subtc/SUBTC-CLI-V1.0.0-Bitcoin-Testnet