fix 1.22.1: pay-wait withdraw board (start order + pendingIncoming)
UX fix: after bank OK_BANK, available is often still 0 while pendingIncoming ticks up. The silent wait looked hung. Show OK/OK_BANK withdraws in the order they started, match wallet txs, and update pendingIncoming→done with a timer on the same wallet DB. Short run-pending only; default wait 90s.
This commit is contained in:
parent
35cd86bd43
commit
fd5ac80566
6 changed files with 232 additions and 6 deletions
|
|
@ -9,7 +9,7 @@
|
|||
: "${LADDER_FREE_TEMPLATE:=goa-free}"
|
||||
: "${LADDER_FREE_TEMPLATE_INSTANCE:=}"
|
||||
: "${LADDER_USE_FREE_TEMPLATE:=1}"
|
||||
: "${LADDER_PAY_WAIT_AVAILABLE_S:=60}"
|
||||
: "${LADDER_PAY_WAIT_AVAILABLE_S:=90}"
|
||||
: "${LADDER_PAY_SETTLE_ROUNDS:=6}"
|
||||
|
||||
MAX_PAY_BEST_AMT="${MAX_PAY_BEST_AMT:-}"
|
||||
|
|
@ -188,17 +188,213 @@ except Exception:
|
|||
return 0
|
||||
}
|
||||
|
||||
# Print withdraw board: OK/OK_BANK rungs in *start order* + wallet tx state.
|
||||
# Uses TSV (ladder order) + transactions dump. Call after refreshing tx dump.
|
||||
# Args: left_secs available_num pendingIncoming_num
|
||||
ladder_print_wd_board() {
|
||||
local left="${1:-?}" bal="${2:-0}" pend="${3:-0}"
|
||||
local tsv="${TSV:-}" txf="${SCRATCH:-/tmp}/tx-wait-pay.out"
|
||||
local board
|
||||
board=$(
|
||||
python3 - "$tsv" "$txf" "${CUR:-GOA}" "$left" "$bal" "$pend" <<'PY' 2>/dev/null || true
|
||||
import json, sys
|
||||
from pathlib import Path
|
||||
|
||||
tsv, txf, cur, left, bal, pend = sys.argv[1:7]
|
||||
dec = json.JSONDecoder()
|
||||
|
||||
def num(a: str) -> str:
|
||||
a = (a or "").strip()
|
||||
if ":" in a:
|
||||
return a.split(":", 1)[1]
|
||||
return a
|
||||
|
||||
def iter_json(text: str):
|
||||
i, n = 0, len(text)
|
||||
while i < n:
|
||||
if text[i] == "{":
|
||||
try:
|
||||
o, end = dec.raw_decode(text, i)
|
||||
yield o
|
||||
i = end
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
i += 1
|
||||
|
||||
# TSV rows in start order (OK / OK_BANK only — successful bank path)
|
||||
rows = []
|
||||
if tsv and Path(tsv).is_file():
|
||||
for ln in open(tsv, errors="replace"):
|
||||
if not ln.strip() or ln.startswith("rung"):
|
||||
continue
|
||||
p = ln.rstrip("\n").split("\t")
|
||||
if len(p) < 10:
|
||||
continue
|
||||
status = p[3]
|
||||
if status not in ("OK", "OK_BANK"):
|
||||
continue
|
||||
rows.append({
|
||||
"rung": p[0],
|
||||
"amt": p[2],
|
||||
"bank": status,
|
||||
"wid": (p[9] if len(p) > 9 else "")[:12],
|
||||
})
|
||||
|
||||
# Wallet withdrawal txs (chronological if timestamp present)
|
||||
wds = []
|
||||
if txf and Path(txf).is_file():
|
||||
try:
|
||||
text = open(txf, errors="replace").read()
|
||||
except Exception:
|
||||
text = ""
|
||||
for o in iter_json(text):
|
||||
txs = o.get("transactions") if isinstance(o, dict) else None
|
||||
if not isinstance(txs, list):
|
||||
continue
|
||||
for t in txs:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
typ = str(t.get("type") or t.get("Type") or "").lower()
|
||||
if "withdraw" not in typ:
|
||||
continue
|
||||
amt = t.get("amountRaw") or t.get("amountEffective") or t.get("amount") or ""
|
||||
st = (
|
||||
t.get("txState")
|
||||
or t.get("status")
|
||||
or t.get("pendingMajor")
|
||||
or t.get("txMajorState")
|
||||
or ""
|
||||
)
|
||||
st = str(st).lower()
|
||||
det = t.get("withdrawalDetails") or t.get("withdrawal_details") or {}
|
||||
if not isinstance(det, dict):
|
||||
det = {}
|
||||
conf = det.get("confirmed")
|
||||
if conf is True and not st:
|
||||
st = "done"
|
||||
elif conf is False and not st:
|
||||
st = "pending"
|
||||
# pending* / dialog / kyc → still incoming; done/abort final
|
||||
if any(x in st for x in ("done", "abort", "fail", "delete")):
|
||||
phase = "done" if "done" in st else st
|
||||
elif st in ("", "none", "null"):
|
||||
phase = "unknown"
|
||||
else:
|
||||
phase = "pendingIncoming"
|
||||
ts = 0
|
||||
for k in ("timestamp", "timestamp_ms", "age"):
|
||||
v = t.get(k)
|
||||
if isinstance(v, dict):
|
||||
v = v.get("t_s") or v.get("t_ms") or v.get("t_usec")
|
||||
try:
|
||||
ts = int(v or 0)
|
||||
except Exception:
|
||||
ts = 0
|
||||
if ts:
|
||||
break
|
||||
wds.append({"amt": str(amt), "n": num(str(amt)), "phase": phase, "st": st or "?", "ts": ts})
|
||||
|
||||
wds.sort(key=lambda x: x["ts"])
|
||||
used = [False] * len(wds)
|
||||
|
||||
lines = [
|
||||
f"withdraw board (start order) · timer {left}s left · available={cur}:{bal} · pendingIncoming={cur}:{pend}"
|
||||
]
|
||||
if not rows:
|
||||
lines.append(" (no OK/OK_BANK rows in TSV yet)")
|
||||
else:
|
||||
for i, r in enumerate(rows, 1):
|
||||
n = num(r["amt"])
|
||||
match = None
|
||||
for j, w in enumerate(wds):
|
||||
if used[j]:
|
||||
continue
|
||||
if w["n"] == n or w["amt"] == r["amt"] or w["amt"].endswith(":" + n):
|
||||
used[j] = True
|
||||
match = w
|
||||
break
|
||||
if match is None:
|
||||
wlabel = "wallet=? (no tx match)"
|
||||
elif match["phase"] == "pendingIncoming":
|
||||
wlabel = f"wallet=pendingIncoming ({match['st']})"
|
||||
elif match["phase"] == "done":
|
||||
wlabel = f"wallet=done ({match['st']})"
|
||||
else:
|
||||
wlabel = f"wallet={match['phase']} ({match['st']})"
|
||||
wid = r["wid"] or "-"
|
||||
lines.append(
|
||||
f" #{i:<3} rung={r['rung']:<4} {r['amt']:<22} bank={r['bank']:<8} {wlabel} wid={wid}"
|
||||
)
|
||||
pending_n = sum(1 for w in wds if w["phase"] == "pendingIncoming")
|
||||
done_n = sum(1 for w in wds if w["phase"] == "done")
|
||||
lines.append(
|
||||
f" summary: tsv_ok={len(rows)} · wallet_wd={len(wds)} (pendingIncoming={pending_n} done={done_n})"
|
||||
)
|
||||
|
||||
print("\n".join(lines))
|
||||
PY
|
||||
)
|
||||
if [ -n "$board" ]; then
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
[ -n "$line" ] && info pay "$line"
|
||||
done <<<"$board"
|
||||
else
|
||||
info pay "timer ${left}s left · available=${CUR}:${bal} · pendingIncoming=${CUR}:${pend} · same wallet (board n/a)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Wait for spendable coins on the *same* wallet DB as withdraw (WDB).
|
||||
# After OK_BANK, available is often still 0 while pendingIncoming > 0 — pay needs available.
|
||||
# Shows withdraw board in start order + countdown (never looks hung).
|
||||
ladder_wait_available() {
|
||||
local wait_s="${1:-$LADDER_PAY_WAIT_AVAILABLE_S}"
|
||||
local end bal
|
||||
local end now left bal pend tick=0
|
||||
end=$(( $(date +%s) + wait_s ))
|
||||
bal=$(wallet_avail)
|
||||
pend=$(wallet_pending_in 2>/dev/null || echo "0")
|
||||
if python3 -c 'import sys; from decimal import Decimal; sys.exit(0 if Decimal(sys.argv[1])>0 else 1)' "$bal" 2>/dev/null; then
|
||||
printf '%s' "$bal"
|
||||
return 0
|
||||
fi
|
||||
info pay "same wallet as withdraw (db=$(basename "${WDB:-wallet}")) · available=${CUR}:${bal} · pendingIncoming=${CUR}:${pend}"
|
||||
info pay "waiting up to ${wait_s}s for spendable available>0 — board lists OK/OK_BANK withdraws in start order"
|
||||
# initial board before loop
|
||||
wcli transactions >"$SCRATCH/tx-wait-pay.out" 2>&1 || true
|
||||
ladder_print_wd_board "$wait_s" "$bal" "$pend"
|
||||
while python3 -c 'import sys; from decimal import Decimal; sys.exit(0 if Decimal(sys.argv[1])<=0 else 1)' "$bal" 2>/dev/null; do
|
||||
[ "$(date +%s)" -ge "$end" ] && break
|
||||
now=$(date +%s)
|
||||
left=$(( end - now ))
|
||||
if [ "$left" -le 0 ]; then
|
||||
wcli transactions >"$SCRATCH/tx-wait-pay.out" 2>&1 || true
|
||||
ladder_print_wd_board "0" "$bal" "$pend"
|
||||
info pay "timer 0s left — stop waiting (available still ${CUR}:0)"
|
||||
break
|
||||
fi
|
||||
# nudge wallet (same DB); do not run-until-done (can hang)
|
||||
wcli transactions >"$SCRATCH/tx-wait-pay.out" 2>&1 || true
|
||||
if command -v timeout >/dev/null 2>&1 || command -v gtimeout >/dev/null 2>&1; then
|
||||
local _to
|
||||
_to=$(command -v gtimeout 2>/dev/null || command -v timeout)
|
||||
"$_to" 8 wcli advanced run-pending >"$SCRATCH/run-pending.out" 2>&1 || true
|
||||
fi
|
||||
# progress board every ~5s (tick every 2s → every 3rd)
|
||||
if [ $((tick % 3)) -eq 0 ]; then
|
||||
ladder_print_wd_board "$left" "$bal" "$pend"
|
||||
fi
|
||||
sleep 2
|
||||
tick=$((tick + 1))
|
||||
bal=$(wallet_avail)
|
||||
pend=$(wallet_pending_in 2>/dev/null || echo "0")
|
||||
done
|
||||
if python3 -c 'import sys; from decimal import Decimal; sys.exit(0 if Decimal(sys.argv[1])>0 else 1)' "$bal" 2>/dev/null; then
|
||||
wcli transactions >"$SCRATCH/tx-wait-pay.out" 2>&1 || true
|
||||
ladder_print_wd_board "ready" "$bal" "$pend"
|
||||
ok pay "spendable coins ready available=${CUR}:${bal} · $(format_amount_alt "${CUR}:${bal}") (same wallet)"
|
||||
else
|
||||
warn pay "no spendable coins after ${wait_s}s" \
|
||||
"problem: available still ${CUR}:0 (pendingIncoming=${CUR}:${pend}). Board above shows which withdraws still pendingIncoming. Pay will SKIP_BALANCE / skip max-pay until wirewatch+wallet finish. Same wallet as withdraw."
|
||||
fi
|
||||
printf '%s' "$bal"
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +542,9 @@ ladder_run_pay_phase() {
|
|||
info pay "public free template ${LADDER_FREE_TEMPLATE}@${LADDER_FREE_TEMPLATE_INSTANCE} (no merchant token)"
|
||||
fi
|
||||
|
||||
# Same cumulative wallet as withdraw (WDB=wallet-main.sqlite3)
|
||||
wallet_prepare "main"
|
||||
info pay "using same wallet as withdraw · WDB=${WDB:-?}"
|
||||
local bal0
|
||||
bal0=$(ladder_wait_available "$LADDER_PAY_WAIT_AVAILABLE_S")
|
||||
info "pay budget" "wallet available ${CUR}:${bal0} · $(format_amount_alt "${CUR}:${bal0}") — never order more than this"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue