commit 8b5e3eb7e42cfb4d33d7a72b506266515196a42f
parent ca45107eca3e2c35c018826edec09546934eb802
Author: Florian Dold <florian@dold.me>
Date: Wed, 3 Dec 2025 15:42:41 +0100
wallet-core: return txState in p2p prepare requests
Also refactor some state transitions
Diffstat:
6 files changed, 279 insertions(+), 238 deletions(-)
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -3683,6 +3683,11 @@ export interface PreparePeerPushCreditResponse {
transactionId: TransactionIdStr;
+ /**
+ * State of the existing or newly created transaction.
+ */
+ txState: TransactionState;
+
exchangeBaseUrl: string;
scopeInfo: ScopeInfo;
@@ -3701,6 +3706,11 @@ export interface PreparePeerPullDebitResponse {
transactionId: TransactionIdStr;
+ /**
+ * State of the existing or newly created transaction.
+ */
+ txState: TransactionState;
+
exchangeBaseUrl: string;
scopeInfo: ScopeInfo;
diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts b/packages/taler-wallet-core/src/pay-peer-common.ts
@@ -204,45 +204,6 @@ interface RecordCtx<Store extends WalletDbStoresName> {
};
}
-/** Create a new record, update its metadata and notify its creation */
-export async function recordCreate<
- Store extends WalletDbStoresName,
- ExtraStores extends WalletDbStoresArr = [],
->(
- ctx: RecordCtx<Store>,
- opts: { extraStores?: ExtraStores; label?: string },
- lambda: (
- tx: WalletDbReadWriteTransaction<
- [Store, "transactionsMeta", ...ExtraStores]
- >,
- ) => Promise<StoreType<Store>>,
-) {
- const baseStore = [ctx.store, "transactionsMeta" as const];
- const storeNames = opts.extraStores
- ? [...baseStore, ...opts.extraStores]
- : baseStore;
- await ctx.wex.db.runReadWriteTx(
- { storeNames, label: opts.label },
- async (tx) => {
- const oldTxState: TransactionState = {
- major: TransactionMajorState.None,
- };
- const rec = await lambda(tx);
- // FIXME: DbReadWriteTransaction conditional type confuse Typescript, we should simplify invariable
- await (tx[ctx.store] as any).add(rec);
- await tx.transactionsMeta.put(ctx.recordMeta(rec));
- const newTxState = ctx.recordState(rec);
- applyNotifyTransition(tx.notify, ctx.transactionId, {
- oldTxState,
- newTxState: newTxState.txState,
- balanceEffect: BalanceEffect.Any,
- oldStId: 0,
- newStId: newTxState.stId,
- });
- },
- );
-}
-
/**
* Optionally update an existing record, ignore if missing.
* If a transition occurs, update its metadata and notify.
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -55,6 +55,7 @@ import {
} from "@gnu-taler/taler-util";
import {
PendingTaskType,
+ RecordHandle,
TaskIdStr,
TaskIdentifiers,
TaskRunResult,
@@ -62,6 +63,7 @@ import {
TransitionResultType,
constructTaskIdentifier,
genericWaitForStateVal,
+ getGenericRecordHandle,
requireExchangeTosAcceptedOrThrow,
runWithClientCancellation,
} from "./common.js";
@@ -94,7 +96,6 @@ import {
import {
getMergeReserveInfo,
isPurseDeposited,
- recordCreate,
recordDelete,
recordTransition,
recordTransitionStatus,
@@ -304,6 +305,25 @@ export class PeerPullCreditTransactionContext implements TransactionContext {
};
}
+ async getRecordHandle(
+ tx: WalletDbReadWriteTransaction<["peerPullCredit", "transactionsMeta"]>,
+ ): Promise<
+ [PeerPullCreditRecord | undefined, RecordHandle<PeerPullCreditRecord>]
+ > {
+ return getGenericRecordHandle<PeerPullCreditRecord>(
+ this,
+ tx as any,
+ async () => tx.peerPullCredit.get(this.pursePub),
+ async (r) => {
+ await tx.peerPullCredit.put(r);
+ },
+ async () => tx.peerPullCredit.delete(this.pursePub),
+ (r) => computePeerPullCreditTransactionState(r),
+ (r) => r.status,
+ () => this.updateTransactionMeta(tx),
+ );
+ }
+
async deleteTransaction(): Promise<void> {
const res = await this.wex.db.runReadWriteTx(
{
@@ -1100,36 +1120,35 @@ export async function initiatePeerPullPayment(
const mergeTimestamp = TalerPreciseTimestamp.now();
const ctx = new PeerPullCreditTransactionContext(wex, pursePair.pub);
- await recordCreate(
- ctx,
- {
- extraStores: ["contractTerms"],
- label: "create-transaction-peer-pull-credit",
- },
- async (tx) => {
- await tx.contractTerms.put({
- contractTermsRaw: contractTerms,
- h: hContractTerms,
- });
- return {
- amount: req.partialContractTerms.amount,
- contractTermsHash: hContractTerms,
- exchangeBaseUrl: exchangeBaseUrl,
- pursePriv: pursePair.priv,
- pursePub: pursePair.pub,
- mergePriv: mergePair.priv,
- mergePub: mergePair.pub,
- status: PeerPullPaymentCreditStatus.PendingCreatePurse,
- mergeTimestamp: timestampPreciseToDb(mergeTimestamp),
- contractEncNonce,
- mergeReserveRowId: mergeReserveRowId,
- contractPriv: contractKeyPair.priv,
- contractPub: contractKeyPair.pub,
- withdrawalGroupId,
- estimatedAmountEffective: wi.withdrawalAmountEffective,
- };
- },
- );
+
+ await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.contractTerms.put({
+ contractTermsRaw: contractTerms,
+ h: hContractTerms,
+ });
+ const [oldRec, h] = await ctx.getRecordHandle(tx);
+ if (oldRec) {
+ throw Error("peer-pull-credit record already exists");
+ }
+ await h.update({
+ amount: req.partialContractTerms.amount,
+ contractTermsHash: hContractTerms,
+ exchangeBaseUrl: exchangeBaseUrl,
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ mergePriv: mergePair.priv,
+ mergePub: mergePair.pub,
+ status: PeerPullPaymentCreditStatus.PendingCreatePurse,
+ mergeTimestamp: timestampPreciseToDb(mergeTimestamp),
+ contractEncNonce,
+ mergeReserveRowId: mergeReserveRowId,
+ contractPriv: contractKeyPair.priv,
+ contractPub: contractKeyPair.pub,
+ withdrawalGroupId,
+ estimatedAmountEffective: wi.withdrawalAmountEffective,
+ });
+ });
+
wex.taskScheduler.startShepherdTask(ctx.taskId);
return {
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
@@ -64,11 +64,13 @@ import {
import { PreviousPayCoins, selectPeerCoins } from "./coinSelection.js";
import {
PendingTaskType,
+ RecordHandle,
TaskIdStr,
TaskRunResult,
TransactionContext,
TransitionResultType,
constructTaskIdentifier,
+ getGenericRecordHandle,
spendCoins,
} from "./common.js";
import {
@@ -85,7 +87,6 @@ import {
getTotalPeerPaymentCost,
isPurseDeposited,
queryCoinInfosForSelection,
- recordCreate,
recordDelete,
recordTransition,
recordTransitionStatus,
@@ -135,6 +136,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext {
currency: Amounts.currencyOf(rec.amount),
exchanges: [rec.exchangeBaseUrl],
});
+
updateTransactionMeta = (
tx: WalletDbReadWriteTransaction<["peerPullDebit", "transactionsMeta"]>,
) => recordUpdateMeta(this, tx);
@@ -183,6 +185,28 @@ export class PeerPullDebitTransactionContext implements TransactionContext {
};
}
+ async getRecordHandle(
+ tx: WalletDbReadWriteTransaction<["peerPullDebit", "transactionsMeta"]>,
+ ): Promise<
+ [
+ PeerPullPaymentIncomingRecord | undefined,
+ RecordHandle<PeerPullPaymentIncomingRecord>,
+ ]
+ > {
+ return getGenericRecordHandle<PeerPullPaymentIncomingRecord>(
+ this,
+ tx as any,
+ async () => tx.peerPullDebit.get(this.peerPullDebitId),
+ async (r) => {
+ await tx.peerPullDebit.put(r);
+ },
+ async () => tx.peerPullDebit.delete(this.peerPullDebitId),
+ (r) => computePeerPullDebitTransactionState(r),
+ (r) => r.status,
+ () => this.updateTransactionMeta(tx),
+ );
+ }
+
async deleteTransaction(): Promise<void> {
const res = await this.wex.db.runReadWriteTx(
{ storeNames: ["peerPullDebit", "transactionsMeta"] },
@@ -829,6 +853,7 @@ export async function preparePeerPullDebit(
contractTerms,
scopeInfo,
exchangeBaseUrl: peerPullDebitRecord.exchangeBaseUrl,
+ rec: peerPullDebitRecord,
};
},
);
@@ -841,6 +866,7 @@ export async function preparePeerPullDebit(
contractTerms: existing.contractTerms.contractTermsRaw,
scopeInfo: existing.scopeInfo,
exchangeBaseUrl: existing.exchangeBaseUrl,
+ txState: computePeerPullDebitTransactionState(existing.rec),
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullDebitId: existing.peerPullDebitRecord.peerPullDebitId,
@@ -948,42 +974,43 @@ export async function preparePeerPullDebit(
const totalAmount = await getTotalPeerPaymentCost(wex, coins);
const ctx = new PeerPullDebitTransactionContext(wex, peerPullDebitId);
- await recordCreate(
- ctx,
- {
- extraStores: ["contractTerms"],
- label: "create-transaction-peer-pull-credit",
- },
- async (tx) => {
- await tx.contractTerms.put({
- h: contractTermsHash,
- contractTermsRaw: contractTerms,
- });
- return {
- peerPullDebitId,
- contractPriv: contractPriv,
- exchangeBaseUrl: exchangeBaseUrl,
- pursePub: pursePub,
- timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- contractTermsHash,
- amount: contractTerms.amount,
- status: PeerPullDebitRecordStatus.DialogProposed,
- totalCostEstimated: Amounts.stringify(totalAmount),
- };
- },
- );
- wex.taskScheduler.startShepherdTask(ctx.taskId);
- const scopeInfo = await wex.db.runAllStoresReadOnlyTx({}, (tx) => {
- return getExchangeScopeInfo(tx, exchangeBaseUrl, currency);
+ const ret = await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.contractTerms.put({
+ h: contractTermsHash,
+ contractTermsRaw: contractTerms,
+ });
+ const [rec, h] = await ctx.getRecordHandle(tx);
+ if (rec) {
+ throw Error("peer-pull-debit record already exists");
+ }
+ const newRec: PeerPullPaymentIncomingRecord = {
+ peerPullDebitId,
+ contractPriv: contractPriv,
+ exchangeBaseUrl: exchangeBaseUrl,
+ pursePub: pursePub,
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ contractTermsHash,
+ amount: contractTerms.amount,
+ status: PeerPullDebitRecordStatus.DialogProposed,
+ totalCostEstimated: Amounts.stringify(totalAmount),
+ };
+ await h.update(newRec);
+ return {
+ newRec,
+ scopeInfo: await getExchangeScopeInfo(tx, exchangeBaseUrl, currency),
+ };
});
+ wex.taskScheduler.startShepherdTask(ctx.taskId);
+
return {
amount: contractTerms.amount,
amountEffective: Amounts.stringify(totalAmount),
amountRaw: contractTerms.amount,
contractTerms: contractTerms,
- scopeInfo,
+ scopeInfo: ret.scopeInfo,
+ txState: computePeerPullDebitTransactionState(ret.newRec),
exchangeBaseUrl,
transactionId: ctx.transactionId,
};
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -54,6 +54,7 @@ import {
} from "@gnu-taler/taler-util";
import {
PendingTaskType,
+ RecordHandle,
TaskIdStr,
TaskIdentifiers,
TaskRunResult,
@@ -61,6 +62,7 @@ import {
TransitionResultType,
constructTaskIdentifier,
genericWaitForStateVal,
+ getGenericRecordHandle,
requireExchangeTosAcceptedOrThrow,
} from "./common.js";
import {
@@ -93,7 +95,6 @@ import {
import {
getMergeReserveInfo,
isPurseMerged,
- recordCreate,
recordDelete,
recordTransition,
recordTransitionStatus,
@@ -268,6 +269,28 @@ export class PeerPushCreditTransactionContext implements TransactionContext {
};
}
+ async getRecordHandle(
+ tx: WalletDbReadWriteTransaction<["peerPushCredit", "transactionsMeta"]>,
+ ): Promise<
+ [
+ PeerPushPaymentIncomingRecord | undefined,
+ RecordHandle<PeerPushPaymentIncomingRecord>,
+ ]
+ > {
+ return getGenericRecordHandle<PeerPushPaymentIncomingRecord>(
+ this,
+ tx as any,
+ async () => tx.peerPushCredit.get(this.peerPushCreditId),
+ async (r) => {
+ await tx.peerPushCredit.put(r);
+ },
+ async () => tx.peerPushCredit.delete(this.peerPushCreditId),
+ (r) => computePeerPushCreditTransactionState(r),
+ (r) => r.status,
+ () => this.updateTransactionMeta(tx),
+ );
+ }
+
async deleteTransaction(): Promise<void> {
const res = await this.wex.db.runReadWriteTx(
{
@@ -494,6 +517,7 @@ export async function preparePeerPushCredit(
tag: TransactionType.PeerPushCredit,
peerPushCreditId: existing.existingPushInc.peerPushCreditId,
}),
+ txState: computePeerPushCreditTransactionState(existing.existingPushInc),
scopeInfo,
exchangeBaseUrl,
...getPeerCreditLimitInfo(
@@ -569,48 +593,40 @@ export async function preparePeerPushCredit(
}
const ctx = new PeerPushCreditTransactionContext(wex, peerPushCreditId);
- await recordCreate(
- ctx,
- {
- extraStores: ["contractTerms"],
- },
- async (tx) => {
- await tx.contractTerms.put({
- h: contractTermsHash,
- contractTermsRaw: dec.contractTerms,
- });
- return {
- peerPushCreditId,
- contractPriv: contractPriv,
- exchangeBaseUrl: exchangeBaseUrl,
- mergePriv: dec.mergePriv,
- pursePub: pursePub,
- timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- contractTermsHash,
- status: PeerPushCreditStatus.DialogProposed,
- withdrawalGroupId,
- currency: Amounts.currencyOf(purseStatus.balance),
- estimatedAmountEffective: Amounts.stringify(
- wi.withdrawalAmountEffective,
- ),
- };
- },
- );
+
+ const res = await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.contractTerms.put({
+ h: contractTermsHash,
+ contractTermsRaw: dec.contractTerms,
+ });
+
+ const [rec, h] = await ctx.getRecordHandle(tx);
+ if (rec) {
+ throw Error("record already exists");
+ }
+ const newRec: PeerPushPaymentIncomingRecord = {
+ peerPushCreditId,
+ contractPriv: contractPriv,
+ exchangeBaseUrl: exchangeBaseUrl,
+ mergePriv: dec.mergePriv,
+ pursePub: pursePub,
+ timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ contractTermsHash,
+ status: PeerPushCreditStatus.DialogProposed,
+ withdrawalGroupId,
+ currency: Amounts.currencyOf(purseStatus.balance),
+ estimatedAmountEffective: Amounts.stringify(wi.withdrawalAmountEffective),
+ };
+ await h.update(newRec);
+ return {
+ scopeInfo: await getExchangeScopeInfo(tx, exchangeBaseUrl, currency),
+ newRec,
+ };
+ });
+
wex.taskScheduler.startShepherdTask(ctx.taskId);
const currency = Amounts.currencyOf(wi.withdrawalAmountRaw);
- const scopeInfo = await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "exchanges",
- "exchangeDetails",
- "globalCurrencyExchanges",
- "globalCurrencyAuditors",
- ],
- },
- (tx) => getExchangeScopeInfo(tx, exchangeBaseUrl, currency),
- );
-
return {
amount: purseStatus.balance,
amountEffective: wi.withdrawalAmountEffective,
@@ -618,7 +634,8 @@ export async function preparePeerPushCredit(
contractTerms: dec.contractTerms,
transactionId: ctx.transactionId,
exchangeBaseUrl,
- scopeInfo,
+ scopeInfo: res.scopeInfo,
+ txState: computePeerPushCreditTransactionState(res.newRec),
...getPeerCreditLimitInfo(exchange, purseStatus.balance),
};
}
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -59,11 +59,13 @@ import {
} from "./coinSelection.js";
import {
PendingTaskType,
+ RecordHandle,
TaskIdStr,
TaskRunResult,
TransactionContext,
TransitionResultType,
constructTaskIdentifier,
+ getGenericRecordHandle,
runWithClientCancellation,
spendCoins,
} from "./common.js";
@@ -87,7 +89,6 @@ import {
getTotalPeerPaymentCostInTx,
isPurseMerged,
queryCoinInfosForSelection,
- recordCreate,
recordDelete,
recordTransition,
recordTransitionStatus,
@@ -199,6 +200,25 @@ export class PeerPushDebitTransactionContext implements TransactionContext {
};
}
+ async getRecordHandle(
+ tx: WalletDbReadWriteTransaction<["peerPushDebit", "transactionsMeta"]>,
+ ): Promise<
+ [PeerPushDebitRecord | undefined, RecordHandle<PeerPushDebitRecord>]
+ > {
+ return getGenericRecordHandle<PeerPushDebitRecord>(
+ this,
+ tx as any,
+ async () => tx.peerPushDebit.get(this.pursePub),
+ async (r) => {
+ await tx.peerPushDebit.put(r);
+ },
+ async () => tx.peerPushDebit.delete(this.pursePub),
+ (r) => computePeerPushDebitTransactionState(r),
+ (r) => r.status,
+ () => this.updateTransactionMeta(tx),
+ );
+ }
+
async deleteTransaction(): Promise<void> {
const res = await this.wex.db.runReadWriteTx(
{ storeNames: ["peerPushDebit", "transactionsMeta"] },
@@ -975,111 +995,98 @@ export async function initiatePeerPushDebit(
await updateWithdrawalDenomsForCurrency(wex, instructedAmount.currency);
let exchangeBaseUrl;
- await recordCreate(
- ctx,
- {
- extraStores: [
- "coinAvailability",
- "coinHistory",
- "coins",
- "contractTerms",
- "denominations",
- "exchangeDetails",
- "exchanges",
- "refreshGroups",
- "refreshSessions",
- "globalCurrencyExchanges",
- "globalCurrencyAuditors",
- ],
- },
- async (tx) => {
- const coinSelRes = await selectPeerCoinsInTx(wex, tx, {
- instructedAmount,
- // Any (single!) exchange that is in scope works.
- restrictScope: req.restrictScope,
- feesCoveredByCounterparty: false,
- });
- let coins: SelectedProspectiveCoin[] | undefined = undefined;
+ await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ const coinSelRes = await selectPeerCoinsInTx(wex, tx, {
+ instructedAmount,
+ // Any (single!) exchange that is in scope works.
+ restrictScope: req.restrictScope,
+ feesCoveredByCounterparty: false,
+ });
- switch (coinSelRes.type) {
- case "failure":
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
- {
- insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
- },
- );
- case "prospective":
- coins = coinSelRes.result.prospectiveCoins;
- break;
- case "success":
- coins = coinSelRes.result.coins;
- break;
- default:
- assertUnreachable(coinSelRes);
- }
+ let coins: SelectedProspectiveCoin[] | undefined = undefined;
- logger.trace(j2s(coinSelRes));
+ switch (coinSelRes.type) {
+ case "failure":
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ coins = coinSelRes.result.prospectiveCoins;
+ break;
+ case "success":
+ coins = coinSelRes.result.coins;
+ break;
+ default:
+ assertUnreachable(coinSelRes);
+ }
- const sel = coinSelRes.result;
+ logger.trace(j2s(coinSelRes));
- logger.trace(
- `peer debit instructed amount: ${Amounts.stringify(instructedAmount)}`,
- );
- logger.trace(
- `peer debit contract terms amount: ${Amounts.stringify(
- contractTerms.amount,
- )}`,
- );
- logger.trace(
- `peer debit deposit fees: ${Amounts.stringify(sel.totalDepositFees)}`,
- );
+ const sel = coinSelRes.result;
- const totalAmount = await getTotalPeerPaymentCostInTx(wex, tx, coins);
- const ppi: PeerPushDebitRecord = {
- amount: Amounts.stringify(instructedAmount),
- restrictScope: req.restrictScope,
- contractPriv: contractKeyPair.priv,
- contractPub: contractKeyPair.pub,
- contractTermsHash: hContractTerms,
- exchangeBaseUrl: sel.exchangeBaseUrl,
- mergePriv: mergePair.priv,
- mergePub: mergePair.pub,
- purseExpiration: timestampProtocolToDb(purseExpiration),
- pursePriv: pursePair.priv,
- pursePub: pursePair.pub,
- timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
- status: PeerPushDebitStatus.PendingCreatePurse,
- contractEncNonce,
- totalCost: Amounts.stringify(totalAmount),
- };
+ logger.trace(
+ `peer debit instructed amount: ${Amounts.stringify(instructedAmount)}`,
+ );
+ logger.trace(
+ `peer debit contract terms amount: ${Amounts.stringify(
+ contractTerms.amount,
+ )}`,
+ );
+ logger.trace(
+ `peer debit deposit fees: ${Amounts.stringify(sel.totalDepositFees)}`,
+ );
- if (coinSelRes.type === "success") {
- ppi.coinSel = {
- coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
- contributions: coinSelRes.result.coins.map((x) => x.contribution),
- };
- // FIXME: Instead of directly doing a spendCoin here,
- // we might want to mark the coins as used and spend them
- // after we've been able to create the purse.
- await spendCoins(wex, tx, {
- transactionId: ctx.transactionId,
- coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
- contributions: coinSelRes.result.coins.map((x) =>
- Amounts.parseOrThrow(x.contribution),
- ),
- refreshReason: RefreshReason.PayPeerPush,
- });
- }
- await tx.contractTerms.put({
- h: hContractTerms,
- contractTermsRaw: contractTerms,
+ const totalAmount = await getTotalPeerPaymentCostInTx(wex, tx, coins);
+ const ppi: PeerPushDebitRecord = {
+ amount: Amounts.stringify(instructedAmount),
+ restrictScope: req.restrictScope,
+ contractPriv: contractKeyPair.priv,
+ contractPub: contractKeyPair.pub,
+ contractTermsHash: hContractTerms,
+ exchangeBaseUrl: sel.exchangeBaseUrl,
+ mergePriv: mergePair.priv,
+ mergePub: mergePair.pub,
+ purseExpiration: timestampProtocolToDb(purseExpiration),
+ pursePriv: pursePair.priv,
+ pursePub: pursePair.pub,
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+ status: PeerPushDebitStatus.PendingCreatePurse,
+ contractEncNonce,
+ totalCost: Amounts.stringify(totalAmount),
+ };
+
+ if (coinSelRes.type === "success") {
+ ppi.coinSel = {
+ coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
+ contributions: coinSelRes.result.coins.map((x) => x.contribution),
+ };
+ // FIXME: Instead of directly doing a spendCoin here,
+ // we might want to mark the coins as used and spend them
+ // after we've been able to create the purse.
+ await spendCoins(wex, tx, {
+ transactionId: ctx.transactionId,
+ coinPubs: coinSelRes.result.coins.map((x) => x.coinPub),
+ contributions: coinSelRes.result.coins.map((x) =>
+ Amounts.parseOrThrow(x.contribution),
+ ),
+ refreshReason: RefreshReason.PayPeerPush,
});
- exchangeBaseUrl = coinSelRes.result.exchangeBaseUrl;
- return ppi;
- },
- );
+ }
+ await tx.contractTerms.put({
+ h: hContractTerms,
+ contractTermsRaw: contractTerms,
+ });
+ exchangeBaseUrl = coinSelRes.result.exchangeBaseUrl;
+ const [oldRec, h] = await ctx.getRecordHandle(tx);
+ if (oldRec) {
+ throw Error("record for peer-push-debit already exists");
+ }
+ await h.update(ppi);
+ });
wex.taskScheduler.startShepherdTask(ctx.taskId);