commit b8a1443edcbb68eafd6f16de428a2905af829600
parent b14adbb9d95c074897fac80cd9e252996785f46f
Author: Florian Dold <dold@taler.net>
Date: Sun, 9 Aug 2026 23:51:10 +0200
wallet-cli: consolidate deposit command
Diffstat:
1 file changed, 118 insertions(+), 22 deletions(-)
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
@@ -1445,6 +1445,98 @@ async function waitForCreatedTx(
}
/**
+ * States at which a deposit no longer needs the CLI to keep running. A
+ * deposit can take a long time to become final while its wire transfer is
+ * being tracked, so finalizing is sufficient here. KYC and suspended states
+ * need action outside the wallet task loop and therefore also end the wait.
+ */
+const DEPOSIT_FINAL_STATE_PATTERNS: TransactionStatePattern[] = [
+ { major: TransactionMajorState.Done, minor: "*", working: "*" },
+ { major: TransactionMajorState.Failed, minor: "*", working: "*" },
+ { major: TransactionMajorState.Aborted, minor: "*", working: "*" },
+ { major: TransactionMajorState.Expired, minor: "*", working: "*" },
+ { major: TransactionMajorState.Deleted, minor: "*", working: "*" },
+];
+
+const DEPOSIT_USER_ACTION_STATE_PATTERNS: TransactionStatePattern[] = [
+ {
+ major: TransactionMajorState.SuspendedFinalizing,
+ minor: "*",
+ working: "*",
+ },
+ { major: TransactionMajorState.Suspended, minor: "*", working: "*" },
+ {
+ major: TransactionMajorState.SuspendedAborting,
+ minor: "*",
+ working: "*",
+ },
+ { major: TransactionMajorState.Dialog, minor: "*", working: "*" },
+ { major: "*", minor: TransactionMinorState.KycRequired, working: "*" },
+ {
+ major: "*",
+ minor: TransactionMinorState.KycAuthRequired,
+ working: "*",
+ },
+];
+
+const DEPOSIT_WAIT_STATE_PATTERNS: TransactionStatePattern[] = [
+ ...DEPOSIT_FINAL_STATE_PATTERNS,
+ { major: TransactionMajorState.Finalizing, minor: "*", working: "*" },
+ ...DEPOSIT_USER_ACTION_STATE_PATTERNS,
+];
+
+interface DepositWaitArgs {
+ noWait: boolean;
+ timeout?: string;
+}
+
+/**
+ * Wait for a newly created deposit until it is final, finalizing, or needs
+ * action from the user. Returns the exit code for the command.
+ */
+async function waitForCreatedDepositTx(
+ ctx: WalletContext,
+ transactionId: TransactionIdStr,
+ a: DepositWaitArgs,
+): Promise<number> {
+ if (a.noWait) {
+ return 0;
+ }
+ const timeout = a.timeout != null ? parseTimeoutSpec(a.timeout) : "forever";
+ const outcome = await waitForTxState(ctx, {
+ transactionId,
+ txState: DEPOSIT_WAIT_STATE_PATTERNS,
+ timeoutMs: timeout === "forever" ? undefined : timeout,
+ });
+ if (outcome.result === "timeout") {
+ console.error(`timeout: transaction ${transactionId} did not finish`);
+ return EXIT_TIMEOUT;
+ }
+ console.log(
+ `transaction ${transactionId} reached state ${formatTxState(
+ outcome.txState,
+ )}`,
+ );
+ const unsuccessful = UNSUCCESSFUL_STATE_PATTERNS.some((pat) =>
+ matchTransactionState(outcome.txState, pat),
+ );
+ if (unsuccessful) {
+ return EXIT_TX_UNSUCCESSFUL;
+ }
+ const requiresUserAction = DEPOSIT_USER_ACTION_STATE_PATTERNS.some((pat) =>
+ matchTransactionState(outcome.txState, pat),
+ );
+ if (requiresUserAction) {
+ const tx = await lookupTxOrNull(ctx, transactionId);
+ if (tx?.kycUrl != null) {
+ console.log(`kyc required: ${tx.kycUrl}`);
+ }
+ return EXIT_INPUT_REQUIRED;
+ }
+ return 0;
+}
+
+/**
* Answers that a continuation may need, supplied on the command line
* so that it also works without a terminal.
*/
@@ -2736,55 +2828,59 @@ bankAccountsCli.subcommand("listArgs", "list").action(async (args) => {
});
const depositCli = walletCli.subcommand("depositArgs", "deposit", {
- help: "Subcommands for depositing money to payto:// accounts",
+ help: "Deposit money to a payto:// account.",
});
depositCli
- .subcommand("createDepositArgs", "create")
.requiredArgument("amount", clk.AMOUNT)
.requiredArgument("targetPayto", clk.STRING)
- .flag("wait", ["--wait"], {
- help: "Wait until the transaction is in a final state.",
+ .flag("check", ["--check"], {
+ help: "Check the deposit without creating it.",
+ })
+ .flag("noWait", ["--no-wait"], {
+ help: "Exit after creating the deposit instead of waiting for its result.",
})
.maybeOption("timeout", ["--timeout"], clk.STRING, {
help: "Give up waiting after this duration (e.g. '30s', '5m').",
})
.action(async (args) => {
+ if (args.depositArgs.check) {
+ await runCliAction(() =>
+ withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
+ const resp = await wallet.client.call(
+ WalletApiOperation.CheckDeposit,
+ {
+ amount: args.depositArgs.amount,
+ depositPaytoUri: args.depositArgs.targetPayto,
+ },
+ );
+ console.log(`Check deposit result: ${j2s(resp)}`);
+ }),
+ );
+ return;
+ }
+
await runCliAction(() =>
withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
const resp = await wallet.client.call(
WalletApiOperation.CreateDepositGroup,
{
- amount: args.createDepositArgs.amount,
- depositPaytoUri: args.createDepositArgs.targetPayto,
+ amount: args.depositArgs.amount,
+ depositPaytoUri: args.depositArgs.targetPayto,
},
);
console.log(`Created deposit ${resp.depositGroupId}`);
// The transaction ID is what the other commands take.
console.log(`transaction ${resp.transactionId}`);
- return await waitForCreatedTx(
+ return await waitForCreatedDepositTx(
wallet,
resp.transactionId,
- args.createDepositArgs,
+ args.depositArgs,
);
}),
);
});
-depositCli
- .subcommand("checkDepositArgs", "check")
- .requiredArgument("amount", clk.AMOUNT)
- .requiredArgument("targetPayto", clk.STRING)
- .action(async (args) => {
- await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- const resp = await wallet.client.call(WalletApiOperation.CheckDeposit, {
- amount: args.checkDepositArgs.amount,
- depositPaytoUri: args.checkDepositArgs.targetPayto,
- });
- console.log(`Check deposit result: ${j2s(resp)}`);
- });
- });
-
const peerCli = walletCli.subcommand("peerArgs", "p2p", {
help: "Subcommands for peer-to-peer payments.",
mark: "legacy",