commit ae990ccbf08988014a8fad2d80eb2b529896c102
parent ef2f1c0d0628c50c6f768bc552655119b59f7687
Author: Florian Dold <florian@dold.me>
Date: Fri, 22 Aug 2025 23:47:57 +0200
wallet-core: allow faking p2p push transactions
Diffstat:
1 file changed, 121 insertions(+), 2 deletions(-)
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -34,6 +34,7 @@ import {
Logger,
MerchantContractTermsV0,
MerchantContractVersion,
+ PeerContractTerms,
RefreshReason,
TalerPreciseTimestamp,
encodeCrock,
@@ -50,15 +51,20 @@ import { PendingTaskType, constructTaskIdentifier } from "./common.js";
import {
DenomLossEventRecord,
DenomLossStatus,
+ PeerPushCreditStatus,
+ PeerPushDebitStatus,
PurchaseStatus,
RefreshGroupRecord,
RefreshOperationStatus,
WithdrawalGroupStatus,
WithdrawalRecordType,
timestampPreciseToDb,
+ timestampProtocolToDb,
} from "./db.js";
import { DenomLossTransactionContext } from "./exchanges.js";
import { PayMerchantTransactionContext } from "./pay-merchant.js";
+import { PeerPushCreditTransactionContext } from "./pay-peer-push-credit.js";
+import { PeerPushDebitTransactionContext } from "./pay-peer-push-debit.js";
import { RefreshTransactionContext } from "./refresh.js";
import { rematerializeTransactions } from "./transactions.js";
import { DevExperimentState, WalletExecutionContext } from "./wallet.js";
@@ -286,8 +292,6 @@ export async function applyDevExperiment(
}
const merchantName =
parsedUri.query?.get("merchantName") ?? "Test Merchant";
- const noncePair = await wex.ws.cryptoApi.createEddsaKeypair({});
- const merchantPub = encodeCrock(getRandomBytes(32));
const exchangeBaseUrl =
parsedUri.query?.get("exchangeBaseUrl") ??
"https://exchange.demo.taler.net/";
@@ -306,6 +310,8 @@ export async function applyDevExperiment(
timestamp = AbsoluteTime.now();
}
const orderId = encodeCrock(randomBytes(8));
+ const noncePair = await wex.ws.cryptoApi.createEddsaKeypair({});
+ const merchantPub = encodeCrock(getRandomBytes(32));
const ct: MerchantContractTermsV0 = {
amount: Amounts.stringify(amountEffective),
exchanges: [
@@ -380,6 +386,119 @@ export async function applyDevExperiment(
});
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(randomBytes(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(randomBytes(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,
+ });
+ });
+ break;
+ }
default: {
throw Error("transaction type not supported");
}