taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 1f83b0afa73d26ffb2f61ec466032f14f6d07ab3
parent f1b329b668e0795726f0fa672b5b91fcc6986df6
Author: Florian Dold <florian@dold.me>
Date:   Sun, 24 Aug 2025 19:18:45 +0200

wallet-core: refactor

Diffstat:
Mpackages/taler-wallet-core/src/dev-experiments.ts | 578++++++++++++++++++++++++++++++++++++++++---------------------------------------
1 file changed, 293 insertions(+), 285 deletions(-)

diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts @@ -30,6 +30,7 @@ import { Amounts, ContractTermsUtil, DenomLossEventType, + DevExperimentUri, Duration, Logger, MerchantContractTermsV0, @@ -218,291 +219,7 @@ export async function applyDevExperiment( return; } case "add-fake-tx": { - const txType = parsedUri.query?.get("txType") ?? "withdrawal"; - const withdrawalGroupId = encodeCrock(getRandomBytes(32)); - const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); - switch (txType) { - case "withdrawal": { - const secretSeed = encodeCrock(getRandomBytes(32)); - const reservePair = await wex.ws.cryptoApi.createEddsaKeypair({}); - const exchangeBaseUrl = - parsedUri.query?.get("exchangeBaseUrl") ?? - "https://exchange.demo.taler.net/"; - const trelStr = parsedUri.query?.get("tRel") ?? undefined; - const amountEffectiveStr = parsedUri.query?.get("amountEffective"); - if (!amountEffectiveStr) { - throw Error("missing amountEffective option"); - } - const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); - let timestamp: AbsoluteTime; - if (trelStr) { - timestamp = AbsoluteTime.subtractDuraction( - AbsoluteTime.now(), - Duration.fromPrettyString(trelStr), - ); - } else { - timestamp = AbsoluteTime.now(); - } - await wex.db.runAllStoresReadWriteTx({}, async (tx) => { - await tx.withdrawalGroups.add({ - denomSelUid: encodeCrock(getRandomBytes(32)), - reservePriv: reservePair.priv, - reservePub: reservePair.pub, - secretSeed, - status: WithdrawalGroupStatus.Done, - timestampStart: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - timestampFinish: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - withdrawalGroupId, - wgInfo: { - withdrawalType: WithdrawalRecordType.BankManual, - exchangeCreditAccounts: [ - { - paytoUri: "payto://x-taler-bank/test/test", - status: "ok", - }, - ], - }, - denomsSel: { - totalCoinValue: Amounts.stringify(amountEffective), - hasDenomWithAgeRestriction: false, - totalWithdrawCost: Amounts.stringify(amountEffective), - selectedDenoms: [], - }, - exchangeBaseUrl, - instructedAmount: Amounts.stringify(amountEffective), - effectiveWithdrawalAmount: Amounts.stringify(amountEffective), - rawWithdrawalAmount: Amounts.stringify(amountEffective), - }); - await ctx.updateTransactionMeta(tx); - }); - break; - } - case "payment": { - const proposalId = encodeCrock(getRandomBytes(32)); - const ctx = new PayMerchantTransactionContext(wex, proposalId); - const summary = parsedUri.query?.get("summary") ?? "Test"; - const amountEffectiveStr = parsedUri.query?.get("amountEffective"); - if (!amountEffectiveStr) { - throw Error("missing amountEffective option"); - } - const merchantName = - parsedUri.query?.get("merchantName") ?? "Test Merchant"; - const exchangeBaseUrl = - parsedUri.query?.get("exchangeBaseUrl") ?? - "https://exchange.demo.taler.net/"; - const merchantBaseUrl = - parsedUri.query?.get("merchantBaseUrl") ?? - "https://backend.demo.taler.net/"; - const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); - const trelStr = parsedUri.query?.get("tRel") ?? undefined; - let timestamp: AbsoluteTime; - if (trelStr) { - timestamp = AbsoluteTime.subtractDuraction( - AbsoluteTime.now(), - Duration.fromPrettyString(trelStr), - ); - } else { - timestamp = AbsoluteTime.now(); - } - const orderId = encodeCrock(getRandomBytes(8)); - const noncePair = await wex.ws.cryptoApi.createEddsaKeypair({}); - const merchantPub = encodeCrock(getRandomBytes(32)); - const ct: MerchantContractTermsV0 = { - amount: Amounts.stringify(amountEffective), - exchanges: [ - { - master_pub: encodeCrock(getRandomBytes(32)), - priority: 1, - url: exchangeBaseUrl, - }, - ], - h_wire: encodeCrock(getRandomBytes(64)), - max_fee: Amounts.stringify(Amounts.zeroOfAmount(amountEffective)), - timestamp: AbsoluteTime.toProtocolTimestamp(timestamp), - summary, - order_id: orderId, - merchant_base_url: merchantBaseUrl, - merchant_pub: merchantPub, - wire_method: "x-taler-bank", - version: MerchantContractVersion.V0, - nonce: encodeCrock(getRandomBytes(32)), - merchant: { - name: merchantName, - }, - pay_deadline: AbsoluteTime.toProtocolTimestamp(timestamp), - refund_deadline: AbsoluteTime.toProtocolTimestamp(timestamp), - wire_transfer_deadline: AbsoluteTime.toProtocolTimestamp(timestamp), - }; - const contractTermsHash = ContractTermsUtil.hashContractTerms(ct); - await wex.db.runAllStoresReadWriteTx({}, async (tx) => { - await tx.contractTerms.put({ - contractTermsRaw: ct, - h: contractTermsHash, - }); - await tx.purchases.put({ - autoRefundDeadline: undefined, - claimToken: encodeCrock(getRandomBytes(32)), - downloadSessionId: encodeCrock(getRandomBytes(32)), - orderId, - proposalId, - download: { - contractTermsHash, - contractTermsMerchantSig: encodeCrock(getRandomBytes(64)), - currency: Amounts.currencyOf(amountEffective), - }, - merchantBaseUrl, - purchaseStatus: PurchaseStatus.Done, - repurchaseProposalId: undefined, - lastSessionId: undefined, - merchantPaySig: encodeCrock(getRandomBytes(64)), - noncePriv: noncePair.priv, - noncePub: noncePair.pub, - payInfo: { - totalPayCost: Amounts.stringify(amountEffective), - }, - posConfirmation: undefined, - secretSeed: encodeCrock(getRandomBytes(32)), - timestamp: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - shared: false, - timestampFirstSuccessfulPay: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - timestampAccept: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - timestampLastRefundStatus: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - refundAmountAwaiting: undefined, - }); - await ctx.updateTransactionMeta(tx); - }); - break; - } - case "peer-push-debit": { - const pursePair = await wex.cryptoApi.createEddsaKeypair({}); - const pursePub = pursePair.pub; - const ctx = new PeerPushDebitTransactionContext(wex, pursePub); - const summary = parsedUri.query?.get("summary") ?? "Test"; - const amountEffectiveStr = parsedUri.query?.get("amountEffective"); - if (!amountEffectiveStr) { - throw Error("missing amountEffective option"); - } - const exchangeBaseUrl = - parsedUri.query?.get("exchangeBaseUrl") ?? - "https://exchange.demo.taler.net/"; - const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); - const trelStr = parsedUri.query?.get("tRel") ?? undefined; - let timestamp: AbsoluteTime; - if (trelStr) { - timestamp = AbsoluteTime.subtractDuraction( - AbsoluteTime.now(), - Duration.fromPrettyString(trelStr), - ); - } else { - timestamp = AbsoluteTime.now(); - } - const ct: PeerContractTerms = { - amount: Amounts.stringify(amountEffective), - purse_expiration: AbsoluteTime.toProtocolTimestamp(timestamp), - summary, - }; - const contractTermsHash = ContractTermsUtil.hashContractTerms(ct); - const contractPair = await wex.cryptoApi.createEddsaKeypair({}); - const mergePair = await wex.cryptoApi.createEddsaKeypair({}); - await wex.db.runAllStoresReadWriteTx({}, async (tx) => { - await tx.contractTerms.put({ - contractTermsRaw: ct, - h: contractTermsHash, - }); - await tx.peerPushDebit.put({ - amount: Amounts.stringify(amountEffective), - contractEncNonce: encodeCrock(getRandomBytes(32)), - contractPriv: contractPair.priv, - contractPub: contractPair.pub, - contractTermsHash: contractTermsHash, - exchangeBaseUrl, - mergePriv: mergePair.priv, - mergePub: mergePair.pub, - purseExpiration: timestampProtocolToDb(ct.purse_expiration), - pursePriv: pursePair.priv, - pursePub: pursePair.pub, - status: PeerPushDebitStatus.Done, - timestampCreated: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - totalCost: Amounts.stringify(amountEffective), - }); - await ctx.updateTransactionMeta(tx); - }); - break; - } - case "peer-push-credit": { - const pursePair = await wex.cryptoApi.createEddsaKeypair({}); - const pursePub = pursePair.pub; - const peerPushCreditId = encodeCrock(getRandomBytes(32)); - const ctx = new PeerPushCreditTransactionContext(wex, pursePub); - const summary = parsedUri.query?.get("summary") ?? "Test"; - const amountEffectiveStr = parsedUri.query?.get("amountEffective"); - if (!amountEffectiveStr) { - throw Error("missing amountEffective option"); - } - const exchangeBaseUrl = - parsedUri.query?.get("exchangeBaseUrl") ?? - "https://exchange.demo.taler.net/"; - const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); - const trelStr = parsedUri.query?.get("tRel") ?? undefined; - let timestamp: AbsoluteTime; - if (trelStr) { - timestamp = AbsoluteTime.subtractDuraction( - AbsoluteTime.now(), - Duration.fromPrettyString(trelStr), - ); - } else { - timestamp = AbsoluteTime.now(); - } - const ct: PeerContractTerms = { - amount: Amounts.stringify(amountEffective), - purse_expiration: AbsoluteTime.toProtocolTimestamp(timestamp), - summary, - }; - const contractTermsHash = ContractTermsUtil.hashContractTerms(ct); - const contractPair = await wex.cryptoApi.createEddsaKeypair({}); - const mergePair = await wex.cryptoApi.createEddsaKeypair({}); - await wex.db.runAllStoresReadWriteTx({}, async (tx) => { - await tx.contractTerms.put({ - contractTermsRaw: ct, - h: contractTermsHash, - }); - await tx.peerPushCredit.put({ - contractPriv: contractPair.priv, - contractTermsHash, - currency: Amounts.currencyOf(amountEffective), - estimatedAmountEffective: Amounts.stringify(amountEffective), - exchangeBaseUrl, - mergePriv: mergePair.priv, - peerPushCreditId, - pursePub, - status: PeerPushCreditStatus.Done, - timestamp: timestampPreciseToDb( - AbsoluteTime.toPreciseTimestamp(timestamp), - ), - withdrawalGroupId: undefined, - }); - await ctx.updateTransactionMeta(tx); - }); - break; - } - default: { - throw Error("transaction type not supported"); - } - } + await addFakeTx(wex, parsedUri); return; } case "flag-confirm-pay-no-wait": { @@ -521,6 +238,297 @@ export async function applyDevExperiment( throw Error(`dev-experiment id not understood ${parsedUri.devExperimentId}`); } +async function addFakeTx( + wex: WalletExecutionContext, + parsedUri: DevExperimentUri, +): Promise<void> { + const txType = parsedUri.query?.get("txType") ?? "withdrawal"; + const withdrawalGroupId = encodeCrock(getRandomBytes(32)); + const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); + switch (txType) { + case "withdrawal": { + const secretSeed = encodeCrock(getRandomBytes(32)); + const reservePair = await wex.ws.cryptoApi.createEddsaKeypair({}); + const exchangeBaseUrl = + parsedUri.query?.get("exchangeBaseUrl") ?? + "https://exchange.demo.taler.net/"; + const trelStr = parsedUri.query?.get("tRel") ?? undefined; + const amountEffectiveStr = parsedUri.query?.get("amountEffective"); + if (!amountEffectiveStr) { + throw Error("missing amountEffective option"); + } + const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); + let timestamp: AbsoluteTime; + if (trelStr) { + timestamp = AbsoluteTime.subtractDuraction( + AbsoluteTime.now(), + Duration.fromPrettyString(trelStr), + ); + } else { + timestamp = AbsoluteTime.now(); + } + await wex.db.runAllStoresReadWriteTx({}, async (tx) => { + await tx.withdrawalGroups.add({ + denomSelUid: encodeCrock(getRandomBytes(32)), + reservePriv: reservePair.priv, + reservePub: reservePair.pub, + secretSeed, + status: WithdrawalGroupStatus.Done, + timestampStart: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + timestampFinish: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + withdrawalGroupId, + wgInfo: { + withdrawalType: WithdrawalRecordType.BankManual, + exchangeCreditAccounts: [ + { + paytoUri: "payto://x-taler-bank/test/test", + status: "ok", + }, + ], + }, + denomsSel: { + totalCoinValue: Amounts.stringify(amountEffective), + hasDenomWithAgeRestriction: false, + totalWithdrawCost: Amounts.stringify(amountEffective), + selectedDenoms: [], + }, + exchangeBaseUrl, + instructedAmount: Amounts.stringify(amountEffective), + effectiveWithdrawalAmount: Amounts.stringify(amountEffective), + rawWithdrawalAmount: Amounts.stringify(amountEffective), + }); + await ctx.updateTransactionMeta(tx); + }); + break; + } + case "payment": { + const proposalId = encodeCrock(getRandomBytes(32)); + const ctx = new PayMerchantTransactionContext(wex, proposalId); + const summary = parsedUri.query?.get("summary") ?? "Test"; + const amountEffectiveStr = parsedUri.query?.get("amountEffective"); + if (!amountEffectiveStr) { + throw Error("missing amountEffective option"); + } + const merchantName = + parsedUri.query?.get("merchantName") ?? "Test Merchant"; + const exchangeBaseUrl = + parsedUri.query?.get("exchangeBaseUrl") ?? + "https://exchange.demo.taler.net/"; + const merchantBaseUrl = + parsedUri.query?.get("merchantBaseUrl") ?? + "https://backend.demo.taler.net/"; + const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); + const trelStr = parsedUri.query?.get("tRel") ?? undefined; + let timestamp: AbsoluteTime; + if (trelStr) { + timestamp = AbsoluteTime.subtractDuraction( + AbsoluteTime.now(), + Duration.fromPrettyString(trelStr), + ); + } else { + timestamp = AbsoluteTime.now(); + } + const orderId = encodeCrock(getRandomBytes(8)); + const noncePair = await wex.ws.cryptoApi.createEddsaKeypair({}); + const merchantPub = encodeCrock(getRandomBytes(32)); + const ct: MerchantContractTermsV0 = { + amount: Amounts.stringify(amountEffective), + exchanges: [ + { + master_pub: encodeCrock(getRandomBytes(32)), + priority: 1, + url: exchangeBaseUrl, + }, + ], + h_wire: encodeCrock(getRandomBytes(64)), + max_fee: Amounts.stringify(Amounts.zeroOfAmount(amountEffective)), + timestamp: AbsoluteTime.toProtocolTimestamp(timestamp), + summary, + order_id: orderId, + merchant_base_url: merchantBaseUrl, + merchant_pub: merchantPub, + wire_method: "x-taler-bank", + version: MerchantContractVersion.V0, + nonce: encodeCrock(getRandomBytes(32)), + merchant: { + name: merchantName, + }, + pay_deadline: AbsoluteTime.toProtocolTimestamp(timestamp), + refund_deadline: AbsoluteTime.toProtocolTimestamp(timestamp), + wire_transfer_deadline: AbsoluteTime.toProtocolTimestamp(timestamp), + }; + const contractTermsHash = ContractTermsUtil.hashContractTerms(ct); + await wex.db.runAllStoresReadWriteTx({}, async (tx) => { + await tx.contractTerms.put({ + contractTermsRaw: ct, + h: contractTermsHash, + }); + await tx.purchases.put({ + autoRefundDeadline: undefined, + claimToken: encodeCrock(getRandomBytes(32)), + downloadSessionId: encodeCrock(getRandomBytes(32)), + orderId, + proposalId, + download: { + contractTermsHash, + contractTermsMerchantSig: encodeCrock(getRandomBytes(64)), + currency: Amounts.currencyOf(amountEffective), + }, + merchantBaseUrl, + purchaseStatus: PurchaseStatus.Done, + repurchaseProposalId: undefined, + lastSessionId: undefined, + merchantPaySig: encodeCrock(getRandomBytes(64)), + noncePriv: noncePair.priv, + noncePub: noncePair.pub, + payInfo: { + totalPayCost: Amounts.stringify(amountEffective), + }, + posConfirmation: undefined, + secretSeed: encodeCrock(getRandomBytes(32)), + timestamp: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + shared: false, + timestampFirstSuccessfulPay: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + timestampAccept: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + timestampLastRefundStatus: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + refundAmountAwaiting: undefined, + }); + await ctx.updateTransactionMeta(tx); + }); + break; + } + case "peer-push-debit": { + const pursePair = await wex.cryptoApi.createEddsaKeypair({}); + const pursePub = pursePair.pub; + const ctx = new PeerPushDebitTransactionContext(wex, pursePub); + const summary = parsedUri.query?.get("summary") ?? "Test"; + const amountEffectiveStr = parsedUri.query?.get("amountEffective"); + if (!amountEffectiveStr) { + throw Error("missing amountEffective option"); + } + const exchangeBaseUrl = + parsedUri.query?.get("exchangeBaseUrl") ?? + "https://exchange.demo.taler.net/"; + const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); + const trelStr = parsedUri.query?.get("tRel") ?? undefined; + let timestamp: AbsoluteTime; + if (trelStr) { + timestamp = AbsoluteTime.subtractDuraction( + AbsoluteTime.now(), + Duration.fromPrettyString(trelStr), + ); + } else { + timestamp = AbsoluteTime.now(); + } + const ct: PeerContractTerms = { + amount: Amounts.stringify(amountEffective), + purse_expiration: AbsoluteTime.toProtocolTimestamp(timestamp), + summary, + }; + const contractTermsHash = ContractTermsUtil.hashContractTerms(ct); + const contractPair = await wex.cryptoApi.createEddsaKeypair({}); + const mergePair = await wex.cryptoApi.createEddsaKeypair({}); + await wex.db.runAllStoresReadWriteTx({}, async (tx) => { + await tx.contractTerms.put({ + contractTermsRaw: ct, + h: contractTermsHash, + }); + await tx.peerPushDebit.put({ + amount: Amounts.stringify(amountEffective), + contractEncNonce: encodeCrock(getRandomBytes(32)), + contractPriv: contractPair.priv, + contractPub: contractPair.pub, + contractTermsHash: contractTermsHash, + exchangeBaseUrl, + mergePriv: mergePair.priv, + mergePub: mergePair.pub, + purseExpiration: timestampProtocolToDb(ct.purse_expiration), + pursePriv: pursePair.priv, + pursePub: pursePair.pub, + status: PeerPushDebitStatus.Done, + timestampCreated: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + totalCost: Amounts.stringify(amountEffective), + }); + await ctx.updateTransactionMeta(tx); + }); + break; + } + case "peer-push-credit": { + const pursePair = await wex.cryptoApi.createEddsaKeypair({}); + const pursePub = pursePair.pub; + const peerPushCreditId = encodeCrock(getRandomBytes(32)); + const ctx = new PeerPushCreditTransactionContext(wex, pursePub); + const summary = parsedUri.query?.get("summary") ?? "Test"; + const amountEffectiveStr = parsedUri.query?.get("amountEffective"); + if (!amountEffectiveStr) { + throw Error("missing amountEffective option"); + } + const exchangeBaseUrl = + parsedUri.query?.get("exchangeBaseUrl") ?? + "https://exchange.demo.taler.net/"; + const amountEffective = Amounts.parseOrThrow(amountEffectiveStr); + const trelStr = parsedUri.query?.get("tRel") ?? undefined; + let timestamp: AbsoluteTime; + if (trelStr) { + timestamp = AbsoluteTime.subtractDuraction( + AbsoluteTime.now(), + Duration.fromPrettyString(trelStr), + ); + } else { + timestamp = AbsoluteTime.now(); + } + const ct: PeerContractTerms = { + amount: Amounts.stringify(amountEffective), + purse_expiration: AbsoluteTime.toProtocolTimestamp(timestamp), + summary, + }; + const contractTermsHash = ContractTermsUtil.hashContractTerms(ct); + const contractPair = await wex.cryptoApi.createEddsaKeypair({}); + const mergePair = await wex.cryptoApi.createEddsaKeypair({}); + await wex.db.runAllStoresReadWriteTx({}, async (tx) => { + await tx.contractTerms.put({ + contractTermsRaw: ct, + h: contractTermsHash, + }); + await tx.peerPushCredit.put({ + contractPriv: contractPair.priv, + contractTermsHash, + currency: Amounts.currencyOf(amountEffective), + estimatedAmountEffective: Amounts.stringify(amountEffective), + exchangeBaseUrl, + mergePriv: mergePair.priv, + peerPushCreditId, + pursePub, + status: PeerPushCreditStatus.Done, + timestamp: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(timestamp), + ), + withdrawalGroupId: undefined, + }); + await ctx.updateTransactionMeta(tx); + }); + break; + } + default: { + throw Error("transaction type not supported"); + } + } +} + function mockResponseJson(resp: HttpResponse, respJson: any): HttpResponse { const textEncoder = new TextEncoder(); return {