scripts: sanity/dns/wallet-cli helpers; ops cleanup

This commit is contained in:
Hernâni Marques 2026-07-09 19:57:14 +02:00
parent 1181680a4b
commit be05666737
No known key found for this signature in database
18 changed files with 1440 additions and 0 deletions

View file

@ -0,0 +1,3 @@
#!/bin/bash
# Compatibility wrapper — canonical benchmark lives under benchmarks/amount-ladder/
exec "$(cd "$(dirname "$0")/../.." && pwd)/benchmarks/amount-ladder/run.sh" "$@"

View file

@ -0,0 +1,350 @@
#!/bin/bash
# Extreme amount tests for GOA: largest practical price vs atomic/tiny coin.
#
# Live denoms (exchange.hacktivism.ch): GOA:0.000001 … GOA:10
# Currency TINY / round unit: GOA:0.00000001 (Atomic-GOA)
#
# Run on the local machine (wallet) with ssh BatchMode to koopa for bank/merchant ops.
# Requires: monorepo taler-wallet-cli, passwords on koopa /root/*-password.txt
#
# Usage:
# ./run-extreme-amount-tests.sh
# HIGH_AMOUNT=GOA:500 TINY_AMOUNT=GOA:0.000001 ./run-extreme-amount-tests.sh
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
SCRATCH="${ROOT}/.tmp/extreme-$(date -u +%Y%m%d-%H%M%S)"
mkdir -p "$SCRATCH"
chmod 700 "$SCRATCH"
MONO="${MONO:-/Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}"
WDB="${WDB:-$SCRATCH/wallet.sqlite3}"
EX="${EXCHANGE_URL:-https://exchange.hacktivism.ch/}"
KOOPA="${KOOPA_HOST:-koopa}"
INST="${MERCHANT_INSTANCE:-goa-demo-cp4zqk}"
BANK_PUBLIC="${BANK_PUBLIC:-https://bank.hacktivism.ch}"
MER_PUBLIC="${MER_PUBLIC:-https://taler.hacktivism.ch}"
# High price: many max-denom (GOA:10) coins. Tiny: smallest issued coin.
HIGH_AMOUNT="${HIGH_AMOUNT:-GOA:1000}"
TINY_AMOUNT="${TINY_AMOUNT:-GOA:0.000001}"
# True atomic (round unit) — may fail if no coin below 1e-6
ATOMIC_AMOUNT="${ATOMIC_AMOUNT:-GOA:0.00000001}"
# Fat bank balance for withdraw buffer
FAT_CREDIT="${FAT_CREDIT:-GOA:50000}"
FAT_USER="${FAT_USER:-extreme-fat}"
# withdraw a bit more than HIGH
WITHDRAW_AMOUNT="${WITHDRAW_AMOUNT:-GOA:1500}"
wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; }
log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
die() { echo "FAIL: $*" >&2; exit 1; }
command -v node >/dev/null || die "node missing"
[ -f "$MONO" ] || die "wallet missing: $MONO"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$KOOPA" 'echo ok' >/dev/null || die "ssh $KOOPA failed"
log "scratch=$SCRATCH"
log "HIGH=$HIGH_AMOUNT TINY=$TINY_AMOUNT ATOMIC=$ATOMIC_AMOUNT WITHDRAW=$WITHDRAW_AMOUNT FAT=$FAT_USER+$FAT_CREDIT"
# --- 1) koopa: bank account + credit + templates + orders ---
log "=== koopa setup (admin credit, templates, orders) ==="
# Generate bank password locally (not committed)
FAT_PW=$(python3 -c 'import secrets; print(secrets.token_urlsafe(12))')
echo "$FAT_PW" >"$SCRATCH/fat-bank.password"
chmod 600 "$SCRATCH/fat-bank.password"
cat >"$SCRATCH/koopa-setup.sh" <<EOS
#!/bin/bash
set +e
exec > /tmp/extreme-setup.out 2>&1
set -x
INST=$INST
FAT_USER=$FAT_USER
FAT_PW='$(printf %s "$FAT_PW" | sed "s/'/'\\\\''/g")'
FAT_CREDIT=$FAT_CREDIT
HIGH_AMOUNT=$HIGH_AMOUNT
TINY_AMOUNT=$TINY_AMOUNT
ATOMIC_AMOUNT=$ATOMIC_AMOUNT
BANK=http://127.0.0.1:9012
MER=https://127.0.0.1:9010
ADMIN_PASS=\$(tr -d '\\n' </root/bank-admin-password.txt)
MPW=\$(tr -d '\\n' </root/merchant-\${INST}-password.txt)
AUTH="Authorization: Bearer secret-token:\${MPW}"
# admin token
AT=\$(curl -sS -u "admin:\${ADMIN_PASS}" -H 'Content-Type: application/json' \\
-d '{"scope":"readwrite"}' "\${BANK}/accounts/admin/token" \\
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))')
[ -n "\$AT" ] || { echo FAIL_admin_token; exit 1; }
# create or reuse bank account
code=\$(curl -sS -o /tmp/acc-create.json -w '%{http_code}' -X POST \\
-H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\
-d "{\\"username\\":\\"\${FAT_USER}\\",\\"password\\":\\"\${FAT_PW}\\",\\"name\\":\\"Extreme Fat Wallet\\",\\"is_public\\":false,\\"is_taler_exchange\\":false,\\"debit_threshold\\":\\"GOA:1000000\\"}" \\
"\${BANK}/accounts")
echo create_account_http=\$code
cat /tmp/acc-create.json; echo
# if exists (409), reset password via admin if possible
if [ "\$code" != "200" ] && [ "\$code" != "201" ] && [ "\$code" != "204" ]; then
echo "account may exist; try credit anyway"
fi
# credit from admin (idempotent enough — we just need lots of GOA)
python3 - <<'PY'
import json, os, urllib.request, base64
bank=os.environ.get("BANK","http://127.0.0.1:9012")
PY
# use host credit-account if present
if [ -x /root/koopa-admin-log/scripts/taler-bank/credit-account.sh ]; then
/root/koopa-admin-log/scripts/taler-bank/credit-account.sh "\$FAT_USER" "\$FAT_CREDIT" || true
elif [ -x /Users/newkamek/src/koopa/koopa-admin-log/scripts/taler-bank/credit-account.sh ]; then
true
else
# inline credit
PAYTO="payto://x-taler-bank/bank.hacktivism.ch/\${FAT_USER}?receiver-name=\${FAT_USER}&message=extreme-credit"
UID=\$(python3 -c 'import os; a="0123456789ABCDEFGHJKMNPQRSTVWXYZ"; b=os.urandom(32); bits=0;v=0;o=[]
for byte in b:
v=(v<<8)|byte; bits+=8
while bits>=5:
bits-=5; o.append(a[(v>>bits)&31])
if bits: o.append(a[(v<<(5-bits))&31])
print("".join(o))')
curl -sS -H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\
-d "{\\"payto_uri\\":\\"\${PAYTO}\\",\\"amount\\":\\"\${FAT_CREDIT}\\",\\"request_uid\\":\\"\${UID}\\"}" \\
"\${BANK}/accounts/admin/transactions" | tee /tmp/credit.out
echo
fi
# balance
curl -sS -H "Authorization: Bearer \${AT}" "\${BANK}/accounts/\${FAT_USER}" | tee /tmp/fat-bal.json
echo
python3 -c 'import json;d=json.load(open("/tmp/fat-bal.json"));print("FAT_BALANCE", (d.get("balance") or {}).get("amount"), (d.get("balance") or {}).get("credit_debit_indicator"))'
# store bank pw for host-side withdraw
printf '%s' "\$FAT_PW" > /root/bank-\${FAT_USER}-password.txt
chmod 600 /root/bank-\${FAT_USER}-password.txt
# templates
for id_summary_amount in \\
"extreme-high|extreme high price|\${HIGH_AMOUNT}" \\
"extreme-tiny|extreme tiny coin|\${TINY_AMOUNT}" \\
"extreme-atomic|true atomic round unit|\${ATOMIC_AMOUNT}"
do
IFS='|' read -r TID SUM AMT <<<"\$id_summary_amount"
body=\$(python3 -c "import json; print(json.dumps({
'template_id': '\$TID',
'template_description': '\$SUM',
'editable_defaults': False,
'template_contract': {
'summary': '\$SUM',
'amount': '\$AMT',
'minimum_age': 0,
},
}))")
# try POST then PATCH
code=\$(curl -sk -o /tmp/tpl.json -w '%{http_code}' -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\
-d "\$body" "\${MER}/instances/\${INST}/private/templates")
echo "template_\$TID post=\$code \$(head -c 120 /tmp/tpl.json)"
if [ "\$code" = "409" ] || [ "\$code" = "400" ]; then
curl -sk -X PATCH -H "\$AUTH" -H 'Content-Type: application/json' \\
-d "\$body" "\${MER}/instances/\${INST}/private/templates/\${TID}" -w " patch=%{http_code}\\n"
fi
done
# also list templates
curl -sk -H "\$AUTH" "\${MER}/instances/\${INST}/private/templates" | python3 -m json.tool | head -80
# create two firm orders (high + tiny) for deterministic pay URIs
create_order() {
local sum="\$1" amt="\$2" tag="\$3"
curl -sk -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\
-d "{\\"order\\":{\\"summary\\":\\"\${sum}\\",\\"amount\\":\\"\${amt}\\",\\"fulfillment_message\\":\\"extreme-ok\\"},\\"create_token\\":true}" \\
"\${MER}/instances/\${INST}/private/orders" > "/tmp/ord-\${tag}.json"
python3 - "\$tag" <<'PY'
import json,sys
tag=sys.argv[1]
d=json.load(open(f"/tmp/ord-{tag}.json"))
print(f"ORDER_{tag.upper()}", d.get("order_id"), "token", (d.get("token") or "")[:12])
open(f"/tmp/oid-{tag}.txt","w").write(d.get("order_id") or "")
open(f"/tmp/otok-{tag}.txt","w").write(d.get("token") or "")
PY
}
create_order "extreme-high-order" "\$HIGH_AMOUNT" high
create_order "extreme-tiny-order" "\$TINY_AMOUNT" tiny
create_order "extreme-atomic-order" "\$ATOMIC_AMOUNT" atomic
# create bank integrated withdrawal for fat user
BT=\$(curl -sS -u "\${FAT_USER}:\${FAT_PW}" -H 'Content-Type: application/json' \\
-d '{"scope":"readwrite"}' "\${BANK}/accounts/\${FAT_USER}/token" \\
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))')
curl -sS -H "Authorization: Bearer \${BT}" -H 'Content-Type: application/json' \\
-d "{\\"amount\\":\\"\$WITHDRAW_AMOUNT\\",\\"exchange_url\\":\\"https://exchange.hacktivism.ch/\\"}" \\
"\${BANK}/accounts/\${FAT_USER}/withdrawals" | tee /tmp/wd-create.json
echo
python3 - <<'PY'
import json
d=json.load(open("/tmp/wd-create.json"))
wid=d.get("withdrawal_id") or d.get("id")
uri=d.get("taler_withdraw_uri")
print("WID", wid)
print("WURI", uri)
open("/tmp/wid.txt","w").write(wid or "")
open("/tmp/wuri.txt","w").write(uri or "")
PY
# publish artifacts for hernani-readable pull
cp /tmp/oid-*.txt /tmp/otok-*.txt /tmp/wuri.txt /tmp/wid.txt /tmp/fat-bal.json /tmp/extreme-setup.out /home/hernani/ 2>/dev/null || true
chmod 644 /home/hernani/oid-*.txt /home/hernani/otok-*.txt /home/hernani/wuri.txt /home/hernani/wid.txt 2>/dev/null || true
echo SETUP_EXTREME_OK
EOS
# inject WITHDRAW_AMOUNT into script (heredoc already expanded partially - fix)
# The setup script uses $WITHDRAW_AMOUNT as shell var on koopa - need to export into script
sed -i.bak "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh" 2>/dev/null || \
sed -i '' "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh"
scp -o BatchMode=yes "$SCRATCH/koopa-setup.sh" "${KOOPA}:/tmp/extreme-setup.sh"
# also scp credit-account for reliable credit
scp -o BatchMode=yes "$ROOT/scripts/taler-bank/credit-account.sh" "${KOOPA}:/tmp/credit-account.sh" || true
# run via screen window 0 (root@koopa) — screen-run cannot source local-machine paths
SESSION=$(screen -ls 2>/dev/null | awk 'match($0, /[0-9]+\.[A-Za-z0-9._-]+/) { print substr($0, RSTART, RLENGTH); exit }')
[ -n "$SESSION" ] || die "no screen session"
screen -S "$SESSION" -p 0 -X stuff $'bash /tmp/extreme-setup.sh; chmod 644 /tmp/extreme-setup.out /home/hernani/*.txt 2>/dev/null\n'
for i in $(seq 1 90); do
if ssh -o BatchMode=yes "$KOOPA" 'grep -q SETUP_EXTREME_OK /tmp/extreme-setup.out 2>/dev/null'; then
log "setup ready @${i}s"
break
fi
sleep 1
[ "$i" -eq 90 ] && { ssh -o BatchMode=yes "$KOOPA" 'tail -80 /tmp/extreme-setup.out' || true; die "setup timeout"; }
done
ssh -o BatchMode=yes "$KOOPA" 'cat /tmp/extreme-setup.out' >"$SCRATCH/koopa-setup.out" || true
# pull artifacts (hernani-readable)
scp -o BatchMode=yes "${KOOPA}:/home/hernani/wuri.txt" "${KOOPA}:/home/hernani/wid.txt" \
"${KOOPA}:/home/hernani/oid-high.txt" "${KOOPA}:/home/hernani/oid-tiny.txt" \
"${KOOPA}:/home/hernani/oid-atomic.txt" \
"${KOOPA}:/home/hernani/otok-high.txt" "${KOOPA}:/home/hernani/otok-tiny.txt" \
"${KOOPA}:/home/hernani/otok-atomic.txt" \
"$SCRATCH/" 2>/dev/null || true
WURI=$(tr -d '\n' <"$SCRATCH/wuri.txt" 2>/dev/null || true)
WID=$(tr -d '\n' <"$SCRATCH/wid.txt" 2>/dev/null || true)
OID_HIGH=$(tr -d '\n' <"$SCRATCH/oid-high.txt" 2>/dev/null || true)
OID_TINY=$(tr -d '\n' <"$SCRATCH/oid-tiny.txt" 2>/dev/null || true)
OID_ATOMIC=$(tr -d '\n' <"$SCRATCH/oid-atomic.txt" 2>/dev/null || true)
TOK_HIGH=$(tr -d '\n' <"$SCRATCH/otok-high.txt" 2>/dev/null || true)
TOK_TINY=$(tr -d '\n' <"$SCRATCH/otok-tiny.txt" 2>/dev/null || true)
TOK_ATOMIC=$(tr -d '\n' <"$SCRATCH/otok-atomic.txt" 2>/dev/null || true)
log "WURI=$WURI"
log "orders high=$OID_HIGH tiny=$OID_TINY atomic=$OID_ATOMIC"
[ -n "$WURI" ] || die "no withdraw URI"
[ -n "$OID_HIGH" ] && [ -n "$OID_TINY" ] || die "orders missing"
# ensure auto-confirm watches this withdrawal
ssh -o BatchMode=yes "$KOOPA" "echo $WID >> /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; sort -u /var/www/bank-landing/withdraw-watch.ids -o /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; true"
# root confirm via screen
screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" </root/bank-'"$FAT_USER"'-password.txt); BT=$(curl -sS -u "'"$FAT_USER"':${BPW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/token | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))"); curl -sS -X POST -H "Authorization: Bearer ${BT}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/withdrawals/'"$WID"'/confirm; echo CONF_DONE\n'
# --- 2) wallet withdraw ---
log "=== wallet: add exchange + ToS ==="
wcli exchanges add "$EX" 2>&1 | tee "$SCRATCH/ex-add.out" || true
wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true
wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true
wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept.out" || true
log "=== withdraw accept-uri ==="
wcli withdraw accept-uri --exchange "$EX" "$WURI" 2>&1 | tee "$SCRATCH/accept-uri.out" || true
for round in $(seq 1 20); do
log "run-until-done round $round"
wcli run-until-done 2>&1 | tee -a "$SCRATCH/rud.out" || true
wcli balance 2>&1 | tee "$SCRATCH/bal-$round.out"
if grep -qE "GOA:" "$SCRATCH/bal-$round.out"; then
# check non-zero available
if python3 -c 'import re,sys; t=open(sys.argv[1]).read();
m=re.search(r"available[^0-9A-Z]*GOA:([0-9.]+)", t)
print(m.group(1) if m else ""); sys.exit(0 if m and float(m.group(1))>0 else 1)' "$SCRATCH/bal-$round.out" 2>/dev/null; then
log "coins available"
break
fi
fi
# re-confirm withdraw on bank
screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" </root/bank-'"$FAT_USER"'-password.txt); BT=$(curl -sS -u "'"$FAT_USER"':${BPW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/token | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))"); curl -sS -X POST -H "Authorization: Bearer ${BT}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/withdrawals/'"$WID"'/confirm 2>/dev/null; echo\n'
sleep 3
done
wcli balance 2>&1 | tee "$SCRATCH/balance-after-withdraw.out"
wcli transactions 2>&1 | tee "$SCRATCH/tx-after-withdraw.out" | head -100
# --- 3) pay high + tiny (+ try atomic) ---
pay_order() {
local tag="$1" oid="$2" tok="$3"
local uri="taler://pay/taler.hacktivism.ch/instances/${INST}/${oid}/?c=${tok}"
echo "$uri" >"$SCRATCH/pay-${tag}.uri"
log "=== pay $tag order=$oid ==="
log "uri=$uri"
wcli handle-uri "$uri" 2>&1 | tee "$SCRATCH/pay-${tag}-handle.out" || true
# some wallets use payments prepare/confirm
wcli run-until-done 2>&1 | tee -a "$SCRATCH/pay-${tag}-rud.out" || true
# check merchant
ssh -o BatchMode=yes "$KOOPA" "MPW=\$(tr -d '\\n' </root/merchant-${INST}-password.txt); curl -sk -H \"Authorization: Bearer secret-token:\${MPW}\" https://127.0.0.1:9010/instances/${INST}/private/orders/${oid}" \
>"$SCRATCH/order-${tag}-status.json" 2>/dev/null || true
python3 - "$SCRATCH/order-${tag}-status.json" "$tag" <<'PY' || true
import json,sys
p,tag=sys.argv[1],sys.argv[2]
try:
d=json.load(open(p))
except Exception as e:
print(f"STATUS_{tag} unreadable", e); raise SystemExit
print(f"STATUS_{tag}", "order_status=", d.get("order_status"), "paid=", d.get("paid"),
"wired=", d.get("wired"), "deposit_total=", d.get("deposit_total"),
"amount=", (d.get("contract_terms") or {}).get("amount"))
PY
}
pay_order high "$OID_HIGH" "$TOK_HIGH"
wcli balance 2>&1 | tee "$SCRATCH/balance-after-high.out"
pay_order tiny "$OID_TINY" "$TOK_TINY"
wcli balance 2>&1 | tee "$SCRATCH/balance-after-tiny.out"
if [ -n "$OID_ATOMIC" ] && [ -n "$TOK_ATOMIC" ]; then
pay_order atomic "$OID_ATOMIC" "$TOK_ATOMIC" || true
wcli balance 2>&1 | tee "$SCRATCH/balance-after-atomic.out" || true
fi
# template URIs for docs
{
echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-high/"
echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-tiny/"
echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-atomic/"
} | tee "$SCRATCH/template-uris.txt"
log "=== summary ==="
python3 - <<PY
import json,glob,os
scratch="$SCRATCH"
print("scratch", scratch)
for tag in ("high","tiny","atomic"):
p=f"{scratch}/order-{tag}-status.json"
if not os.path.isfile(p):
print(tag, "no status file"); continue
try:
d=json.load(open(p))
print(tag, "paid=", d.get("paid"), "status=", d.get("order_status"),
"amount=", (d.get("contract_terms") or {}).get("amount"),
"deposit_total=", d.get("deposit_total"))
except Exception as e:
print(tag, "err", e)
print("template_uris:")
print(open(f"{scratch}/template-uris.txt").read())
PY
log "done — details in $SCRATCH"

View file

@ -0,0 +1,54 @@
#!/bin/bash
# Local machine: withdraw GOA via bank-integrated QR1 URI (explorer demo account).
# Requires: monorepo wallet, ssh koopa, auto-confirm on host.
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
SCRATCH="${ROOT}/.tmp/session-withdraw-$(date -u +%Y%m%d-%H%M%S)"
mkdir -p "$SCRATCH"
MONO="${MONO:-/Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}"
WDB="${WDB:-$SCRATCH/wallet.sqlite3}"
EX="${EXCHANGE_URL:-https://exchange.hacktivism.ch/}"
URI="${1:-}"
wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; }
echo "scratch=$SCRATCH"
echo "=== add + accept ToS for $EX ==="
wcli exchanges add "$EX" 2>&1 | tee "$SCRATCH/ex-add.out" || true
wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true
wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true
wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept-tos.out"
if [ -z "$URI" ]; then
URI=$(ssh -o BatchMode=yes koopa 'tr -d "\n" </var/www/bank-landing/withdraw.uri')
fi
echo "URI=$URI"
echo "$URI" >"$SCRATCH/withdraw.uri"
echo "=== withdraw accept-uri ==="
wcli withdraw accept-uri --exchange "$EX" "$URI" 2>&1 | tee "$SCRATCH/accept-uri.out"
WID=$(basename "$URI")
echo "=== ensure watch + auto-confirm on koopa ==="
ssh -o BatchMode=yes koopa "echo $WID >> /var/www/bank-landing/withdraw-watch.ids; sort -u /var/www/bank-landing/withdraw-watch.ids -o /var/www/bank-landing/withdraw-watch.ids"
# one-shot confirm as root via ssh if hernani can sudo? usually not — use screen later
# public status
curl -sS "https://bank.hacktivism.ch/taler-integration/withdrawals/${WID}" 2>/dev/null | head -c 500 || true
echo
for round in 1 2 3 4 5 6 7 8; do
echo "=== run-until-done round $round ==="
wcli run-until-done &
PID=$!
( sleep 20; kill $PID 2>/dev/null ) &
wait $PID 2>/dev/null || true
wcli balance 2>&1 | tee "$SCRATCH/bal-$round.out"
wcli transactions 2>&1 | tee "$SCRATCH/tx-$round.out" | head -80
if grep -qE '"available": "GOA:[1-9]' "$SCRATCH/bal-$round.out"; then
echo "OK coins received"
exit 0
fi
sleep 3
done
echo "FAIL no balance" >&2
exit 1