summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-11-22 15:20:10 +0100
committerFlorian Dold <florian@dold.me>2023-11-22 17:08:15 +0100
commitbd37a0b04123d734e1e3fae105f0d9c24279629f (patch)
tree31f03c2acd3c1f123f8762391456b1aa22df9803 /packages/taler-wallet-core
parent32182fb1b912e1136ba933c4a4f204e6e2f33de2 (diff)
downloadwallet-core-bd37a0b04123d734e1e3fae105f0d9c24279629f.tar.gz
wallet-core-bd37a0b04123d734e1e3fae105f0d9c24279629f.tar.bz2
wallet-core-bd37a0b04123d734e1e3fae105f0d9c24279629f.zip
wallet-core: implement and test currency conversion withdrawals
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts2
-rw-r--r--packages/taler-wallet-core/src/db.ts7
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts1
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts129
-rw-r--r--packages/taler-wallet-core/src/wallet.ts1
5 files changed, 121 insertions, 19 deletions
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 56392f090..36ca128ae 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -164,7 +164,7 @@ export interface TalerCryptoInterface {
req: ContractTermsValidationRequest,
): Promise<ValidationResult>;
- createEddsaKeypair(req: unknown): Promise<EddsaKeypair>;
+ createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 3e8452bcd..0cafae2a1 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -52,10 +52,10 @@ import {
TalerPreciseTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
- //TalerProtocolTimestamp,
TransactionIdStr,
UnblindedSignature,
WireInfo,
+ WithdrawalAccountInfo,
codecForAny,
} from "@gnu-taler/taler-util";
import { DbRetryInfo, TaskIdentifiers } from "./operations/common.js";
@@ -1373,6 +1373,11 @@ export interface WgInfoBankIntegrated {
export interface WgInfoBankManual {
withdrawalType: WithdrawalRecordType.BankManual;
+
+ /**
+ * Info about withdrawal accounts, possibly including currency conversion.
+ */
+ exchangeCreditAccounts?: WithdrawalAccountInfo[];
}
export interface WgInfoBankPeerPull {
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 7ee6275b0..b294c163e 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -720,6 +720,7 @@ function buildTransactionForManualWithdraw(
type: WithdrawalType.ManualTransfer,
reservePub: withdrawalGroup.reservePub,
exchangePaytoUris,
+ exchangeCreditAccounts: withdrawalGroup.wgInfo.exchangeCreditAccounts,
reserveIsReady:
withdrawalGroup.status === WithdrawalGroupStatus.Done ||
withdrawalGroup.status === WithdrawalGroupStatus.PendingReady,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 275d0aaf0..a5a6bded8 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -24,6 +24,7 @@ import {
AgeRestriction,
AmountJson,
AmountLike,
+ AmountString,
Amounts,
BankWithdrawDetails,
CancellationToken,
@@ -42,6 +43,7 @@ import {
LibtoolVersion,
Logger,
NotificationType,
+ PaytoUri,
TalerError,
TalerErrorCode,
TalerErrorDetail,
@@ -54,21 +56,25 @@ import {
TransactionType,
URL,
UnblindedSignature,
+ WireAccountDetails,
WithdrawUriInfoResponse,
+ WithdrawalAccountInfo,
addPaytoQueryParams,
canonicalizeBaseUrl,
codecForBankWithdrawalOperationPostResponse,
+ codecForCashinConversionResponse,
codecForExchangeWithdrawBatchResponse,
codecForIntegrationBankConfig,
codecForReserveStatus,
codecForWalletKycUuid,
codecForWithdrawOperationStatusResponse,
+ createEddsaKeyPair,
encodeCrock,
getErrorDetailFromException,
getRandomBytes,
j2s,
makeErrorDetail,
- parseWithdrawUri
+ parseWithdrawUri,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -95,9 +101,8 @@ import {
import {
ExchangeDetailsRecord,
ExchangeEntryDbRecordStatus,
- PendingTaskType,
isWithdrawableDenom,
- timestampPreciseToDb
+ timestampPreciseToDb,
} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import {
@@ -110,6 +115,7 @@ import {
makeExchangeListItem,
runLongpollAsync,
} from "../operations/common.js";
+import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
import {
selectForcedWithdrawalDenominations,
@@ -1751,8 +1757,23 @@ export async function getExchangeWithdrawalInfo(
logger.trace("updating exchange");
const { exchange, exchangeDetails } =
await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl);
+
+ if (exchangeDetails.currency != instructedAmount.currency) {
+ // Specifiying the amount in the conversion input currency is not yet supported.
+ // We might add support for it later.
+ throw new Error(
+ `withdrawal only supported when specifying target currency ${exchangeDetails.currency}`,
+ );
+ }
+
+ const withdrawalAccountList = await fetchWithdrawalAccountInfo(ws, {
+ exchangeDetails,
+ instructedAmount,
+ });
+
logger.trace("updating withdrawal denoms");
await updateWithdrawalDenoms(ws, exchangeBaseUrl);
+
logger.trace("getting candidate denoms");
const denoms = await getCandidateWithdrawalDenoms(ws, exchangeBaseUrl);
logger.trace("selecting withdrawal denoms");
@@ -1773,8 +1794,15 @@ export async function getExchangeWithdrawalInfo(
}
const exchangeWireAccounts: string[] = [];
+
for (const account of exchangeDetails.wireInfo.accounts) {
- exchangeWireAccounts.push(account.payto_uri);
+ const details: WireAccountDetails = {
+ paytoUri: account.payto_uri,
+ };
+ if (account.credit_restrictions) {
+ details.creditRestrictions = account.credit_restrictions;
+ }
+ exchangeWireAccounts.push(details.paytoUri);
}
let hasDenomWithAgeRestriction = false;
@@ -1854,6 +1882,7 @@ export async function getExchangeWithdrawalInfo(
earliestDepositExpiration,
exchangePaytoUris: paytoUris,
exchangeWireAccounts,
+ withdrawalAccountList,
exchangeVersion: exchangeDetails.protocolVersionRange || "unknown",
numOfferedDenoms: possibleDenoms.length,
selectedDenoms,
@@ -1946,15 +1975,6 @@ export async function getWithdrawalDetailsForUri(
};
}
-export async function getFundingPaytoUrisTx(
- ws: InternalWalletState,
- withdrawalGroupId: string,
-): Promise<string[]> {
- return await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.withdrawalGroups])
- .runReadWrite((tx) => getFundingPaytoUris(tx, withdrawalGroupId));
-}
-
export function augmentPaytoUrisForWithdrawal(
plainPaytoUris: string[],
reservePub: string,
@@ -2361,10 +2381,6 @@ export async function internalPrepareCreateWithdrawalGroup(
const exchangeInfo = await updateExchangeFromUrl(ws, canonExchange);
const exchangeDetails = exchangeInfo.exchangeDetails;
- if (!exchangeDetails) {
- logger.trace(exchangeDetails);
- throw Error("exchange not updated");
- }
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@@ -2566,6 +2582,60 @@ export async function acceptWithdrawalFromUri(
}
/**
+ * Gather information about bank accounts that can be used for
+ * withdrawals. This includes accounts that are in a different
+ * currency and require conversion.
+ */
+async function fetchWithdrawalAccountInfo(
+ ws: InternalWalletState,
+ req: {
+ exchangeDetails: ExchangeDetailsRecord;
+ instructedAmount: AmountJson;
+ reservePub?: string;
+ },
+): Promise<WithdrawalAccountInfo[]> {
+ const { exchangeDetails, instructedAmount } = req;
+ const withdrawalAccounts: WithdrawalAccountInfo[] = [];
+ for (let acct of exchangeDetails.wireInfo.accounts) {
+ let paytoUri: string;
+ let transferAmount: AmountString;
+ if (acct.conversion_url != null) {
+ const reqUrl = new URL("cashin-rate", acct.conversion_url);
+ reqUrl.searchParams.set(
+ "amount_credit",
+ Amounts.stringify(instructedAmount),
+ );
+ const httpResp = await ws.http.fetch(reqUrl.href);
+ const resp = await readSuccessResponseJsonOrThrow(
+ httpResp,
+ codecForCashinConversionResponse(),
+ );
+ paytoUri = acct.payto_uri;
+ transferAmount = resp.amount_debit;
+ if (req.reservePub) {
+ }
+ } else {
+ paytoUri = acct.payto_uri;
+ transferAmount = Amounts.stringify(instructedAmount);
+ }
+ paytoUri = addPaytoQueryParams(paytoUri, {
+ amount: Amounts.stringify(transferAmount),
+ });
+ if (req.reservePub != null) {
+ paytoUri = addPaytoQueryParams(paytoUri, {
+ message: `Taler Withdrawal ${req.reservePub}`,
+ });
+ }
+ const acctInfo: WithdrawalAccountInfo = {
+ paytoUri,
+ transferAmount,
+ };
+ withdrawalAccounts.push(acctInfo);
+ }
+ return withdrawalAccounts;
+}
+
+/**
* Create a manual withdrawal operation.
*
* Adds the corresponding exchange as a trusted exchange if it is neither
@@ -2582,15 +2652,39 @@ export async function createManualWithdrawal(
forcedDenomSel?: ForcedDenomSel;
},
): Promise<AcceptManualWithdrawalResult> {
+ const { exchangeBaseUrl } = req;
+ const amount = Amounts.parseOrThrow(req.amount);
+ const { exchangeDetails } = await ws.exchangeOps.updateExchangeFromUrl(
+ ws,
+ exchangeBaseUrl,
+ );
+
+ if (exchangeDetails.currency != amount.currency) {
+ throw Error(
+ "manual withdrawal with conversion from foreign currency is not yet supported",
+ );
+ }
+ const reserveKeyPair: EddsaKeypair = await ws.cryptoApi.createEddsaKeypair(
+ {},
+ );
+
+ const withdrawalAccountList = await fetchWithdrawalAccountInfo(ws, {
+ exchangeDetails,
+ instructedAmount: amount,
+ reservePub: reserveKeyPair.pub,
+ });
+
const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
amount: Amounts.jsonifyAmount(req.amount),
wgInfo: {
withdrawalType: WithdrawalRecordType.BankManual,
+ exchangeCreditAccounts: withdrawalAccountList,
},
exchangeBaseUrl: req.exchangeBaseUrl,
forcedDenomSel: req.forcedDenomSel,
restrictAge: req.restrictAge,
reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
+ reserveKeyPair,
});
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
@@ -2610,6 +2704,7 @@ export async function createManualWithdrawal(
return {
reservePub: withdrawalGroup.reservePub,
exchangePaytoUris: exchangePaytoUris,
+ withdrawalAccountsList: withdrawalAccountList,
transactionId,
};
}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 0694aef8a..4472bdbad 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -1215,6 +1215,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
paytoUris: wi.exchangePaytoUris,
tosAccepted: wi.termsOfServiceAccepted,
ageRestrictionOptions: wi.ageRestrictionOptions,
+ withdrawalAccountList: wi.withdrawalAccountList,
numCoins,
};
return resp;