wallet-core: store total p2p push cost in DB
This commit is contained in:
parent
a3f9e86805
commit
a31b8c3c31
@ -1713,6 +1713,8 @@ export interface PeerPushPaymentInitiationRecord {
|
|||||||
*/
|
*/
|
||||||
amount: AmountString;
|
amount: AmountString;
|
||||||
|
|
||||||
|
totalCost: AmountString;
|
||||||
|
|
||||||
coinSel: PeerPushPaymentCoinSelection;
|
coinSel: PeerPushPaymentCoinSelection;
|
||||||
|
|
||||||
contractTermsHash: HashCodeString;
|
contractTermsHash: HashCodeString;
|
||||||
|
@ -103,20 +103,22 @@ import { internalCreateWithdrawalGroup } from "./withdraw.js";
|
|||||||
|
|
||||||
const logger = new Logger("operations/peer-to-peer.ts");
|
const logger = new Logger("operations/peer-to-peer.ts");
|
||||||
|
|
||||||
export interface PeerCoinSelectionDetails {
|
interface SelectedPeerCoin {
|
||||||
exchangeBaseUrl: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Info of Coins that were selected.
|
|
||||||
*/
|
|
||||||
coins: {
|
|
||||||
coinPub: string;
|
coinPub: string;
|
||||||
coinPriv: string;
|
coinPriv: string;
|
||||||
contribution: AmountString;
|
contribution: AmountString;
|
||||||
denomPubHash: string;
|
denomPubHash: string;
|
||||||
denomSig: UnblindedSignature;
|
denomSig: UnblindedSignature;
|
||||||
ageCommitmentProof: AgeCommitmentProof | undefined;
|
ageCommitmentProof: AgeCommitmentProof | undefined;
|
||||||
}[];
|
}
|
||||||
|
|
||||||
|
interface PeerCoinSelectionDetails {
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info of Coins that were selected.
|
||||||
|
*/
|
||||||
|
coins: SelectedPeerCoin[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much of the deposit fees is the customer paying?
|
* How much of the deposit fees is the customer paying?
|
||||||
@ -195,15 +197,19 @@ export async function queryCoinInfosForSelection(
|
|||||||
|
|
||||||
export async function selectPeerCoins(
|
export async function selectPeerCoins(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
tx: GetReadOnlyAccess<{
|
|
||||||
exchanges: typeof WalletStoresV1.exchanges;
|
|
||||||
denominations: typeof WalletStoresV1.denominations;
|
|
||||||
coins: typeof WalletStoresV1.coins;
|
|
||||||
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
|
||||||
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
|
||||||
}>,
|
|
||||||
instructedAmount: AmountJson,
|
instructedAmount: AmountJson,
|
||||||
): Promise<SelectPeerCoinsResult> {
|
): Promise<SelectPeerCoinsResult> {
|
||||||
|
return await ws.db
|
||||||
|
.mktx((x) => [
|
||||||
|
x.exchanges,
|
||||||
|
x.contractTerms,
|
||||||
|
x.coins,
|
||||||
|
x.coinAvailability,
|
||||||
|
x.denominations,
|
||||||
|
x.refreshGroups,
|
||||||
|
x.peerPushPaymentInitiations,
|
||||||
|
])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
const exchanges = await tx.exchanges.iter().toArray();
|
const exchanges = await tx.exchanges.iter().toArray();
|
||||||
const exchangeFeeGap: { [url: string]: AmountJson } = {};
|
const exchangeFeeGap: { [url: string]: AmountJson } = {};
|
||||||
const currency = Amounts.currencyOf(instructedAmount);
|
const currency = Amounts.currencyOf(instructedAmount);
|
||||||
@ -309,7 +315,8 @@ export async function selectPeerCoins(
|
|||||||
currency,
|
currency,
|
||||||
restrictExchangeTo: exch.baseUrl,
|
restrictExchangeTo: exch.baseUrl,
|
||||||
});
|
});
|
||||||
let gap = exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
|
let gap =
|
||||||
|
exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
|
||||||
if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
|
if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
|
||||||
// Show fee gap only if we should've been able to pay with the material amount
|
// Show fee gap only if we should've been able to pay with the material amount
|
||||||
gap = Amounts.zeroOfAmount(currency);
|
gap = Amounts.zeroOfAmount(currency);
|
||||||
@ -329,18 +336,19 @@ export async function selectPeerCoins(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return { type: "failure", insufficientBalanceDetails: errDetails };
|
return { type: "failure", insufficientBalanceDetails: errDetails };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTotalPeerPaymentCost(
|
export async function getTotalPeerPaymentCost(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
pcs: PeerCoinSelectionDetails,
|
pcs: SelectedPeerCoin[],
|
||||||
): Promise<AmountJson> {
|
): Promise<AmountJson> {
|
||||||
return ws.db
|
return ws.db
|
||||||
.mktx((x) => [x.coins, x.denominations])
|
.mktx((x) => [x.coins, x.denominations])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
const costs: AmountJson[] = [];
|
const costs: AmountJson[] = [];
|
||||||
for (let i = 0; i < pcs.coins.length; i++) {
|
for (let i = 0; i < pcs.length; i++) {
|
||||||
const coin = await tx.coins.get(pcs.coins[i].coinPub);
|
const coin = await tx.coins.get(pcs[i].coinPub);
|
||||||
if (!coin) {
|
if (!coin) {
|
||||||
throw Error("can't calculate payment cost, coin not found");
|
throw Error("can't calculate payment cost, coin not found");
|
||||||
}
|
}
|
||||||
@ -358,22 +366,22 @@ export async function getTotalPeerPaymentCost(
|
|||||||
.filter((x) =>
|
.filter((x) =>
|
||||||
Amounts.isSameCurrency(
|
Amounts.isSameCurrency(
|
||||||
DenominationRecord.getValue(x),
|
DenominationRecord.getValue(x),
|
||||||
pcs.coins[i].contribution,
|
pcs[i].contribution,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const amountLeft = Amounts.sub(
|
const amountLeft = Amounts.sub(
|
||||||
DenominationRecord.getValue(denom),
|
DenominationRecord.getValue(denom),
|
||||||
pcs.coins[i].contribution,
|
pcs[i].contribution,
|
||||||
).amount;
|
).amount;
|
||||||
const refreshCost = getTotalRefreshCost(
|
const refreshCost = getTotalRefreshCost(
|
||||||
allDenoms,
|
allDenoms,
|
||||||
DenominationRecord.toDenomInfo(denom),
|
DenominationRecord.toDenomInfo(denom),
|
||||||
amountLeft,
|
amountLeft,
|
||||||
);
|
);
|
||||||
costs.push(Amounts.parseOrThrow(pcs.coins[i].contribution));
|
costs.push(Amounts.parseOrThrow(pcs[i].contribution));
|
||||||
costs.push(refreshCost);
|
costs.push(refreshCost);
|
||||||
}
|
}
|
||||||
const zero = Amounts.zeroOfAmount(pcs.coins[0].contribution);
|
const zero = Amounts.zeroOfAmount(pcs[0].contribution);
|
||||||
return Amounts.sum([zero, ...costs]).amount;
|
return Amounts.sum([zero, ...costs]).amount;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -383,20 +391,7 @@ export async function preparePeerPushPayment(
|
|||||||
req: PreparePeerPushPaymentRequest,
|
req: PreparePeerPushPaymentRequest,
|
||||||
): Promise<PreparePeerPushPaymentResponse> {
|
): Promise<PreparePeerPushPaymentResponse> {
|
||||||
const instructedAmount = Amounts.parseOrThrow(req.amount);
|
const instructedAmount = Amounts.parseOrThrow(req.amount);
|
||||||
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
const coinSelRes = await selectPeerCoins(ws, instructedAmount);
|
||||||
.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") {
|
if (coinSelRes.type === "failure") {
|
||||||
throw TalerError.fromDetail(
|
throw TalerError.fromDetail(
|
||||||
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
||||||
@ -405,7 +400,10 @@ export async function preparePeerPushPayment(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const totalAmount = await getTotalPeerPaymentCost(ws, coinSelRes.result);
|
const totalAmount = await getTotalPeerPaymentCost(
|
||||||
|
ws,
|
||||||
|
coinSelRes.result.coins,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
amountEffective: Amounts.stringify(totalAmount),
|
amountEffective: Amounts.stringify(totalAmount),
|
||||||
amountRaw: req.amount,
|
amountRaw: req.amount,
|
||||||
@ -517,7 +515,28 @@ export async function initiatePeerPushPayment(
|
|||||||
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
|
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
|
||||||
|
|
||||||
const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
|
const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
|
||||||
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
|
||||||
|
const coinSelRes = await selectPeerCoins(ws, instructedAmount);
|
||||||
|
|
||||||
|
if (coinSelRes.type !== "success") {
|
||||||
|
throw TalerError.fromDetail(
|
||||||
|
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
||||||
|
{
|
||||||
|
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sel = coinSelRes.result;
|
||||||
|
|
||||||
|
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
|
||||||
|
|
||||||
|
const totalAmount = await getTotalPeerPaymentCost(
|
||||||
|
ws,
|
||||||
|
coinSelRes.result.coins,
|
||||||
|
);
|
||||||
|
|
||||||
|
await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
x.exchanges,
|
x.exchanges,
|
||||||
x.contractTerms,
|
x.contractTerms,
|
||||||
@ -528,13 +547,6 @@ export async function initiatePeerPushPayment(
|
|||||||
x.peerPushPaymentInitiations,
|
x.peerPushPaymentInitiations,
|
||||||
])
|
])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
|
||||||
if (selRes.type === "failure") {
|
|
||||||
return selRes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sel = selRes.result;
|
|
||||||
|
|
||||||
await spendCoins(ws, tx, {
|
await spendCoins(ws, tx, {
|
||||||
allocationId: `txn:peer-push-debit:${pursePair.pub}`,
|
allocationId: `txn:peer-push-debit:${pursePair.pub}`,
|
||||||
coinPubs: sel.coins.map((x) => x.coinPub),
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
||||||
@ -562,25 +574,14 @@ export async function initiatePeerPushPayment(
|
|||||||
coinPubs: sel.coins.map((x) => x.coinPub),
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
||||||
contributions: sel.coins.map((x) => x.contribution),
|
contributions: sel.coins.map((x) => x.contribution),
|
||||||
},
|
},
|
||||||
|
totalCost: Amounts.stringify(totalAmount),
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.contractTerms.put({
|
await tx.contractTerms.put({
|
||||||
h: hContractTerms,
|
h: hContractTerms,
|
||||||
contractTermsRaw: contractTerms,
|
contractTermsRaw: contractTerms,
|
||||||
});
|
});
|
||||||
|
|
||||||
return selRes;
|
|
||||||
});
|
});
|
||||||
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
|
|
||||||
|
|
||||||
if (coinSelRes.type !== "success") {
|
|
||||||
throw TalerError.fromDetail(
|
|
||||||
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
|
||||||
{
|
|
||||||
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await runOperationWithErrorReporting(
|
await runOperationWithErrorReporting(
|
||||||
ws,
|
ws,
|
||||||
@ -866,7 +867,22 @@ export async function acceptPeerPullPayment(
|
|||||||
const instructedAmount = Amounts.parseOrThrow(
|
const instructedAmount = Amounts.parseOrThrow(
|
||||||
peerPullInc.contractTerms.amount,
|
peerPullInc.contractTerms.amount,
|
||||||
);
|
);
|
||||||
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
|
||||||
|
const coinSelRes = await selectPeerCoins(ws, instructedAmount);
|
||||||
|
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
|
||||||
|
|
||||||
|
if (coinSelRes.type !== "success") {
|
||||||
|
throw TalerError.fromDetail(
|
||||||
|
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
||||||
|
{
|
||||||
|
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sel = coinSelRes.result;
|
||||||
|
|
||||||
|
await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
x.exchanges,
|
x.exchanges,
|
||||||
x.coins,
|
x.coins,
|
||||||
@ -876,13 +892,6 @@ export async function acceptPeerPullPayment(
|
|||||||
x.coinAvailability,
|
x.coinAvailability,
|
||||||
])
|
])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
|
||||||
if (selRes.type !== "success") {
|
|
||||||
return selRes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sel = selRes.result;
|
|
||||||
|
|
||||||
await spendCoins(ws, tx, {
|
await spendCoins(ws, tx, {
|
||||||
allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`,
|
allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`,
|
||||||
coinPubs: sel.coins.map((x) => x.coinPub),
|
coinPubs: sel.coins.map((x) => x.coinPub),
|
||||||
@ -900,19 +909,7 @@ export async function acceptPeerPullPayment(
|
|||||||
}
|
}
|
||||||
pi.status = PeerPullPaymentIncomingStatus.Accepted;
|
pi.status = PeerPullPaymentIncomingStatus.Accepted;
|
||||||
await tx.peerPullPaymentIncoming.put(pi);
|
await tx.peerPullPaymentIncoming.put(pi);
|
||||||
|
|
||||||
return selRes;
|
|
||||||
});
|
});
|
||||||
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
|
|
||||||
|
|
||||||
if (coinSelRes.type !== "success") {
|
|
||||||
throw TalerError.fromDetail(
|
|
||||||
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
|
|
||||||
{
|
|
||||||
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pursePub = peerPullInc.pursePub;
|
const pursePub = peerPullInc.pursePub;
|
||||||
|
|
||||||
|
@ -346,7 +346,7 @@ function buildTransactionForPushPaymentDebit(
|
|||||||
): Transaction {
|
): Transaction {
|
||||||
return {
|
return {
|
||||||
type: TransactionType.PeerPushDebit,
|
type: TransactionType.PeerPushDebit,
|
||||||
amountEffective: pi.amount,
|
amountEffective: pi.totalCost,
|
||||||
amountRaw: pi.amount,
|
amountRaw: pi.amount,
|
||||||
exchangeBaseUrl: pi.exchangeBaseUrl,
|
exchangeBaseUrl: pi.exchangeBaseUrl,
|
||||||
info: {
|
info: {
|
||||||
|
Loading…
Reference in New Issue
Block a user