2022-06-21 12:40:12 +02:00
|
|
|
/*
|
|
|
|
This file is part of GNU Taler
|
2022-08-09 15:00:45 +02:00
|
|
|
(C) 2022 GNUnet e.V.
|
2022-06-21 12:40:12 +02:00
|
|
|
|
|
|
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
|
|
|
import {
|
2022-08-24 11:11:02 +02:00
|
|
|
AcceptPeerPullPaymentRequest,
|
2022-09-16 16:06:55 +02:00
|
|
|
AcceptPeerPullPaymentResponse,
|
2022-07-12 17:41:14 +02:00
|
|
|
AcceptPeerPushPaymentRequest,
|
2022-09-16 16:06:55 +02:00
|
|
|
AcceptPeerPushPaymentResponse,
|
2022-09-05 12:55:46 +02:00
|
|
|
AgeCommitmentProof,
|
2022-09-16 16:24:47 +02:00
|
|
|
AmountJson,
|
|
|
|
Amounts,
|
2022-07-12 17:41:14 +02:00
|
|
|
AmountString,
|
|
|
|
buildCodecForObject,
|
2022-08-24 11:11:02 +02:00
|
|
|
CheckPeerPullPaymentRequest,
|
|
|
|
CheckPeerPullPaymentResponse,
|
2022-07-12 17:41:14 +02:00
|
|
|
CheckPeerPushPaymentRequest,
|
|
|
|
CheckPeerPushPaymentResponse,
|
|
|
|
Codec,
|
|
|
|
codecForAmountString,
|
|
|
|
codecForAny,
|
2022-09-16 16:24:47 +02:00
|
|
|
codecForExchangeGetContractResponse,
|
2022-10-15 11:52:07 +02:00
|
|
|
CoinStatus,
|
2022-09-16 16:24:47 +02:00
|
|
|
constructPayPullUri,
|
2022-08-09 15:00:45 +02:00
|
|
|
constructPayPushUri,
|
2022-07-12 17:41:14 +02:00
|
|
|
ContractTermsUtil,
|
|
|
|
decodeCrock,
|
|
|
|
eddsaGetPublic,
|
|
|
|
encodeCrock,
|
2022-08-24 11:11:02 +02:00
|
|
|
ExchangePurseDeposits,
|
2022-07-12 17:41:14 +02:00
|
|
|
ExchangePurseMergeRequest,
|
2022-08-23 11:29:45 +02:00
|
|
|
ExchangeReservePurseRequest,
|
2022-08-09 15:00:45 +02:00
|
|
|
getRandomBytes,
|
2022-08-23 11:29:45 +02:00
|
|
|
InitiatePeerPullPaymentRequest,
|
|
|
|
InitiatePeerPullPaymentResponse,
|
2022-06-21 12:40:12 +02:00
|
|
|
InitiatePeerPushPaymentRequest,
|
2022-07-12 17:41:14 +02:00
|
|
|
InitiatePeerPushPaymentResponse,
|
2022-06-21 12:40:12 +02:00
|
|
|
j2s,
|
2022-07-12 17:41:14 +02:00
|
|
|
Logger,
|
2022-08-24 11:11:02 +02:00
|
|
|
parsePayPullUri,
|
2022-08-09 15:00:45 +02:00
|
|
|
parsePayPushUri,
|
2023-01-06 11:08:45 +01:00
|
|
|
PayPeerInsufficientBalanceDetails,
|
2022-11-02 17:02:42 +01:00
|
|
|
PeerContractTerms,
|
2022-11-08 17:00:34 +01:00
|
|
|
PreparePeerPullPaymentRequest,
|
|
|
|
PreparePeerPullPaymentResponse,
|
|
|
|
PreparePeerPushPaymentRequest,
|
|
|
|
PreparePeerPushPaymentResponse,
|
2022-08-24 11:11:02 +02:00
|
|
|
RefreshReason,
|
2022-07-12 17:41:14 +02:00
|
|
|
strcmp,
|
2023-01-06 13:55:08 +01:00
|
|
|
TalerErrorCode,
|
2022-06-21 12:40:12 +02:00
|
|
|
TalerProtocolTimestamp,
|
2022-09-16 16:06:55 +02:00
|
|
|
TransactionType,
|
2022-06-21 12:40:12 +02:00
|
|
|
UnblindedSignature,
|
2022-09-16 16:24:47 +02:00
|
|
|
WalletAccountMergeFlags,
|
2022-06-21 12:40:12 +02:00
|
|
|
} from "@gnu-taler/taler-util";
|
2023-01-12 15:11:32 +01:00
|
|
|
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
|
2022-07-12 17:41:14 +02:00
|
|
|
import {
|
2023-01-13 01:45:33 +01:00
|
|
|
DenominationRecord,
|
2022-11-02 17:02:42 +01:00
|
|
|
OperationStatus,
|
|
|
|
PeerPullPaymentIncomingStatus,
|
2023-01-12 15:11:32 +01:00
|
|
|
PeerPushPaymentCoinSelection,
|
2022-11-02 17:02:42 +01:00
|
|
|
PeerPushPaymentIncomingRecord,
|
|
|
|
PeerPushPaymentInitiationStatus,
|
2022-10-15 11:52:07 +02:00
|
|
|
ReserveRecord,
|
2022-08-24 11:11:02 +02:00
|
|
|
WalletStoresV1,
|
2022-10-15 11:52:07 +02:00
|
|
|
WithdrawalGroupStatus,
|
2022-09-16 16:24:47 +02:00
|
|
|
WithdrawalRecordType,
|
2022-07-12 17:41:14 +02:00
|
|
|
} from "../db.js";
|
2023-01-06 13:55:08 +01:00
|
|
|
import { TalerError } from "../errors.js";
|
2022-06-21 12:40:12 +02:00
|
|
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
2023-01-12 15:11:32 +01:00
|
|
|
import {
|
|
|
|
makeTransactionId,
|
|
|
|
runOperationWithErrorReporting,
|
|
|
|
spendCoins,
|
|
|
|
} from "../operations/common.js";
|
2022-09-16 16:06:55 +02:00
|
|
|
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
2022-07-12 17:41:14 +02:00
|
|
|
import { checkDbInvariant } from "../util/invariants.js";
|
2022-08-24 11:11:02 +02:00
|
|
|
import { GetReadOnlyAccess } from "../util/query.js";
|
2023-01-12 15:11:32 +01:00
|
|
|
import {
|
|
|
|
OperationAttemptResult,
|
|
|
|
OperationAttemptResultType,
|
|
|
|
RetryTags,
|
|
|
|
} from "../util/retries.js";
|
2023-01-06 13:55:08 +01:00
|
|
|
import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
|
2022-09-16 16:06:55 +02:00
|
|
|
import { updateExchangeFromUrl } from "./exchanges.js";
|
2023-01-13 01:45:33 +01:00
|
|
|
import { getTotalRefreshCost } from "./refresh.js";
|
2022-09-16 16:06:55 +02:00
|
|
|
import { internalCreateWithdrawalGroup } from "./withdraw.js";
|
2022-06-21 12:40:12 +02:00
|
|
|
|
|
|
|
const logger = new Logger("operations/peer-to-peer.ts");
|
|
|
|
|
2023-01-12 15:11:32 +01:00
|
|
|
export interface PeerCoinSelectionDetails {
|
2022-06-21 12:40:12 +02:00
|
|
|
exchangeBaseUrl: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Info of Coins that were selected.
|
|
|
|
*/
|
|
|
|
coins: {
|
|
|
|
coinPub: string;
|
|
|
|
coinPriv: string;
|
|
|
|
contribution: AmountString;
|
|
|
|
denomPubHash: string;
|
|
|
|
denomSig: UnblindedSignature;
|
2022-09-05 12:55:46 +02:00
|
|
|
ageCommitmentProof: AgeCommitmentProof | undefined;
|
2022-06-21 12:40:12 +02:00
|
|
|
}[];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* How much of the deposit fees is the customer paying?
|
|
|
|
*/
|
|
|
|
depositFees: AmountJson;
|
|
|
|
}
|
|
|
|
|
2023-01-12 15:11:32 +01:00
|
|
|
/**
|
|
|
|
* Information about a selected coin for peer to peer payments.
|
|
|
|
*/
|
2022-06-21 12:40:12 +02:00
|
|
|
interface CoinInfo {
|
|
|
|
/**
|
|
|
|
* Public key of the coin.
|
|
|
|
*/
|
|
|
|
coinPub: string;
|
|
|
|
|
|
|
|
coinPriv: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deposit fee for the coin.
|
|
|
|
*/
|
|
|
|
feeDeposit: AmountJson;
|
|
|
|
|
|
|
|
value: AmountJson;
|
|
|
|
|
|
|
|
denomPubHash: string;
|
|
|
|
|
|
|
|
denomSig: UnblindedSignature;
|
2022-09-05 12:55:46 +02:00
|
|
|
|
2022-09-16 16:20:47 +02:00
|
|
|
maxAge: number;
|
2023-01-12 15:11:32 +01:00
|
|
|
|
2022-09-16 16:20:47 +02:00
|
|
|
ageCommitmentProof?: AgeCommitmentProof;
|
2022-06-21 12:40:12 +02:00
|
|
|
}
|
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
export type SelectPeerCoinsResult =
|
2023-01-12 15:11:32 +01:00
|
|
|
| { type: "success"; result: PeerCoinSelectionDetails }
|
2023-01-06 11:08:45 +01:00
|
|
|
| {
|
|
|
|
type: "failure";
|
2023-01-06 13:55:08 +01:00
|
|
|
insufficientBalanceDetails: PayPeerInsufficientBalanceDetails;
|
2023-01-06 11:08:45 +01:00
|
|
|
};
|
|
|
|
|
2023-01-12 15:11:32 +01:00
|
|
|
export async function queryCoinInfosForSelection(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
csel: PeerPushPaymentCoinSelection,
|
|
|
|
): Promise<SpendCoinDetails[]> {
|
|
|
|
let infos: SpendCoinDetails[] = [];
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => [x.coins, x.denominations])
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
for (let i = 0; i < csel.coinPubs.length; i++) {
|
|
|
|
const coin = await tx.coins.get(csel.coinPubs[i]);
|
|
|
|
if (!coin) {
|
|
|
|
throw Error("coin not found anymore");
|
|
|
|
}
|
|
|
|
const denom = await ws.getDenomInfo(
|
|
|
|
ws,
|
|
|
|
tx,
|
|
|
|
coin.exchangeBaseUrl,
|
|
|
|
coin.denomPubHash,
|
|
|
|
);
|
|
|
|
if (!denom) {
|
|
|
|
throw Error("denom for coin not found anymore");
|
|
|
|
}
|
|
|
|
infos.push({
|
|
|
|
coinPriv: coin.coinPriv,
|
|
|
|
coinPub: coin.coinPub,
|
|
|
|
denomPubHash: coin.denomPubHash,
|
|
|
|
denomSig: coin.denomSig,
|
|
|
|
ageCommitmentProof: coin.ageCommitmentProof,
|
|
|
|
contribution: csel.contributions[i],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return infos;
|
|
|
|
}
|
|
|
|
|
2022-08-24 11:11:02 +02:00
|
|
|
export async function selectPeerCoins(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
tx: GetReadOnlyAccess<{
|
|
|
|
exchanges: typeof WalletStoresV1.exchanges;
|
|
|
|
denominations: typeof WalletStoresV1.denominations;
|
|
|
|
coins: typeof WalletStoresV1.coins;
|
2023-01-06 13:55:08 +01:00
|
|
|
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
|
|
|
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
2022-08-24 11:11:02 +02:00
|
|
|
}>,
|
|
|
|
instructedAmount: AmountJson,
|
2023-01-06 11:08:45 +01:00
|
|
|
): Promise<SelectPeerCoinsResult> {
|
2022-08-24 11:11:02 +02:00
|
|
|
const exchanges = await tx.exchanges.iter().toArray();
|
2023-01-06 13:55:08 +01:00
|
|
|
const exchangeFeeGap: { [url: string]: AmountJson } = {};
|
|
|
|
const currency = Amounts.currencyOf(instructedAmount);
|
2022-08-24 11:11:02 +02:00
|
|
|
for (const exch of exchanges) {
|
2023-01-06 13:55:08 +01:00
|
|
|
if (exch.detailsPointer?.currency !== currency) {
|
2022-08-24 11:11:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const coins = (
|
|
|
|
await tx.coins.indexes.byBaseUrl.getAll(exch.baseUrl)
|
|
|
|
).filter((x) => x.status === CoinStatus.Fresh);
|
|
|
|
const coinInfos: CoinInfo[] = [];
|
|
|
|
for (const coin of coins) {
|
|
|
|
const denom = await ws.getDenomInfo(
|
|
|
|
ws,
|
|
|
|
tx,
|
|
|
|
coin.exchangeBaseUrl,
|
|
|
|
coin.denomPubHash,
|
|
|
|
);
|
|
|
|
if (!denom) {
|
|
|
|
throw Error("denom not found");
|
|
|
|
}
|
|
|
|
coinInfos.push({
|
|
|
|
coinPub: coin.coinPub,
|
2022-11-02 17:42:14 +01:00
|
|
|
feeDeposit: Amounts.parseOrThrow(denom.feeDeposit),
|
|
|
|
value: Amounts.parseOrThrow(denom.value),
|
2022-08-24 11:11:02 +02:00
|
|
|
denomPubHash: denom.denomPubHash,
|
|
|
|
coinPriv: coin.coinPriv,
|
|
|
|
denomSig: coin.denomSig,
|
2022-09-16 16:20:47 +02:00
|
|
|
maxAge: coin.maxAge,
|
2022-09-05 12:55:46 +02:00
|
|
|
ageCommitmentProof: coin.ageCommitmentProof,
|
2022-08-24 11:11:02 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
if (coinInfos.length === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
coinInfos.sort(
|
|
|
|
(o1, o2) =>
|
|
|
|
-Amounts.cmp(o1.value, o2.value) ||
|
|
|
|
strcmp(o1.denomPubHash, o2.denomPubHash),
|
|
|
|
);
|
2023-01-06 13:55:08 +01:00
|
|
|
let amountAcc = Amounts.zeroOfCurrency(currency);
|
|
|
|
let depositFeesAcc = Amounts.zeroOfCurrency(currency);
|
2022-08-24 11:11:02 +02:00
|
|
|
const resCoins: {
|
|
|
|
coinPub: string;
|
|
|
|
coinPriv: string;
|
|
|
|
contribution: AmountString;
|
|
|
|
denomPubHash: string;
|
|
|
|
denomSig: UnblindedSignature;
|
2022-09-05 12:55:46 +02:00
|
|
|
ageCommitmentProof: AgeCommitmentProof | undefined;
|
2022-08-24 11:11:02 +02:00
|
|
|
}[] = [];
|
2023-01-06 13:55:08 +01:00
|
|
|
let lastDepositFee = Amounts.zeroOfCurrency(currency);
|
2022-08-24 11:11:02 +02:00
|
|
|
for (const coin of coinInfos) {
|
|
|
|
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
|
2022-12-06 19:41:32 +01:00
|
|
|
break;
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
|
|
|
const gap = Amounts.add(
|
|
|
|
coin.feeDeposit,
|
|
|
|
Amounts.sub(instructedAmount, amountAcc).amount,
|
|
|
|
).amount;
|
|
|
|
const contrib = Amounts.min(gap, coin.value);
|
|
|
|
amountAcc = Amounts.add(
|
|
|
|
amountAcc,
|
|
|
|
Amounts.sub(contrib, coin.feeDeposit).amount,
|
|
|
|
).amount;
|
|
|
|
depositFeesAcc = Amounts.add(depositFeesAcc, coin.feeDeposit).amount;
|
|
|
|
resCoins.push({
|
|
|
|
coinPriv: coin.coinPriv,
|
|
|
|
coinPub: coin.coinPub,
|
|
|
|
contribution: Amounts.stringify(contrib),
|
|
|
|
denomPubHash: coin.denomPubHash,
|
|
|
|
denomSig: coin.denomSig,
|
2022-09-05 12:55:46 +02:00
|
|
|
ageCommitmentProof: coin.ageCommitmentProof,
|
2022-08-24 11:11:02 +02:00
|
|
|
});
|
2023-01-06 13:55:08 +01:00
|
|
|
lastDepositFee = coin.feeDeposit;
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
2022-12-06 19:41:32 +01:00
|
|
|
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
|
2023-01-12 15:11:32 +01:00
|
|
|
const res: PeerCoinSelectionDetails = {
|
2022-12-06 19:41:32 +01:00
|
|
|
exchangeBaseUrl: exch.baseUrl,
|
|
|
|
coins: resCoins,
|
|
|
|
depositFees: depositFeesAcc,
|
|
|
|
};
|
2023-01-06 11:08:45 +01:00
|
|
|
return { type: "success", result: res };
|
2022-12-06 19:41:32 +01:00
|
|
|
}
|
2023-01-06 13:55:08 +01:00
|
|
|
const diff = Amounts.sub(instructedAmount, amountAcc).amount;
|
|
|
|
exchangeFeeGap[exch.baseUrl] = Amounts.add(lastDepositFee, diff).amount;
|
|
|
|
|
2022-08-24 11:11:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
2023-01-06 13:55:08 +01:00
|
|
|
// We were unable to select coins.
|
|
|
|
// Now we need to produce error details.
|
|
|
|
|
|
|
|
const infoGeneral = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
|
|
|
|
currency,
|
|
|
|
});
|
|
|
|
|
|
|
|
const perExchange: PayPeerInsufficientBalanceDetails["perExchange"] = {};
|
|
|
|
|
|
|
|
for (const exch of exchanges) {
|
|
|
|
if (exch.detailsPointer?.currency !== currency) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const infoExchange = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
|
|
|
|
currency,
|
|
|
|
restrictExchangeTo: exch.baseUrl,
|
|
|
|
});
|
|
|
|
let gap = exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
|
|
|
|
if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
|
|
|
|
// Show fee gap only if we should've been able to pay with the material amount
|
|
|
|
gap = Amounts.zeroOfAmount(currency);
|
|
|
|
}
|
|
|
|
perExchange[exch.baseUrl] = {
|
|
|
|
balanceAvailable: Amounts.stringify(infoExchange.balanceAvailable),
|
|
|
|
balanceMaterial: Amounts.stringify(infoExchange.balanceMaterial),
|
|
|
|
feeGapEstimate: Amounts.stringify(gap),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const errDetails: PayPeerInsufficientBalanceDetails = {
|
|
|
|
amountRequested: Amounts.stringify(instructedAmount),
|
|
|
|
balanceAvailable: Amounts.stringify(infoGeneral.balanceAvailable),
|
|
|
|
balanceMaterial: Amounts.stringify(infoGeneral.balanceMaterial),
|
|
|
|
perExchange,
|
|
|
|
};
|
|
|
|
|
|
|
|
return { type: "failure", insufficientBalanceDetails: errDetails };
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
|
|
|
|
2023-01-13 01:45:33 +01:00
|
|
|
export async function getTotalPeerPaymentCost(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
pcs: PeerCoinSelectionDetails,
|
|
|
|
): Promise<AmountJson> {
|
|
|
|
return ws.db
|
|
|
|
.mktx((x) => [x.coins, x.denominations])
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
const costs: AmountJson[] = [];
|
|
|
|
for (let i = 0; i < pcs.coins.length; i++) {
|
|
|
|
const coin = await tx.coins.get(pcs.coins[i].coinPub);
|
|
|
|
if (!coin) {
|
|
|
|
throw Error("can't calculate payment cost, coin not found");
|
|
|
|
}
|
|
|
|
const denom = await tx.denominations.get([
|
|
|
|
coin.exchangeBaseUrl,
|
|
|
|
coin.denomPubHash,
|
|
|
|
]);
|
|
|
|
if (!denom) {
|
|
|
|
throw Error(
|
|
|
|
"can't calculate payment cost, denomination for coin not found",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
|
|
|
.iter(coin.exchangeBaseUrl)
|
|
|
|
.filter((x) =>
|
|
|
|
Amounts.isSameCurrency(
|
|
|
|
DenominationRecord.getValue(x),
|
|
|
|
pcs.coins[i].contribution,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
const amountLeft = Amounts.sub(
|
|
|
|
DenominationRecord.getValue(denom),
|
|
|
|
pcs.coins[i].contribution,
|
|
|
|
).amount;
|
|
|
|
const refreshCost = getTotalRefreshCost(
|
|
|
|
allDenoms,
|
|
|
|
DenominationRecord.toDenomInfo(denom),
|
|
|
|
amountLeft,
|
|
|
|
);
|
|
|
|
costs.push(Amounts.parseOrThrow(pcs.coins[i].contribution));
|
|
|
|
costs.push(refreshCost);
|
|
|
|
}
|
|
|
|
const zero = Amounts.zeroOfAmount(pcs.coins[0].contribution);
|
|
|
|
return Amounts.sum([zero, ...costs]).amount;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:00:34 +01:00
|
|
|
export async function preparePeerPushPayment(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: PreparePeerPushPaymentRequest,
|
|
|
|
): Promise<PreparePeerPushPaymentResponse> {
|
2023-01-13 01:45:33 +01:00
|
|
|
const instructedAmount = Amounts.parseOrThrow(req.amount);
|
|
|
|
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
|
|
|
.mktx((x) => [
|
|
|
|
x.exchanges,
|
|
|
|
x.contractTerms,
|
|
|
|
x.coins,
|
|
|
|
x.coinAvailability,
|
|
|
|
x.denominations,
|
|
|
|
x.refreshGroups,
|
|
|
|
x.peerPushPaymentInitiations,
|
|
|
|
])
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
|
|
|
return selRes;
|
|
|
|
});
|
|
|
|
if (coinSelRes.type === "failure") {
|
|
|
|
throw TalerError.fromDetail(
|
|
|
|
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
|
|
|
{
|
|
|
|
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const totalAmount = await getTotalPeerPaymentCost(ws, coinSelRes.result);
|
2022-11-08 17:00:34 +01:00
|
|
|
return {
|
2023-01-13 01:45:33 +01:00
|
|
|
amountEffective: Amounts.stringify(totalAmount),
|
2022-11-08 17:00:34 +01:00
|
|
|
amountRaw: req.amount,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-12 16:57:51 +01:00
|
|
|
export async function processPeerPushInitiation(
|
2023-01-12 15:11:32 +01:00
|
|
|
ws: InternalWalletState,
|
|
|
|
pursePub: string,
|
|
|
|
): Promise<OperationAttemptResult> {
|
|
|
|
const peerPushInitiation = await ws.db
|
|
|
|
.mktx((x) => [x.peerPushPaymentInitiations])
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
return tx.peerPushPaymentInitiations.get(pursePub);
|
|
|
|
});
|
|
|
|
if (!peerPushInitiation) {
|
|
|
|
throw Error("peer push payment not found");
|
|
|
|
}
|
|
|
|
|
|
|
|
const purseExpiration = peerPushInitiation.purseExpiration;
|
|
|
|
const hContractTerms = peerPushInitiation.contractTermsHash;
|
|
|
|
|
|
|
|
const purseSigResp = await ws.cryptoApi.signPurseCreation({
|
|
|
|
hContractTerms,
|
|
|
|
mergePub: peerPushInitiation.mergePub,
|
|
|
|
minAge: 0,
|
|
|
|
purseAmount: peerPushInitiation.amount,
|
|
|
|
purseExpiration,
|
|
|
|
pursePriv: peerPushInitiation.pursePriv,
|
|
|
|
});
|
|
|
|
|
|
|
|
const coins = await queryCoinInfosForSelection(
|
|
|
|
ws,
|
|
|
|
peerPushInitiation.coinSel,
|
|
|
|
);
|
|
|
|
|
|
|
|
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
|
|
|
exchangeBaseUrl: peerPushInitiation.exchangeBaseUrl,
|
|
|
|
pursePub: peerPushInitiation.pursePub,
|
|
|
|
coins,
|
|
|
|
});
|
|
|
|
|
|
|
|
const econtractResp = await ws.cryptoApi.encryptContractForMerge({
|
|
|
|
contractTerms: peerPushInitiation.contractTerms,
|
|
|
|
mergePriv: peerPushInitiation.mergePriv,
|
|
|
|
pursePriv: peerPushInitiation.pursePriv,
|
|
|
|
pursePub: peerPushInitiation.pursePub,
|
|
|
|
contractPriv: peerPushInitiation.contractPriv,
|
|
|
|
contractPub: peerPushInitiation.contractPub,
|
|
|
|
});
|
|
|
|
|
|
|
|
const createPurseUrl = new URL(
|
|
|
|
`purses/${peerPushInitiation.pursePub}/create`,
|
|
|
|
peerPushInitiation.exchangeBaseUrl,
|
|
|
|
);
|
|
|
|
|
|
|
|
const httpResp = await ws.http.postJson(createPurseUrl.href, {
|
|
|
|
amount: peerPushInitiation.amount,
|
|
|
|
merge_pub: peerPushInitiation.mergePub,
|
|
|
|
purse_sig: purseSigResp.sig,
|
|
|
|
h_contract_terms: hContractTerms,
|
|
|
|
purse_expiration: purseExpiration,
|
|
|
|
deposits: depositSigsResp.deposits,
|
|
|
|
min_age: 0,
|
|
|
|
econtract: econtractResp.econtract,
|
|
|
|
});
|
|
|
|
|
|
|
|
const resp = await httpResp.json();
|
|
|
|
|
|
|
|
logger.info(`resp: ${j2s(resp)}`);
|
|
|
|
|
|
|
|
if (httpResp.status !== 200) {
|
|
|
|
throw Error("got error response from exchange");
|
|
|
|
}
|
|
|
|
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => [x.peerPushPaymentInitiations])
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
const ppi = await tx.peerPushPaymentInitiations.get(pursePub);
|
|
|
|
if (!ppi) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ppi.status = PeerPushPaymentInitiationStatus.PurseCreated;
|
2023-01-12 16:57:51 +01:00
|
|
|
await tx.peerPushPaymentInitiations.put(ppi);
|
2023-01-12 15:11:32 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
type: OperationAttemptResultType.Finished,
|
|
|
|
result: undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initiate sending a peer-to-peer push payment.
|
|
|
|
*/
|
2023-01-12 16:57:51 +01:00
|
|
|
export async function initiatePeerPushPayment(
|
2022-06-21 12:40:12 +02:00
|
|
|
ws: InternalWalletState,
|
|
|
|
req: InitiatePeerPushPaymentRequest,
|
|
|
|
): Promise<InitiatePeerPushPaymentResponse> {
|
2022-11-08 17:00:34 +01:00
|
|
|
const instructedAmount = Amounts.parseOrThrow(
|
|
|
|
req.partialContractTerms.amount,
|
|
|
|
);
|
|
|
|
const purseExpiration = req.partialContractTerms.purse_expiration;
|
|
|
|
const contractTerms = req.partialContractTerms;
|
2022-08-24 22:17:19 +02:00
|
|
|
|
|
|
|
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
|
|
|
|
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
|
|
|
|
|
|
|
|
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
|
|
|
|
|
2023-01-12 15:11:32 +01:00
|
|
|
const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
|
2023-01-06 11:08:45 +01:00
|
|
|
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
2022-09-13 13:25:41 +02:00
|
|
|
.mktx((x) => [
|
|
|
|
x.exchanges,
|
2022-11-02 17:02:42 +01:00
|
|
|
x.contractTerms,
|
2022-09-13 13:25:41 +02:00
|
|
|
x.coins,
|
2022-09-16 16:20:47 +02:00
|
|
|
x.coinAvailability,
|
2022-09-13 13:25:41 +02:00
|
|
|
x.denominations,
|
|
|
|
x.refreshGroups,
|
|
|
|
x.peerPushPaymentInitiations,
|
|
|
|
])
|
2022-08-24 11:11:02 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
2023-01-06 11:08:45 +01:00
|
|
|
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
|
|
|
if (selRes.type === "failure") {
|
|
|
|
return selRes;
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
const sel = selRes.result;
|
|
|
|
|
2022-09-14 20:34:37 +02:00
|
|
|
await spendCoins(ws, tx, {
|
2022-10-14 22:47:11 +02:00
|
|
|
allocationId: `txn:peer-push-debit:${pursePair.pub}`,
|
2022-09-14 20:34:37 +02:00
|
|
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
|
|
|
contributions: sel.coins.map((x) =>
|
|
|
|
Amounts.parseOrThrow(x.contribution),
|
|
|
|
),
|
|
|
|
refreshReason: RefreshReason.PayPeerPush,
|
|
|
|
});
|
2022-08-24 11:11:02 +02:00
|
|
|
|
2022-08-24 22:17:19 +02:00
|
|
|
await tx.peerPushPaymentInitiations.add({
|
|
|
|
amount: Amounts.stringify(instructedAmount),
|
2023-01-12 15:11:32 +01:00
|
|
|
contractPriv: contractKeyPair.priv,
|
|
|
|
contractPub: contractKeyPair.pub,
|
2022-11-02 17:02:42 +01:00
|
|
|
contractTermsHash: hContractTerms,
|
2022-08-24 22:17:19 +02:00
|
|
|
exchangeBaseUrl: sel.exchangeBaseUrl,
|
|
|
|
mergePriv: mergePair.priv,
|
|
|
|
mergePub: mergePair.pub,
|
|
|
|
purseExpiration: purseExpiration,
|
|
|
|
pursePriv: pursePair.priv,
|
|
|
|
pursePub: pursePair.pub,
|
|
|
|
timestampCreated: TalerProtocolTimestamp.now(),
|
2023-01-12 15:11:32 +01:00
|
|
|
status: PeerPushPaymentInitiationStatus.Initiated,
|
|
|
|
contractTerms: contractTerms,
|
|
|
|
coinSel: {
|
|
|
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
|
|
|
contributions: sel.coins.map((x) => x.contribution),
|
|
|
|
},
|
2022-11-02 17:02:42 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
await tx.contractTerms.put({
|
|
|
|
h: hContractTerms,
|
|
|
|
contractTermsRaw: contractTerms,
|
2022-08-24 22:17:19 +02:00
|
|
|
});
|
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
return selRes;
|
2022-06-21 12:40:12 +02:00
|
|
|
});
|
2022-09-05 12:55:46 +02:00
|
|
|
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
|
2022-06-21 12:40:12 +02:00
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
if (coinSelRes.type !== "success") {
|
2023-01-06 13:55:08 +01:00
|
|
|
throw TalerError.fromDetail(
|
|
|
|
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
|
|
|
{
|
|
|
|
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
|
|
|
},
|
|
|
|
);
|
2022-06-21 12:40:12 +02:00
|
|
|
}
|
|
|
|
|
2023-01-12 15:11:32 +01:00
|
|
|
await runOperationWithErrorReporting(
|
|
|
|
ws,
|
|
|
|
RetryTags.byPeerPushPaymentInitiationPursePub(pursePair.pub),
|
|
|
|
async () => {
|
2023-01-12 16:57:51 +01:00
|
|
|
return await processPeerPushInitiation(ws, pursePair.pub);
|
2023-01-12 15:11:32 +01:00
|
|
|
},
|
2022-06-21 12:40:12 +02:00
|
|
|
);
|
|
|
|
|
2022-07-12 17:41:14 +02:00
|
|
|
return {
|
2023-01-12 15:11:32 +01:00
|
|
|
contractPriv: contractKeyPair.priv,
|
2022-07-12 17:41:14 +02:00
|
|
|
mergePriv: mergePair.priv,
|
|
|
|
pursePub: pursePair.pub,
|
2023-01-06 11:08:45 +01:00
|
|
|
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
2022-08-09 15:00:45 +02:00
|
|
|
talerUri: constructPayPushUri({
|
2023-01-06 11:08:45 +01:00
|
|
|
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
2023-01-12 15:11:32 +01:00
|
|
|
contractPriv: contractKeyPair.priv,
|
2022-08-09 15:00:45 +02:00
|
|
|
}),
|
2022-10-14 22:56:29 +02:00
|
|
|
transactionId: makeTransactionId(
|
|
|
|
TransactionType.PeerPushDebit,
|
|
|
|
pursePair.pub,
|
|
|
|
),
|
2022-07-12 17:41:14 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ExchangePurseStatus {
|
|
|
|
balance: AmountString;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
|
|
|
|
buildCodecForObject<ExchangePurseStatus>()
|
|
|
|
.property("balance", codecForAmountString())
|
|
|
|
.build("ExchangePurseStatus");
|
|
|
|
|
|
|
|
export async function checkPeerPushPayment(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: CheckPeerPushPaymentRequest,
|
|
|
|
): Promise<CheckPeerPushPaymentResponse> {
|
2022-08-09 15:00:45 +02:00
|
|
|
// FIXME: Check if existing record exists!
|
2022-07-12 17:41:14 +02:00
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
const uri = parsePayPushUri(req.talerUri);
|
2022-07-12 17:41:14 +02:00
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
if (!uri) {
|
|
|
|
throw Error("got invalid taler://pay-push URI");
|
|
|
|
}
|
2022-07-12 17:41:14 +02:00
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
const exchangeBaseUrl = uri.exchangeBaseUrl;
|
2022-08-24 21:07:09 +02:00
|
|
|
|
|
|
|
await updateExchangeFromUrl(ws, exchangeBaseUrl);
|
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
const contractPriv = uri.contractPriv;
|
|
|
|
const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv)));
|
2022-07-12 17:41:14 +02:00
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
|
2022-07-12 17:41:14 +02:00
|
|
|
|
|
|
|
const contractHttpResp = await ws.http.get(getContractUrl.href);
|
|
|
|
|
|
|
|
const contractResp = await readSuccessResponseJsonOrThrow(
|
|
|
|
contractHttpResp,
|
|
|
|
codecForExchangeGetContractResponse(),
|
|
|
|
);
|
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
const pursePub = contractResp.purse_pub;
|
|
|
|
|
2022-07-12 17:41:14 +02:00
|
|
|
const dec = await ws.cryptoApi.decryptContractForMerge({
|
|
|
|
ciphertext: contractResp.econtract,
|
2022-08-09 15:00:45 +02:00
|
|
|
contractPriv: contractPriv,
|
|
|
|
pursePub: pursePub,
|
2022-07-12 17:41:14 +02:00
|
|
|
});
|
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl);
|
|
|
|
|
|
|
|
const purseHttpResp = await ws.http.get(getPurseUrl.href);
|
|
|
|
|
|
|
|
const purseStatus = await readSuccessResponseJsonOrThrow(
|
|
|
|
purseHttpResp,
|
|
|
|
codecForExchangePurseStatus(),
|
|
|
|
);
|
|
|
|
|
|
|
|
const peerPushPaymentIncomingId = encodeCrock(getRandomBytes(32));
|
|
|
|
|
2022-11-02 17:02:42 +01:00
|
|
|
const contractTermsHash = ContractTermsUtil.hashContractTerms(
|
|
|
|
dec.contractTerms,
|
|
|
|
);
|
|
|
|
|
2022-07-12 17:41:14 +02:00
|
|
|
await ws.db
|
2022-11-02 17:02:42 +01:00
|
|
|
.mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
|
2022-07-12 17:41:14 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
await tx.peerPushPaymentIncoming.add({
|
2022-08-09 15:00:45 +02:00
|
|
|
peerPushPaymentIncomingId,
|
|
|
|
contractPriv: contractPriv,
|
|
|
|
exchangeBaseUrl: exchangeBaseUrl,
|
2022-07-12 17:41:14 +02:00
|
|
|
mergePriv: dec.mergePriv,
|
2022-08-09 15:00:45 +02:00
|
|
|
pursePub: pursePub,
|
2022-08-24 11:11:02 +02:00
|
|
|
timestamp: TalerProtocolTimestamp.now(),
|
2022-11-02 17:02:42 +01:00
|
|
|
contractTermsHash,
|
|
|
|
status: OperationStatus.Finished,
|
|
|
|
});
|
|
|
|
|
|
|
|
await tx.contractTerms.put({
|
|
|
|
h: contractTermsHash,
|
|
|
|
contractTermsRaw: dec.contractTerms,
|
2022-07-12 17:41:14 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
amount: purseStatus.balance,
|
|
|
|
contractTerms: dec.contractTerms,
|
2022-08-09 15:00:45 +02:00
|
|
|
peerPushPaymentIncomingId,
|
2022-07-12 17:41:14 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function talerPaytoFromExchangeReserve(
|
|
|
|
exchangeBaseUrl: string,
|
|
|
|
reservePub: string,
|
|
|
|
): string {
|
|
|
|
const url = new URL(exchangeBaseUrl);
|
|
|
|
let proto: string;
|
|
|
|
if (url.protocol === "http:") {
|
2022-08-09 15:00:45 +02:00
|
|
|
proto = "taler-reserve-http";
|
2022-07-12 17:41:14 +02:00
|
|
|
} else if (url.protocol === "https:") {
|
2022-08-09 15:00:45 +02:00
|
|
|
proto = "taler-reserve";
|
2022-07-12 17:41:14 +02:00
|
|
|
} else {
|
|
|
|
throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
|
|
|
|
}
|
|
|
|
|
|
|
|
let path = url.pathname;
|
|
|
|
if (!path.endsWith("/")) {
|
|
|
|
path = path + "/";
|
|
|
|
}
|
|
|
|
|
|
|
|
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
|
|
|
|
}
|
|
|
|
|
2022-08-23 11:29:45 +02:00
|
|
|
async function getMergeReserveInfo(
|
2022-07-12 17:41:14 +02:00
|
|
|
ws: InternalWalletState,
|
2022-08-23 11:29:45 +02:00
|
|
|
req: {
|
|
|
|
exchangeBaseUrl: string;
|
|
|
|
},
|
2022-10-14 22:56:29 +02:00
|
|
|
): Promise<ReserveRecord> {
|
2022-08-09 15:00:45 +02:00
|
|
|
// We have to eagerly create the key pair outside of the transaction,
|
2022-07-12 17:41:14 +02:00
|
|
|
// due to the async crypto API.
|
|
|
|
const newReservePair = await ws.cryptoApi.createEddsaKeypair({});
|
|
|
|
|
2022-10-14 22:56:29 +02:00
|
|
|
const mergeReserveRecord: ReserveRecord = await ws.db
|
|
|
|
.mktx((x) => [x.exchanges, x.reserves, x.withdrawalGroups])
|
2022-07-12 17:41:14 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
2022-08-23 11:29:45 +02:00
|
|
|
const ex = await tx.exchanges.get(req.exchangeBaseUrl);
|
2022-07-12 17:41:14 +02:00
|
|
|
checkDbInvariant(!!ex);
|
2022-10-14 22:56:29 +02:00
|
|
|
if (ex.currentMergeReserveRowId != null) {
|
|
|
|
const reserve = await tx.reserves.get(ex.currentMergeReserveRowId);
|
|
|
|
checkDbInvariant(!!reserve);
|
|
|
|
return reserve;
|
2022-07-12 17:41:14 +02:00
|
|
|
}
|
2022-10-14 22:56:29 +02:00
|
|
|
const reserve: ReserveRecord = {
|
2022-07-12 17:41:14 +02:00
|
|
|
reservePriv: newReservePair.priv,
|
2022-08-09 15:00:45 +02:00
|
|
|
reservePub: newReservePair.pub,
|
2022-07-12 17:41:14 +02:00
|
|
|
};
|
2022-10-14 22:56:29 +02:00
|
|
|
const insertResp = await tx.reserves.put(reserve);
|
|
|
|
checkDbInvariant(typeof insertResp.key === "number");
|
|
|
|
reserve.rowId = insertResp.key;
|
|
|
|
ex.currentMergeReserveRowId = reserve.rowId;
|
|
|
|
await tx.exchanges.put(ex);
|
|
|
|
return reserve;
|
2022-07-12 17:41:14 +02:00
|
|
|
});
|
|
|
|
|
2022-10-14 22:56:29 +02:00
|
|
|
return mergeReserveRecord;
|
2022-08-23 11:29:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function acceptPeerPushPayment(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: AcceptPeerPushPaymentRequest,
|
2022-09-16 16:06:55 +02:00
|
|
|
): Promise<AcceptPeerPushPaymentResponse> {
|
2022-11-02 17:02:42 +01:00
|
|
|
let peerInc: PeerPushPaymentIncomingRecord | undefined;
|
|
|
|
let contractTerms: PeerContractTerms | undefined;
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
|
2022-08-23 11:29:45 +02:00
|
|
|
.runReadOnly(async (tx) => {
|
2022-11-02 17:02:42 +01:00
|
|
|
peerInc = await tx.peerPushPaymentIncoming.get(
|
|
|
|
req.peerPushPaymentIncomingId,
|
|
|
|
);
|
|
|
|
if (!peerInc) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash);
|
|
|
|
if (ctRec) {
|
|
|
|
contractTerms = ctRec.contractTermsRaw;
|
|
|
|
}
|
2022-08-23 11:29:45 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!peerInc) {
|
|
|
|
throw Error(
|
|
|
|
`can't accept unknown incoming p2p push payment (${req.peerPushPaymentIncomingId})`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-02 17:02:42 +01:00
|
|
|
checkDbInvariant(!!contractTerms);
|
|
|
|
|
2022-08-24 21:07:09 +02:00
|
|
|
await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl);
|
|
|
|
|
2022-11-02 17:02:42 +01:00
|
|
|
const amount = Amounts.parseOrThrow(contractTerms.amount);
|
2022-08-23 11:29:45 +02:00
|
|
|
|
|
|
|
const mergeReserveInfo = await getMergeReserveInfo(ws, {
|
|
|
|
exchangeBaseUrl: peerInc.exchangeBaseUrl,
|
|
|
|
});
|
|
|
|
|
2022-07-12 17:41:14 +02:00
|
|
|
const mergeTimestamp = TalerProtocolTimestamp.now();
|
|
|
|
|
|
|
|
const reservePayto = talerPaytoFromExchangeReserve(
|
2022-08-09 15:00:45 +02:00
|
|
|
peerInc.exchangeBaseUrl,
|
|
|
|
mergeReserveInfo.reservePub,
|
2022-07-12 17:41:14 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const sigRes = await ws.cryptoApi.signPurseMerge({
|
2022-11-02 17:02:42 +01:00
|
|
|
contractTermsHash: ContractTermsUtil.hashContractTerms(contractTerms),
|
2022-07-12 17:41:14 +02:00
|
|
|
flags: WalletAccountMergeFlags.MergeFullyPaidPurse,
|
|
|
|
mergePriv: peerInc.mergePriv,
|
|
|
|
mergeTimestamp: mergeTimestamp,
|
|
|
|
purseAmount: Amounts.stringify(amount),
|
2022-11-02 17:02:42 +01:00
|
|
|
purseExpiration: contractTerms.purse_expiration,
|
2022-11-02 17:42:14 +01:00
|
|
|
purseFee: Amounts.stringify(Amounts.zeroOfCurrency(amount.currency)),
|
2022-07-12 17:41:14 +02:00
|
|
|
pursePub: peerInc.pursePub,
|
|
|
|
reservePayto,
|
2022-08-09 15:00:45 +02:00
|
|
|
reservePriv: mergeReserveInfo.reservePriv,
|
2022-07-12 17:41:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const mergePurseUrl = new URL(
|
2022-08-09 15:00:45 +02:00
|
|
|
`purses/${peerInc.pursePub}/merge`,
|
|
|
|
peerInc.exchangeBaseUrl,
|
2022-07-12 17:41:14 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const mergeReq: ExchangePurseMergeRequest = {
|
|
|
|
payto_uri: reservePayto,
|
|
|
|
merge_timestamp: mergeTimestamp,
|
|
|
|
merge_sig: sigRes.mergeSig,
|
|
|
|
reserve_sig: sigRes.accountSig,
|
|
|
|
};
|
|
|
|
|
|
|
|
const mergeHttpReq = await ws.http.postJson(mergePurseUrl.href, mergeReq);
|
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
logger.info(`merge request: ${j2s(mergeReq)}`);
|
2022-07-12 17:41:14 +02:00
|
|
|
const res = await readSuccessResponseJsonOrThrow(mergeHttpReq, codecForAny());
|
2022-08-09 15:00:45 +02:00
|
|
|
logger.info(`merge response: ${j2s(res)}`);
|
|
|
|
|
2022-09-16 16:06:55 +02:00
|
|
|
const wg = await internalCreateWithdrawalGroup(ws, {
|
2022-08-09 15:00:45 +02:00
|
|
|
amount,
|
2022-08-24 22:42:30 +02:00
|
|
|
wgInfo: {
|
|
|
|
withdrawalType: WithdrawalRecordType.PeerPushCredit,
|
2022-11-02 17:02:42 +01:00
|
|
|
contractTerms,
|
2022-08-24 22:42:30 +02:00
|
|
|
},
|
2022-08-09 15:00:45 +02:00
|
|
|
exchangeBaseUrl: peerInc.exchangeBaseUrl,
|
2022-09-21 20:46:45 +02:00
|
|
|
reserveStatus: WithdrawalGroupStatus.QueryingStatus,
|
2022-08-09 15:00:45 +02:00
|
|
|
reserveKeyPair: {
|
|
|
|
priv: mergeReserveInfo.reservePriv,
|
|
|
|
pub: mergeReserveInfo.reservePub,
|
|
|
|
},
|
|
|
|
});
|
2022-09-16 16:06:55 +02:00
|
|
|
|
|
|
|
return {
|
2022-10-14 22:47:11 +02:00
|
|
|
transactionId: makeTransactionId(
|
2022-09-16 16:06:55 +02:00
|
|
|
TransactionType.PeerPushCredit,
|
2022-09-16 16:24:47 +02:00
|
|
|
wg.withdrawalGroupId,
|
|
|
|
),
|
|
|
|
};
|
2022-06-21 12:40:12 +02:00
|
|
|
}
|
2022-08-23 11:29:45 +02:00
|
|
|
|
2022-08-24 11:11:02 +02:00
|
|
|
export async function acceptPeerPullPayment(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: AcceptPeerPullPaymentRequest,
|
2022-09-16 16:06:55 +02:00
|
|
|
): Promise<AcceptPeerPullPaymentResponse> {
|
2022-08-24 11:11:02 +02:00
|
|
|
const peerPullInc = await ws.db
|
2022-09-13 13:25:41 +02:00
|
|
|
.mktx((x) => [x.peerPullPaymentIncoming])
|
2022-08-24 11:11:02 +02:00
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
return tx.peerPullPaymentIncoming.get(req.peerPullPaymentIncomingId);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!peerPullInc) {
|
|
|
|
throw Error(
|
|
|
|
`can't accept unknown incoming p2p pull payment (${req.peerPullPaymentIncomingId})`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const instructedAmount = Amounts.parseOrThrow(
|
|
|
|
peerPullInc.contractTerms.amount,
|
|
|
|
);
|
2023-01-06 11:08:45 +01:00
|
|
|
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
2022-09-13 13:25:41 +02:00
|
|
|
.mktx((x) => [
|
|
|
|
x.exchanges,
|
|
|
|
x.coins,
|
|
|
|
x.denominations,
|
|
|
|
x.refreshGroups,
|
|
|
|
x.peerPullPaymentIncoming,
|
2022-09-16 16:20:47 +02:00
|
|
|
x.coinAvailability,
|
2022-09-13 13:25:41 +02:00
|
|
|
])
|
2022-08-24 11:11:02 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
2023-01-06 11:08:45 +01:00
|
|
|
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
|
|
|
if (selRes.type !== "success") {
|
|
|
|
return selRes;
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
const sel = selRes.result;
|
|
|
|
|
2022-09-14 20:34:37 +02:00
|
|
|
await spendCoins(ws, tx, {
|
2022-10-14 22:47:11 +02:00
|
|
|
allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`,
|
2022-09-14 20:34:37 +02:00
|
|
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
|
|
|
contributions: sel.coins.map((x) =>
|
|
|
|
Amounts.parseOrThrow(x.contribution),
|
|
|
|
),
|
|
|
|
refreshReason: RefreshReason.PayPeerPull,
|
|
|
|
});
|
2022-08-24 11:11:02 +02:00
|
|
|
|
2022-08-24 22:17:19 +02:00
|
|
|
const pi = await tx.peerPullPaymentIncoming.get(
|
|
|
|
req.peerPullPaymentIncomingId,
|
|
|
|
);
|
|
|
|
if (!pi) {
|
|
|
|
throw Error();
|
|
|
|
}
|
2022-11-02 17:02:42 +01:00
|
|
|
pi.status = PeerPullPaymentIncomingStatus.Accepted;
|
2022-08-24 22:17:19 +02:00
|
|
|
await tx.peerPullPaymentIncoming.put(pi);
|
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
return selRes;
|
2022-08-24 11:11:02 +02:00
|
|
|
});
|
2022-09-05 12:55:46 +02:00
|
|
|
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
|
2022-08-24 11:11:02 +02:00
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
if (coinSelRes.type !== "success") {
|
2023-01-06 13:55:08 +01:00
|
|
|
throw TalerError.fromDetail(
|
|
|
|
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
|
|
|
{
|
|
|
|
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
|
|
|
},
|
|
|
|
);
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const pursePub = peerPullInc.pursePub;
|
|
|
|
|
2023-01-06 11:08:45 +01:00
|
|
|
const coinSel = coinSelRes.result;
|
|
|
|
|
2022-08-24 11:11:02 +02:00
|
|
|
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
2023-01-06 11:08:45 +01:00
|
|
|
exchangeBaseUrl: coinSel.exchangeBaseUrl,
|
2022-08-24 11:11:02 +02:00
|
|
|
pursePub,
|
2023-01-06 11:08:45 +01:00
|
|
|
coins: coinSel.coins,
|
2022-08-24 11:11:02 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const purseDepositUrl = new URL(
|
|
|
|
`purses/${pursePub}/deposit`,
|
2023-01-06 11:08:45 +01:00
|
|
|
coinSel.exchangeBaseUrl,
|
2022-08-24 11:11:02 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const depositPayload: ExchangePurseDeposits = {
|
|
|
|
deposits: depositSigsResp.deposits,
|
|
|
|
};
|
|
|
|
|
|
|
|
const httpResp = await ws.http.postJson(purseDepositUrl.href, depositPayload);
|
|
|
|
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
|
|
|
|
logger.trace(`purse deposit response: ${j2s(resp)}`);
|
2022-09-16 16:06:55 +02:00
|
|
|
|
|
|
|
return {
|
2022-10-14 22:47:11 +02:00
|
|
|
transactionId: makeTransactionId(
|
2022-09-16 16:06:55 +02:00
|
|
|
TransactionType.PeerPullDebit,
|
|
|
|
req.peerPullPaymentIncomingId,
|
2022-09-16 16:24:47 +02:00
|
|
|
),
|
|
|
|
};
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function checkPeerPullPayment(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: CheckPeerPullPaymentRequest,
|
|
|
|
): Promise<CheckPeerPullPaymentResponse> {
|
|
|
|
const uri = parsePayPullUri(req.talerUri);
|
|
|
|
|
|
|
|
if (!uri) {
|
|
|
|
throw Error("got invalid taler://pay-push URI");
|
|
|
|
}
|
|
|
|
|
|
|
|
const exchangeBaseUrl = uri.exchangeBaseUrl;
|
|
|
|
const contractPriv = uri.contractPriv;
|
|
|
|
const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv)));
|
|
|
|
|
|
|
|
const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
|
|
|
|
|
|
|
|
const contractHttpResp = await ws.http.get(getContractUrl.href);
|
|
|
|
|
|
|
|
const contractResp = await readSuccessResponseJsonOrThrow(
|
|
|
|
contractHttpResp,
|
|
|
|
codecForExchangeGetContractResponse(),
|
|
|
|
);
|
|
|
|
|
|
|
|
const pursePub = contractResp.purse_pub;
|
|
|
|
|
|
|
|
const dec = await ws.cryptoApi.decryptContractForDeposit({
|
|
|
|
ciphertext: contractResp.econtract,
|
|
|
|
contractPriv: contractPriv,
|
|
|
|
pursePub: pursePub,
|
|
|
|
});
|
|
|
|
|
|
|
|
const getPurseUrl = new URL(`purses/${pursePub}/merge`, exchangeBaseUrl);
|
|
|
|
|
|
|
|
const purseHttpResp = await ws.http.get(getPurseUrl.href);
|
|
|
|
|
|
|
|
const purseStatus = await readSuccessResponseJsonOrThrow(
|
|
|
|
purseHttpResp,
|
|
|
|
codecForExchangePurseStatus(),
|
|
|
|
);
|
|
|
|
|
|
|
|
const peerPullPaymentIncomingId = encodeCrock(getRandomBytes(32));
|
|
|
|
|
|
|
|
await ws.db
|
2022-09-13 13:25:41 +02:00
|
|
|
.mktx((x) => [x.peerPullPaymentIncoming])
|
2022-08-24 11:11:02 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
await tx.peerPullPaymentIncoming.add({
|
|
|
|
peerPullPaymentIncomingId,
|
|
|
|
contractPriv: contractPriv,
|
|
|
|
exchangeBaseUrl: exchangeBaseUrl,
|
|
|
|
pursePub: pursePub,
|
2022-08-24 22:17:19 +02:00
|
|
|
timestampCreated: TalerProtocolTimestamp.now(),
|
2022-08-24 11:11:02 +02:00
|
|
|
contractTerms: dec.contractTerms,
|
2022-11-02 17:02:42 +01:00
|
|
|
status: PeerPullPaymentIncomingStatus.Proposed,
|
2022-08-24 11:11:02 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
amount: purseStatus.balance,
|
|
|
|
contractTerms: dec.contractTerms,
|
|
|
|
peerPullPaymentIncomingId,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-12 16:57:51 +01:00
|
|
|
export async function processPeerPullInitiation(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
pursePub: string,
|
|
|
|
): Promise<OperationAttemptResult> {
|
|
|
|
const pullIni = await ws.db
|
|
|
|
.mktx((x) => [x.peerPullPaymentInitiations])
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
return tx.peerPullPaymentInitiations.get(pursePub);
|
|
|
|
});
|
|
|
|
if (!pullIni) {
|
|
|
|
throw Error("peer pull payment initiation not found in database");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pullIni.status === OperationStatus.Finished) {
|
|
|
|
logger.warn("peer pull payment initiation is already finished");
|
|
|
|
return {
|
|
|
|
type: OperationAttemptResultType.Finished,
|
|
|
|
result: undefined,
|
2023-01-13 01:45:33 +01:00
|
|
|
};
|
2023-01-12 16:57:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const mergeReserve = await ws.db
|
|
|
|
.mktx((x) => [x.reserves])
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
return tx.reserves.get(pullIni.mergeReserveRowId);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!mergeReserve) {
|
|
|
|
throw Error("merge reserve for peer pull payment not found in database");
|
|
|
|
}
|
|
|
|
|
|
|
|
const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount));
|
|
|
|
|
|
|
|
const reservePayto = talerPaytoFromExchangeReserve(
|
|
|
|
pullIni.exchangeBaseUrl,
|
|
|
|
mergeReserve.reservePub,
|
|
|
|
);
|
|
|
|
|
|
|
|
const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
|
|
|
|
contractPriv: pullIni.contractPriv,
|
|
|
|
contractPub: pullIni.contractPub,
|
|
|
|
contractTerms: pullIni.contractTerms,
|
|
|
|
pursePriv: pullIni.pursePriv,
|
|
|
|
pursePub: pullIni.pursePub,
|
|
|
|
});
|
|
|
|
|
|
|
|
const purseExpiration = pullIni.contractTerms.purse_expiration;
|
|
|
|
const sigRes = await ws.cryptoApi.signReservePurseCreate({
|
|
|
|
contractTermsHash: pullIni.contractTermsHash,
|
|
|
|
flags: WalletAccountMergeFlags.CreateWithPurseFee,
|
|
|
|
mergePriv: pullIni.mergePriv,
|
|
|
|
mergeTimestamp: pullIni.mergeTimestamp,
|
|
|
|
purseAmount: pullIni.contractTerms.amount,
|
|
|
|
purseExpiration: purseExpiration,
|
|
|
|
purseFee: purseFee,
|
|
|
|
pursePriv: pullIni.pursePriv,
|
|
|
|
pursePub: pullIni.pursePub,
|
|
|
|
reservePayto,
|
|
|
|
reservePriv: mergeReserve.reservePriv,
|
|
|
|
});
|
|
|
|
|
|
|
|
const reservePurseReqBody: ExchangeReservePurseRequest = {
|
|
|
|
merge_sig: sigRes.mergeSig,
|
|
|
|
merge_timestamp: pullIni.mergeTimestamp,
|
|
|
|
h_contract_terms: pullIni.contractTermsHash,
|
|
|
|
merge_pub: pullIni.mergePub,
|
|
|
|
min_age: 0,
|
|
|
|
purse_expiration: purseExpiration,
|
|
|
|
purse_fee: purseFee,
|
|
|
|
purse_pub: pullIni.pursePub,
|
|
|
|
purse_sig: sigRes.purseSig,
|
|
|
|
purse_value: pullIni.contractTerms.amount,
|
|
|
|
reserve_sig: sigRes.accountSig,
|
|
|
|
econtract: econtractResp.econtract,
|
|
|
|
};
|
|
|
|
|
|
|
|
logger.info(`reserve purse request: ${j2s(reservePurseReqBody)}`);
|
|
|
|
|
|
|
|
const reservePurseMergeUrl = new URL(
|
|
|
|
`reserves/${mergeReserve.reservePub}/purse`,
|
|
|
|
pullIni.exchangeBaseUrl,
|
|
|
|
);
|
|
|
|
|
|
|
|
const httpResp = await ws.http.postJson(
|
|
|
|
reservePurseMergeUrl.href,
|
|
|
|
reservePurseReqBody,
|
|
|
|
);
|
|
|
|
|
|
|
|
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
|
|
|
|
|
|
|
|
logger.info(`reserve merge response: ${j2s(resp)}`);
|
|
|
|
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => [x.peerPullPaymentInitiations])
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
const pi2 = await tx.peerPullPaymentInitiations.get(pursePub);
|
|
|
|
if (!pi2) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
pi2.status = OperationStatus.Finished;
|
|
|
|
await tx.peerPullPaymentInitiations.put(pi2);
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
type: OperationAttemptResultType.Finished,
|
|
|
|
result: undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:00:34 +01:00
|
|
|
export async function preparePeerPullPayment(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: PreparePeerPullPaymentRequest,
|
|
|
|
): Promise<PreparePeerPullPaymentResponse> {
|
|
|
|
//FIXME: look up for exchange details and use purse fee
|
|
|
|
return {
|
|
|
|
amountEffective: req.amount,
|
|
|
|
amountRaw: req.amount,
|
|
|
|
};
|
|
|
|
}
|
2023-01-06 11:08:45 +01:00
|
|
|
|
2022-09-12 20:52:01 +02:00
|
|
|
/**
|
|
|
|
* Initiate a peer pull payment.
|
|
|
|
*/
|
2022-11-02 17:02:42 +01:00
|
|
|
export async function initiatePeerPullPayment(
|
2022-08-23 11:29:45 +02:00
|
|
|
ws: InternalWalletState,
|
|
|
|
req: InitiatePeerPullPaymentRequest,
|
|
|
|
): Promise<InitiatePeerPullPaymentResponse> {
|
2022-08-24 22:17:19 +02:00
|
|
|
await updateExchangeFromUrl(ws, req.exchangeBaseUrl);
|
|
|
|
|
2022-08-23 11:29:45 +02:00
|
|
|
const mergeReserveInfo = await getMergeReserveInfo(ws, {
|
|
|
|
exchangeBaseUrl: req.exchangeBaseUrl,
|
|
|
|
});
|
|
|
|
|
|
|
|
const mergeTimestamp = TalerProtocolTimestamp.now();
|
|
|
|
|
|
|
|
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
|
|
|
|
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
|
|
|
|
|
2022-11-08 17:00:34 +01:00
|
|
|
const instructedAmount = Amounts.parseOrThrow(
|
|
|
|
req.partialContractTerms.amount,
|
2022-08-23 11:29:45 +02:00
|
|
|
);
|
2022-11-08 17:00:34 +01:00
|
|
|
const contractTerms = req.partialContractTerms;
|
2022-08-23 11:29:45 +02:00
|
|
|
|
|
|
|
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
|
|
|
|
|
2023-01-12 16:57:51 +01:00
|
|
|
const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
|
2022-08-23 11:29:45 +02:00
|
|
|
|
2023-01-12 16:57:51 +01:00
|
|
|
const mergeReserveRowId = mergeReserveInfo.rowId;
|
|
|
|
checkDbInvariant(!!mergeReserveRowId);
|
2022-08-23 11:29:45 +02:00
|
|
|
|
|
|
|
await ws.db
|
2022-11-02 17:02:42 +01:00
|
|
|
.mktx((x) => [x.peerPullPaymentInitiations, x.contractTerms])
|
2022-08-23 11:29:45 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
2022-09-13 13:25:41 +02:00
|
|
|
await tx.peerPullPaymentInitiations.put({
|
2022-11-08 17:00:34 +01:00
|
|
|
amount: req.partialContractTerms.amount,
|
2022-11-02 17:02:42 +01:00
|
|
|
contractTermsHash: hContractTerms,
|
2022-08-23 11:29:45 +02:00
|
|
|
exchangeBaseUrl: req.exchangeBaseUrl,
|
|
|
|
pursePriv: pursePair.priv,
|
|
|
|
pursePub: pursePair.pub,
|
2023-01-12 16:57:51 +01:00
|
|
|
mergePriv: mergePair.priv,
|
|
|
|
mergePub: mergePair.pub,
|
|
|
|
status: OperationStatus.Pending,
|
|
|
|
contractTerms: contractTerms,
|
|
|
|
mergeTimestamp,
|
|
|
|
mergeReserveRowId: mergeReserveRowId,
|
|
|
|
contractPriv: contractKeyPair.priv,
|
|
|
|
contractPub: contractKeyPair.pub,
|
2022-11-02 17:02:42 +01:00
|
|
|
});
|
|
|
|
await tx.contractTerms.put({
|
|
|
|
contractTermsRaw: contractTerms,
|
|
|
|
h: hContractTerms,
|
2022-08-23 11:29:45 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-01-12 16:57:51 +01:00
|
|
|
// FIXME: Should we somehow signal to the client
|
|
|
|
// whether purse creation has failed, or does the client/
|
|
|
|
// check this asynchronously from the transaction status?
|
2022-08-23 11:29:45 +02:00
|
|
|
|
2023-01-12 16:57:51 +01:00
|
|
|
await runOperationWithErrorReporting(
|
|
|
|
ws,
|
|
|
|
RetryTags.byPeerPullPaymentInitiationPursePub(pursePair.pub),
|
|
|
|
async () => {
|
|
|
|
return processPeerPullInitiation(ws, pursePair.pub);
|
|
|
|
},
|
2022-08-23 11:29:45 +02:00
|
|
|
);
|
|
|
|
|
2022-09-16 16:06:55 +02:00
|
|
|
const wg = await internalCreateWithdrawalGroup(ws, {
|
2022-11-08 17:00:34 +01:00
|
|
|
amount: instructedAmount,
|
2022-08-24 22:42:30 +02:00
|
|
|
wgInfo: {
|
|
|
|
withdrawalType: WithdrawalRecordType.PeerPullCredit,
|
2022-09-01 13:41:22 +02:00
|
|
|
contractTerms,
|
2023-01-12 16:57:51 +01:00
|
|
|
contractPriv: contractKeyPair.priv,
|
2022-08-24 22:42:30 +02:00
|
|
|
},
|
2022-08-24 11:11:02 +02:00
|
|
|
exchangeBaseUrl: req.exchangeBaseUrl,
|
2022-09-21 20:46:45 +02:00
|
|
|
reserveStatus: WithdrawalGroupStatus.QueryingStatus,
|
2022-08-24 11:11:02 +02:00
|
|
|
reserveKeyPair: {
|
|
|
|
priv: mergeReserveInfo.reservePriv,
|
|
|
|
pub: mergeReserveInfo.reservePub,
|
|
|
|
},
|
|
|
|
});
|
2022-08-23 11:29:45 +02:00
|
|
|
|
|
|
|
return {
|
2022-08-24 11:11:02 +02:00
|
|
|
talerUri: constructPayPullUri({
|
2022-08-23 11:29:45 +02:00
|
|
|
exchangeBaseUrl: req.exchangeBaseUrl,
|
2023-01-12 16:57:51 +01:00
|
|
|
contractPriv: contractKeyPair.priv,
|
2022-08-23 11:29:45 +02:00
|
|
|
}),
|
2022-10-14 22:47:11 +02:00
|
|
|
transactionId: makeTransactionId(
|
2022-09-16 16:06:55 +02:00
|
|
|
TransactionType.PeerPullCredit,
|
2022-09-16 16:24:47 +02:00
|
|
|
wg.withdrawalGroupId,
|
|
|
|
),
|
2022-08-23 11:29:45 +02:00
|
|
|
};
|
|
|
|
}
|