bank: demo withdraw/credit helpers (container; no public landing yet)
This commit is contained in:
parent
cd3ffadcde
commit
96f0943319
4 changed files with 365 additions and 0 deletions
103
scripts/taler-bank/auto-confirm-withdrawals.sh
Executable file
103
scripts/taler-bank/auto-confirm-withdrawals.sh
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
#!/bin/bash
|
||||
# Auto-confirm bank withdrawals once the wallet has selected exchange+reserve.
|
||||
# Required for regional x-taler-bank: confirm wires GOA from user → exchange.
|
||||
#
|
||||
# Run once: auto-confirm-withdrawals.sh
|
||||
# Loop: auto-confirm-withdrawals.sh --loop [SECS]
|
||||
#
|
||||
# Env:
|
||||
# BANK_URL (default http://127.0.0.1:9012)
|
||||
# BANK_USER (default explorer) — account that owns the demo withdrawals
|
||||
# BANK_PASS or /root/bank-explorer-password.txt / /root/bank-${USER}-password.txt
|
||||
set -euo pipefail
|
||||
|
||||
BANK="${BANK_URL:-http://127.0.0.1:9012}"
|
||||
USER="${BANK_USER:-explorer}"
|
||||
LOOP=0
|
||||
SLEEP=5
|
||||
if [ "${1:-}" = "--loop" ]; then
|
||||
LOOP=1
|
||||
SLEEP="${2:-5}"
|
||||
fi
|
||||
|
||||
PASS="${BANK_PASS:-}"
|
||||
if [ -z "$PASS" ]; then
|
||||
for f in "/root/bank-${USER}-password.txt" /root/bank-explorer-password.txt; do
|
||||
if [ -f "$f" ]; then PASS=$(tr -d '\n' <"$f"); break; fi
|
||||
done
|
||||
fi
|
||||
[ -n "$PASS" ] || { echo "no password for $USER" >&2; exit 1; }
|
||||
|
||||
token() {
|
||||
echo '{"scope":"readwrite"}' > /tmp/acw-tok-req.json
|
||||
curl -sS -m 12 -u "${USER}:${PASS}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @/tmp/acw-tok-req.json \
|
||||
"${BANK}/accounts/${USER}/token"
|
||||
}
|
||||
|
||||
# Extract withdrawal IDs we care about:
|
||||
# 1) public landing withdraw.uri
|
||||
# 2) any ID passed as args after --loop secs
|
||||
known_ids() {
|
||||
if [ -f /var/www/bank-landing/withdraw.uri ]; then
|
||||
basename "$(tr -d '\n' </var/www/bank-landing/withdraw.uri)"
|
||||
fi
|
||||
# optional list file of UUIDs
|
||||
if [ -f /var/www/bank-landing/withdraw-watch.ids ]; then
|
||||
cat /var/www/bank-landing/withdraw-watch.ids
|
||||
fi
|
||||
}
|
||||
|
||||
confirm_one() {
|
||||
local wid="$1"
|
||||
local tok="$2"
|
||||
local info conf
|
||||
info=$(curl -sS -m 10 "${BANK}/withdrawals/${wid}" 2>/dev/null || true)
|
||||
[ -n "$info" ] || return 0
|
||||
python3 -c "import json,sys; d=json.loads(sys.argv[1]); sys.exit(0 if d.get('status')=='selected' else 1)" "$info" 2>/dev/null || {
|
||||
st=$(python3 -c "import json,sys; print(json.loads(sys.argv[1]).get('status','?'))" "$info" 2>/dev/null || echo '?')
|
||||
echo "skip $wid status=$st"
|
||||
return 0
|
||||
}
|
||||
echo "confirming $wid ..."
|
||||
conf=$(curl -sS -m 15 -o /tmp/acw-conf.out -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer ${tok}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{}' \
|
||||
"${BANK}/accounts/${USER}/withdrawals/${wid}/confirm")
|
||||
echo " HTTP $conf $(cat /tmp/acw-conf.out 2>/dev/null | head -c 200)"
|
||||
# re-check
|
||||
curl -sS -m 8 "${BANK}/withdrawals/${wid}" | python3 -c "import json,sys; d=json.load(sys.stdin); print(' now', d.get('status'), d.get('amount'))" 2>/dev/null || true
|
||||
}
|
||||
|
||||
once() {
|
||||
local tjson tok
|
||||
tjson=$(token)
|
||||
tok=$(python3 -c "import json,sys; print(json.loads(sys.argv[1]).get('access_token',''))" "$tjson")
|
||||
if [ -z "$tok" ]; then
|
||||
echo "token fail: $tjson" >&2
|
||||
return 1
|
||||
fi
|
||||
local ids
|
||||
ids=$(known_ids | sort -u)
|
||||
if [ -z "$ids" ]; then
|
||||
echo "no withdrawal ids to watch"
|
||||
return 0
|
||||
fi
|
||||
while read -r wid; do
|
||||
[ -n "$wid" ] || continue
|
||||
confirm_one "$wid" "$tok"
|
||||
done <<<"$ids"
|
||||
}
|
||||
|
||||
if [ "$LOOP" -eq 1 ]; then
|
||||
echo "auto-confirm loop every ${SLEEP}s for user=$USER"
|
||||
while true; do
|
||||
once || true
|
||||
sleep "$SLEEP"
|
||||
done
|
||||
else
|
||||
once
|
||||
fi
|
||||
96
scripts/taler-bank/credit-account.sh
Executable file
96
scripts/taler-bank/credit-account.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/bash
|
||||
# Credit a bank user by transferring from admin (creates regional GOA via admin debit).
|
||||
# Run as root on koopa host (bank on 127.0.0.1:9012).
|
||||
#
|
||||
# Usage:
|
||||
# credit-account.sh [USERNAME] [AMOUNT]
|
||||
# credit-account.sh explorer GOA:1000
|
||||
set -euo pipefail
|
||||
|
||||
BANK="${BANK_URL:-http://127.0.0.1:9012}"
|
||||
TO_USER="${1:-explorer}"
|
||||
AMOUNT="${2:-GOA:1000}"
|
||||
ADMIN_PASS="${BANK_ADMIN_PASS:-}"
|
||||
if [ -z "$ADMIN_PASS" ] && [ -f /root/bank-admin-password.txt ]; then
|
||||
ADMIN_PASS=$(tr -d '\n' </root/bank-admin-password.txt)
|
||||
fi
|
||||
[ -n "$ADMIN_PASS" ] || { echo "Need BANK_ADMIN_PASS or /root/bank-admin-password.txt" >&2; exit 1; }
|
||||
|
||||
W=$(mktemp -d)
|
||||
trap 'rm -rf "$W"' EXIT
|
||||
|
||||
echo '{"scope":"readwrite"}' >"$W/tok.json"
|
||||
curl -sS -m 15 -u "admin:${ADMIN_PASS}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @"$W/tok.json" \
|
||||
"${BANK}/accounts/admin/token" >"$W/tok.out"
|
||||
TOKEN=$(python3 -c "import json;print(json.load(open('$W/tok.out')).get('access_token',''))")
|
||||
[ -n "$TOKEN" ] || { echo "admin token failed:"; cat "$W/tok.out"; exit 1; }
|
||||
|
||||
# payto for x-taler-bank regional accounts
|
||||
PAYTO="payto://x-taler-bank/bank.hacktivism.ch/${TO_USER}?receiver-name=${TO_USER}&message=credit-from-admin"
|
||||
|
||||
python3 - "$AMOUNT" "$PAYTO" "$W/tx.json" <<'PY'
|
||||
import json, sys, os
|
||||
amount, payto, out = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
# ShortHashCode: Crockford base32 of 32 random bytes (52 chars)
|
||||
_ALPH = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
||||
def crockford32(data: bytes) -> str:
|
||||
n = int.from_bytes(data, "big")
|
||||
bits = len(data) * 8
|
||||
outc = []
|
||||
while bits > 0:
|
||||
bits -= 5
|
||||
outc.append(_ALPH[(n >> bits) & 31] if bits >= 0 else _ALPH[(n << (-bits)) & 31])
|
||||
if bits <= 0:
|
||||
break
|
||||
# pad to full groups
|
||||
s = "".join(outc)
|
||||
# simpler bit stream
|
||||
return None
|
||||
def crock32(b: bytes) -> str:
|
||||
bits = 0
|
||||
val = 0
|
||||
outc = []
|
||||
for byte in b:
|
||||
val = (val << 8) | byte
|
||||
bits += 8
|
||||
while bits >= 5:
|
||||
bits -= 5
|
||||
outc.append(_ALPH[(val >> bits) & 31])
|
||||
if bits:
|
||||
outc.append(_ALPH[(val << (5 - bits)) & 31])
|
||||
return "".join(outc)
|
||||
uid = crock32(os.urandom(32))
|
||||
json.dump({"payto_uri": payto, "amount": amount, "request_uid": uid}, open(out, "w"))
|
||||
print("request_uid", uid, "len", len(uid))
|
||||
PY
|
||||
|
||||
echo "POST admin -> $TO_USER amount=$AMOUNT"
|
||||
curl -sS -m 15 \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @"$W/tx.json" \
|
||||
"${BANK}/accounts/admin/transactions" | tee "$W/tx.out"
|
||||
echo
|
||||
|
||||
# show balances
|
||||
for u in admin "$TO_USER"; do
|
||||
curl -sS -m 10 -u "admin:${ADMIN_PASS}" \
|
||||
-H 'Content-Type: application/json' -d '{"scope":"readonly"}' \
|
||||
"${BANK}/accounts/admin/token" >"$W/rtok" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# re-token readonly and print target balance
|
||||
echo '{"scope":"readonly"}' >"$W/rtok.json"
|
||||
# admin can GET any account
|
||||
curl -sS -m 10 -H "Authorization: Bearer ${TOKEN}" \
|
||||
"${BANK}/accounts/${TO_USER}" | tee "$W/acc.out"
|
||||
echo
|
||||
python3 -c "
|
||||
import json
|
||||
d=json.load(open('$W/acc.out'))
|
||||
b=d.get('balance') or {}
|
||||
print('RESULT', '${TO_USER}', 'balance=', b.get('amount'), b.get('credit_debit_indicator'))
|
||||
print('debit_threshold=', d.get('debit_threshold'))
|
||||
"
|
||||
112
scripts/taler-bank/make-demo-withdraw-qr.sh
Executable file
112
scripts/taler-bank/make-demo-withdraw-qr.sh
Executable file
|
|
@ -0,0 +1,112 @@
|
|||
#!/bin/bash
|
||||
# Create a demo GOA withdrawal for user explorer and write QR assets under LANDING_DIR.
|
||||
# Run on koopa (host) with bank on 127.0.0.1:9012.
|
||||
set -euo pipefail
|
||||
|
||||
BANK="${BANK_URL:-http://127.0.0.1:9012}"
|
||||
USER="${BANK_USER:-explorer}"
|
||||
PASS="${BANK_PASS:-}"
|
||||
AMOUNT="${AMOUNT:-GOA:10}"
|
||||
LANDING_DIR="${LANDING_DIR:-/var/www/bank-landing}"
|
||||
|
||||
if [ -z "$PASS" ]; then
|
||||
if [ -f /root/bank-explorer-password.txt ]; then
|
||||
PASS=$(tr -d '\n' </root/bank-explorer-password.txt)
|
||||
elif [ -f /tmp/bank-caddy-wire.log ]; then
|
||||
PASS=$(grep -E '^explorer=' /tmp/bank-caddy-wire.log | tail -1 | cut -d= -f2-)
|
||||
fi
|
||||
fi
|
||||
if [ -z "$PASS" ]; then
|
||||
echo "Set BANK_PASS or put password in /root/bank-explorer-password.txt" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORKDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$WORKDIR"' EXIT
|
||||
|
||||
echo "{\"scope\":\"readwrite\",\"refreshable\":true}" >"$WORKDIR/tok.json"
|
||||
curl -sS -m 15 -u "${USER}:${PASS}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$WORKDIR/tok.json" \
|
||||
"${BANK}/accounts/${USER}/token" >"$WORKDIR/tok.out"
|
||||
|
||||
TOKEN=$(python3 - "$WORKDIR/tok.out" <<'PY'
|
||||
import json,sys
|
||||
d=json.load(open(sys.argv[1]))
|
||||
print(d.get("access_token",""))
|
||||
PY
|
||||
)
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "token failed:" >&2
|
||||
cat "$WORKDIR/tok.out" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "{\"suggested_amount\":\"${AMOUNT}\"}" >"$WORKDIR/wd.json"
|
||||
curl -sS -m 15 \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$WORKDIR/wd.json" \
|
||||
"${BANK}/accounts/${USER}/withdrawals" >"$WORKDIR/wd.out"
|
||||
|
||||
URI=$(python3 - "$WORKDIR/wd.out" <<'PY'
|
||||
import json,sys
|
||||
d=json.load(open(sys.argv[1]))
|
||||
print(d.get("taler_withdraw_uri") or "")
|
||||
if not d.get("taler_withdraw_uri"):
|
||||
sys.stderr.write(open(sys.argv[1]).read()+"\n")
|
||||
sys.exit(1)
|
||||
print(d.get("withdrawal_id",""), file=sys.stderr)
|
||||
PY
|
||||
)
|
||||
|
||||
mkdir -p "$LANDING_DIR"
|
||||
printf '%s\n' "$URI" >"$LANDING_DIR/withdraw.uri"
|
||||
printf '%s\n' "$AMOUNT" >"$LANDING_DIR/withdraw.amount"
|
||||
date -u +%Y-%m-%dT%H:%MZ >"$LANDING_DIR/withdraw.created"
|
||||
# track id for auto-confirm-withdrawals.sh
|
||||
WID=$(basename "$URI")
|
||||
echo "$WID" >>"$LANDING_DIR/withdraw-watch.ids"
|
||||
sort -u "$LANDING_DIR/withdraw-watch.ids" -o "$LANDING_DIR/withdraw-watch.ids"
|
||||
echo "watch_id=$WID (auto-confirm when wallet selects)"
|
||||
|
||||
# QR as SVG (no external deps) via python qrcode if present, else pure matrix via segno/qrcode
|
||||
python3 - "$URI" "$LANDING_DIR/withdraw-qr.svg" <<'PY'
|
||||
import sys
|
||||
uri, out = sys.argv[1], sys.argv[2]
|
||||
try:
|
||||
import qrcode
|
||||
import qrcode.image.svg
|
||||
img = qrcode.make(uri, image_factory=qrcode.image.svg.SvgPathImage)
|
||||
img.save(out)
|
||||
print("qrcode lib ok")
|
||||
except Exception as e:
|
||||
# minimal fallback: write HTML with data attribute for client-side QR
|
||||
open(out, "w").write(
|
||||
f'<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" width="8" height="8">'
|
||||
f'<!-- QR_FALLBACK uri={uri} --></svg>\n'
|
||||
)
|
||||
open(out + ".uri", "w").write(uri)
|
||||
print("fallback:", e)
|
||||
PY
|
||||
|
||||
# Also PNG if pillow/qrcode available
|
||||
python3 - "$URI" "$LANDING_DIR/withdraw-qr.png" <<'PY' || true
|
||||
import sys
|
||||
uri, out = sys.argv[1], sys.argv[2]
|
||||
import qrcode
|
||||
img = qrcode.make(uri, box_size=8, border=2)
|
||||
img.save(out)
|
||||
print("png ok", out)
|
||||
PY
|
||||
|
||||
echo "URI=$URI"
|
||||
echo "wrote under $LANDING_DIR"
|
||||
ls -la "$LANDING_DIR"/withdraw* 2>/dev/null || true
|
||||
|
||||
# Refresh public stats.json when landing-stats is available (in-container path)
|
||||
if [ -x /usr/local/bin/landing-stats.sh ]; then
|
||||
LANDING_DIR="$LANDING_DIR" /usr/local/bin/landing-stats.sh || true
|
||||
elif [ -x "$(dirname "$0")/landing-stats.sh" ]; then
|
||||
LANDING_DIR="$LANDING_DIR" "$(dirname "$0")/landing-stats.sh" || true
|
||||
fi
|
||||
54
scripts/taler-bank/refresh-demo-withdraw.sh
Executable file
54
scripts/taler-bank/refresh-demo-withdraw.sh
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/bin/bash
|
||||
# Create a fresh demo GOA withdraw for landing QR (no python — runs in bank container).
|
||||
# Writes under LANDING_DIR (default /var/www/bank-landing).
|
||||
#
|
||||
# Inside container:
|
||||
# /usr/local/bin/refresh-demo-withdraw.sh
|
||||
# Host:
|
||||
# podman exec taler-hacktivism-bank /usr/local/bin/refresh-demo-withdraw.sh
|
||||
set -euo pipefail
|
||||
|
||||
BANK="${BANK_URL:-http://127.0.0.1:9012}"
|
||||
USER="${BANK_USER:-explorer}"
|
||||
AMOUNT="${AMOUNT:-GOA:10}"
|
||||
LANDING_DIR="${LANDING_DIR:-/var/www/bank-landing}"
|
||||
PASS="${BANK_PASS:-}"
|
||||
|
||||
if [ -z "$PASS" ]; then
|
||||
for f in "/root/bank-${USER}-password.txt" /root/bank-explorer-password.txt; do
|
||||
if [ -f "$f" ]; then PASS=$(tr -d '\n' <"$f"); break; fi
|
||||
done
|
||||
fi
|
||||
[ -n "$PASS" ] || { echo "Set BANK_PASS or /root/bank-explorer-password.txt" >&2; exit 1; }
|
||||
|
||||
BANK="${BANK%/}"
|
||||
TOK=$(curl -sS -m 12 -u "${USER}:${PASS}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"scope":"readwrite","refreshable":true}' \
|
||||
"${BANK}/accounts/${USER}/token")
|
||||
TOKEN=$(printf '%s' "$TOK" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
||||
[ -n "$TOKEN" ] || { echo "token fail: $TOK" >&2; exit 1; }
|
||||
|
||||
WD=$(curl -sS -m 15 \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"suggested_amount\":\"${AMOUNT}\"}" \
|
||||
"${BANK}/accounts/${USER}/withdrawals")
|
||||
URI=$(printf '%s' "$WD" | sed -n 's/.*"taler_withdraw_uri"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
||||
WID=$(printf '%s' "$WD" | sed -n 's/.*"withdrawal_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
||||
[ -n "$URI" ] || { echo "no URI from: $WD" >&2; exit 1; }
|
||||
[ -n "$WID" ] || WID=$(basename "$URI")
|
||||
|
||||
mkdir -p "$LANDING_DIR"
|
||||
printf '%s\n' "$URI" >"$LANDING_DIR/withdraw.uri"
|
||||
printf '%s\n' "$AMOUNT" >"$LANDING_DIR/withdraw.amount"
|
||||
date -u +%Y-%m-%dT%H:%MZ >"$LANDING_DIR/withdraw.created"
|
||||
echo "$WID" >>"$LANDING_DIR/withdraw-watch.ids"
|
||||
sort -u "$LANDING_DIR/withdraw-watch.ids" -o "$LANDING_DIR/withdraw-watch.ids" 2>/dev/null || true
|
||||
|
||||
echo "URI=$URI"
|
||||
echo "WID=$WID"
|
||||
|
||||
if [ -x /usr/local/bin/landing-stats.sh ]; then
|
||||
LANDING_DIR="$LANDING_DIR" /usr/local/bin/landing-stats.sh || true
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue