commit 5651a7a0219817936487a473959570e86a9c2d42
parent 2fb8ecb717a230d9e97696336f3196707f556f1b
Author: Florian Dold <dold@taler.net>
Date: Mon, 10 Aug 2026 01:09:17 +0200
wallet-cli: unify withdrawal commands
Diffstat:
1 file changed, 112 insertions(+), 132 deletions(-)
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
@@ -2240,72 +2240,130 @@ walletCli
});
});
-const withdrawCli = walletCli.subcommand("withdraw", "withdraw", {
- help: "Withdraw with a taler://withdraw/ URI",
-});
-
-withdrawCli
- .subcommand("withdrawCheckUri", "check-uri", { mark: "legacy" })
- .requiredArgument("uri", clk.STRING)
+const withdrawCli = walletCli
+ .subcommand("withdraw", "withdraw", {
+ help: "Withdraw from a bank-integrated URI or directly from an exchange.",
+ })
+ .maybeOption("uri", ["--uri"], clk.STRING, {
+ help: "Bank-integrated taler://withdraw/ URI.",
+ })
+ .maybeOption("exchange", ["--exchange"], clk.STRING, {
+ help: "Base URL of the exchange (required without a URI).",
+ })
+ .maybeOption("amount", ["--amount"], clk.AMOUNT, {
+ help: "Amount to withdraw (required without a URI).",
+ })
+ .flag("check", ["--check"], {
+ help: "Show withdrawal details and fees without creating a transaction.",
+ })
+ .maybeOption("forcedReservePriv", ["--forced-reserve-priv"], clk.STRING)
.maybeOption("restrictAge", ["--restrict-age"], clk.INT)
- .action(async (args) => {
- const uri = args.withdrawCheckUri.uri;
- const restrictAge = args.withdrawCheckUri.restrictAge;
- logger.info(`age restriction requested (${restrictAge})`);
- await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- const withdrawInfo = await wallet.client.call(
- WalletApiOperation.GetWithdrawalDetailsForUri,
- {
- talerWithdrawUri: uri,
- restrictAge,
- },
- );
- console.log("withdrawInfo", withdrawInfo);
- });
+ .flag("wait", ["--wait"], {
+ help: "Wait until the transaction is in a final state.",
+ })
+ .maybeOption("timeout", ["--timeout"], clk.STRING, {
+ help: "Give up waiting after this duration (e.g. '30s', '5m').",
});
-withdrawCli
- .subcommand("withdrawCheckAmount", "check-amount")
- .requiredArgument("exchange", clk.STRING)
- .requiredArgument("amount", clk.AMOUNT)
- .maybeOption("restrictAge", ["--restrict-age"], clk.INT)
- .action(async (args) => {
- const restrictAge = args.withdrawCheckAmount.restrictAge;
- logger.info(`age restriction requested (${restrictAge})`);
- await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- const withdrawInfo = await wallet.client.call(
+withdrawCli.action(async (args) => {
+ await runCliAction(() =>
+ withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
+ const { uri, exchange, amount, restrictAge } = args.withdraw;
+ if (uri != null) {
+ if (args.withdraw.forcedReservePriv != null) {
+ throw new CliUsageError(
+ "--forced-reserve-priv is only available for direct exchange withdrawals",
+ "omit the URI to use a direct exchange withdrawal",
+ );
+ }
+ const details = await wallet.client.call(
+ WalletApiOperation.GetWithdrawalDetailsForUri,
+ {
+ talerWithdrawUri: uri,
+ restrictAge,
+ },
+ );
+ if (args.withdraw.check) {
+ console.log(j2s(details));
+ return;
+ }
+ const exchangeBaseUrl = exchange ?? details.defaultExchangeBaseUrl;
+ if (exchangeBaseUrl == null) {
+ throw new CliUsageError(
+ "no exchange was specified or suggested by the withdrawal URI",
+ "pass --exchange EXCHANGE_URL",
+ );
+ }
+ const res = await wallet.client.call(
+ WalletApiOperation.AcceptBankIntegratedWithdrawal,
+ {
+ exchangeBaseUrl,
+ talerWithdrawUri: uri,
+ amount,
+ restrictAge,
+ },
+ );
+ console.log(`transaction ${res.transactionId}`);
+ return await waitForCreatedTx(wallet, res.transactionId, args.withdraw);
+ }
+
+ if (exchange == null || amount == null) {
+ throw new CliUsageError(
+ "direct withdrawals require both --exchange and --amount",
+ "pass a TALER_WITHDRAW_URI for a bank-integrated withdrawal",
+ );
+ }
+ const details = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForAmount,
{
- amount: args.withdrawCheckAmount.amount,
- exchangeBaseUrl: args.withdrawCheckAmount.exchange,
+ amount,
+ exchangeBaseUrl: exchange,
restrictAge,
},
);
- console.log("withdrawInfo", withdrawInfo);
- });
- });
-
-withdrawCli
- .subcommand("withdrawAcceptUri", "accept-uri", { mark: "legacy" })
- .requiredArgument("uri", clk.STRING)
- .requiredOption("exchange", ["--exchange"], clk.STRING)
- .maybeOption("restrictAge", ["--restrict-age"], clk.INT)
- .action(async (args) => {
- const uri = args.withdrawAcceptUri.uri;
- const restrictAge = args.withdrawAcceptUri.restrictAge;
- console.log(`age restriction requested (${restrictAge})`);
- await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- const res = await wallet.client.call(
- WalletApiOperation.AcceptBankIntegratedWithdrawal,
+ if (args.withdraw.check) {
+ console.log(j2s(details));
+ return;
+ }
+ if (details.withdrawalAccountsList.length === 0) {
+ console.log("exchange has no accounts");
+ return EXIT_API_ERROR;
+ }
+ const resp = await wallet.client.call(
+ WalletApiOperation.AcceptManualWithdrawal,
{
- exchangeBaseUrl: args.withdrawAcceptUri.exchange,
- talerWithdrawUri: uri,
+ amount,
+ exchangeBaseUrl: exchange,
restrictAge,
+ forceReservePriv: args.withdraw.forcedReservePriv,
},
);
- console.log(j2s(res));
- });
- });
+ await wallet.client.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: resp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.ExchangeWaitReserve,
+ },
+ });
+ const txDet = await wallet.client.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: resp.transactionId,
+ },
+ );
+ if (txDet.type !== TransactionType.Withdrawal) {
+ throw Error("assertion failed");
+ }
+ if (txDet.withdrawalDetails.type !== WithdrawalType.ManualTransfer) {
+ throw Error("assertion failed");
+ }
+ console.log(`transaction ${resp.transactionId}`);
+ console.log("transfer accounts:");
+ console.log(j2s(txDet.withdrawalDetails.exchangeCreditAccountDetails));
+ return await waitForCreatedTx(wallet, resp.transactionId, args.withdraw);
+ }),
+ );
+});
async function cliHandleTos(
wallet: WalletContext,
@@ -2598,84 +2656,6 @@ walletCli
});
});
-withdrawCli
- .subcommand("withdrawManually", "manual", {
- help: "Withdraw manually from an exchange.",
- })
- .requiredOption("exchange", ["--exchange"], clk.STRING, {
- help: "Base URL of the exchange.",
- })
- .requiredOption("amount", ["--amount"], clk.AMOUNT, {
- help: "Amount to withdraw",
- })
- .maybeOption("forcedReservePriv", ["--forced-reserve-priv"], clk.STRING, {})
- .maybeOption("restrictAge", ["--restrict-age"], clk.INT)
- .flag("wait", ["--wait"], {
- help: "Wait until the transaction is in a final state.",
- })
- .maybeOption("timeout", ["--timeout"], clk.STRING, {
- help: "Give up waiting after this duration (e.g. '30s', '5m').",
- })
- .action(async (args) => {
- await runCliAction(() =>
- withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
- const exchangeBaseUrl = args.withdrawManually.exchange;
- const amount = args.withdrawManually.amount;
- const d = await wallet.client.call(
- WalletApiOperation.GetWithdrawalDetailsForAmount,
- {
- amount: args.withdrawManually.amount,
- exchangeBaseUrl: exchangeBaseUrl,
- },
- );
- const acct = d.withdrawalAccountsList[0];
- if (!acct) {
- console.log("exchange has no accounts");
- return EXIT_API_ERROR;
- }
- const resp = await wallet.client.call(
- WalletApiOperation.AcceptManualWithdrawal,
- {
- amount,
- exchangeBaseUrl,
- restrictAge: args.withdrawManually.restrictAge,
- forceReservePriv: args.withdrawManually.forcedReservePriv,
- },
- );
- await wallet.client.call(
- WalletApiOperation.TestingWaitTransactionState,
- {
- transactionId: resp.transactionId,
- txState: {
- major: TransactionMajorState.Pending,
- minor: TransactionMinorState.ExchangeWaitReserve,
- },
- },
- );
- const txDet = await wallet.client.call(
- WalletApiOperation.GetTransactionById,
- {
- transactionId: resp.transactionId,
- },
- );
- if (txDet.type !== TransactionType.Withdrawal) {
- throw Error("assertion failed");
- }
- if (txDet.withdrawalDetails.type !== WithdrawalType.ManualTransfer) {
- throw Error("assertion failed");
- }
- console.log(`transaction ${resp.transactionId}`);
- console.log("transfer accounts:");
- console.log(j2s(txDet.withdrawalDetails.exchangeCreditAccountDetails));
- return await waitForCreatedTx(
- wallet,
- resp.transactionId,
- args.withdrawManually,
- );
- }),
- );
- });
-
const exchangesCli = walletCli
.subcommand("exchangesCmd", "exchanges", {
help: [