wallet-core: support forced coins in new coin selection algo
This commit is contained in:
parent
b7f7b95602
commit
2747bc260b
@ -38,7 +38,6 @@ import {
|
|||||||
ContractTerms,
|
ContractTerms,
|
||||||
ContractTermsUtil,
|
ContractTermsUtil,
|
||||||
DenominationInfo,
|
DenominationInfo,
|
||||||
DenominationPubKey,
|
|
||||||
Duration,
|
Duration,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
ForcedCoinSel,
|
ForcedCoinSel,
|
||||||
@ -93,7 +92,6 @@ import {
|
|||||||
CoinSelectionTally,
|
CoinSelectionTally,
|
||||||
PreviousPayCoins,
|
PreviousPayCoins,
|
||||||
selectForcedPayCoins,
|
selectForcedPayCoins,
|
||||||
selectPayCoinsLegacy,
|
|
||||||
tallyFees,
|
tallyFees,
|
||||||
} from "../util/coinSelection.js";
|
} from "../util/coinSelection.js";
|
||||||
import {
|
import {
|
||||||
@ -104,6 +102,7 @@ import {
|
|||||||
readUnexpectedResponseDetails,
|
readUnexpectedResponseDetails,
|
||||||
throwUnexpectedRequestError,
|
throwUnexpectedRequestError,
|
||||||
} from "../util/http.js";
|
} from "../util/http.js";
|
||||||
|
import { checkLogicInvariant } from "../util/invariants.js";
|
||||||
import { GetReadWriteAccess } from "../util/query.js";
|
import { GetReadWriteAccess } from "../util/query.js";
|
||||||
import { RetryInfo, RetryTags, scheduleRetry } from "../util/retries.js";
|
import { RetryInfo, RetryTags, scheduleRetry } from "../util/retries.js";
|
||||||
import { spendCoins } from "../wallet.js";
|
import { spendCoins } from "../wallet.js";
|
||||||
@ -926,17 +925,6 @@ async function handleInsufficientFunds(
|
|||||||
|
|
||||||
const { contractData } = proposal.download;
|
const { contractData } = proposal.download;
|
||||||
|
|
||||||
const candidates = await getCandidatePayCoins(ws, {
|
|
||||||
allowedAuditors: contractData.allowedAuditors,
|
|
||||||
allowedExchanges: contractData.allowedExchanges,
|
|
||||||
amount: contractData.amount,
|
|
||||||
maxDepositFee: contractData.maxDepositFee,
|
|
||||||
maxWireFee: contractData.maxWireFee,
|
|
||||||
timestamp: contractData.timestamp,
|
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization,
|
|
||||||
wireMethod: contractData.wireMethod,
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevPayCoins: PreviousPayCoins = [];
|
const prevPayCoins: PreviousPayCoins = [];
|
||||||
|
|
||||||
await ws.db
|
await ws.db
|
||||||
@ -968,8 +956,10 @@ async function handleInsufficientFunds(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = selectPayCoinsLegacy({
|
const res = await selectPayCoinsNew(ws, {
|
||||||
candidates,
|
auditors: contractData.allowedAuditors,
|
||||||
|
exchanges: contractData.allowedExchanges,
|
||||||
|
wireMethod: contractData.wireMethod,
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
||||||
@ -1026,8 +1016,8 @@ async function unblockBackup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectPayCoinRequestNg {
|
export interface SelectPayCoinRequestNg {
|
||||||
exchanges: string[];
|
exchanges: AllowedExchangeInfo[];
|
||||||
auditors: string[];
|
auditors: AllowedAuditorInfo[];
|
||||||
wireMethod: string;
|
wireMethod: string;
|
||||||
contractTermsAmount: AmountJson;
|
contractTermsAmount: AmountJson;
|
||||||
depositFeeLimit: AmountJson;
|
depositFeeLimit: AmountJson;
|
||||||
@ -1035,34 +1025,18 @@ export interface SelectPayCoinRequestNg {
|
|||||||
wireFeeAmortization: number;
|
wireFeeAmortization: number;
|
||||||
prevPayCoins?: PreviousPayCoins;
|
prevPayCoins?: PreviousPayCoins;
|
||||||
requiredMinimumAge?: number;
|
requiredMinimumAge?: number;
|
||||||
|
forcedSelection?: ForcedCoinSel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AvailableDenom = DenominationInfo & {
|
export type AvailableDenom = DenominationInfo & {
|
||||||
numAvailable: number;
|
numAvailable: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
async function selectCandidates(
|
||||||
* Given a list of candidate coins, select coins to spend under the merchant's
|
|
||||||
* constraints.
|
|
||||||
*
|
|
||||||
* The prevPayCoins can be specified to "repair" a coin selection
|
|
||||||
* by adding additional coins, after a broken (e.g. double-spent) coin
|
|
||||||
* has been removed from the selection.
|
|
||||||
*
|
|
||||||
* This function is only exported for the sake of unit tests.
|
|
||||||
*/
|
|
||||||
export async function selectPayCoinsNew(
|
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
req: SelectPayCoinRequestNg,
|
req: SelectPayCoinRequestNg,
|
||||||
): Promise<PayCoinSelection | undefined> {
|
): Promise<[AvailableDenom[], Record<string, AmountJson>]> {
|
||||||
const {
|
return await ws.db
|
||||||
contractTermsAmount,
|
|
||||||
depositFeeLimit,
|
|
||||||
wireFeeLimit,
|
|
||||||
wireFeeAmortization,
|
|
||||||
} = req;
|
|
||||||
|
|
||||||
const [candidateDenoms, wireFeesPerExchange] = await ws.db
|
|
||||||
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
|
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
const denoms: AvailableDenom[] = [];
|
const denoms: AvailableDenom[] = [];
|
||||||
@ -1070,16 +1044,21 @@ export async function selectPayCoinsNew(
|
|||||||
const wfPerExchange: Record<string, AmountJson> = {};
|
const wfPerExchange: Record<string, AmountJson> = {};
|
||||||
for (const exchange of exchanges) {
|
for (const exchange of exchanges) {
|
||||||
const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
|
const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
|
||||||
if (exchangeDetails?.currency !== contractTermsAmount.currency) {
|
if (exchangeDetails?.currency !== req.contractTermsAmount.currency) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let accepted = false;
|
let accepted = false;
|
||||||
if (req.exchanges.includes(exchange.baseUrl)) {
|
for (const allowedExchange of req.exchanges) {
|
||||||
accepted = true;
|
if (allowedExchange.exchangePub === exchangeDetails.masterPublicKey) {
|
||||||
} else {
|
accepted = true;
|
||||||
for (const auditor of exchangeDetails.auditors) {
|
break;
|
||||||
if (req.auditors.includes(auditor.auditor_url)) {
|
}
|
||||||
|
}
|
||||||
|
for (const allowedAuditor of req.auditors) {
|
||||||
|
for (const providedAuditor of exchangeDetails.auditors) {
|
||||||
|
if (allowedAuditor.auditorPub === providedAuditor.auditor_pub) {
|
||||||
accepted = true;
|
accepted = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1090,6 +1069,7 @@ export async function selectPayCoinsNew(
|
|||||||
const exchangeDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
const exchangeDenoms = await tx.denominations.indexes.byExchangeBaseUrl
|
||||||
.iter(exchangeDetails.exchangeBaseUrl)
|
.iter(exchangeDetails.exchangeBaseUrl)
|
||||||
.filter((x) => x.freshCoinCount != null && x.freshCoinCount > 0);
|
.filter((x) => x.freshCoinCount != null && x.freshCoinCount > 0);
|
||||||
|
// FIXME: Check that the individual denomination is audited!
|
||||||
// FIXME: Should we exclude denominations that are
|
// FIXME: Should we exclude denominations that are
|
||||||
// not spendable anymore?
|
// not spendable anymore?
|
||||||
for (const denom of exchangeDenoms) {
|
for (const denom of exchangeDenoms) {
|
||||||
@ -1099,61 +1079,38 @@ export async function selectPayCoinsNew(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Sort by available amount (descending), deposit fee (ascending) and
|
||||||
|
// denomPub (ascending) if deposit fee is the same
|
||||||
|
// (to guarantee deterministic results)
|
||||||
|
denoms.sort(
|
||||||
|
(o1, o2) =>
|
||||||
|
-Amounts.cmp(o1.value, o2.value) ||
|
||||||
|
Amounts.cmp(o1.feeDeposit, o2.feeDeposit) ||
|
||||||
|
strcmp(o1.denomPubHash, o2.denomPubHash),
|
||||||
|
);
|
||||||
return [denoms, wfPerExchange];
|
return [denoms, wfPerExchange];
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const coinPubs: string[] = [];
|
/**
|
||||||
const coinContributions: AmountJson[] = [];
|
* Selection result.
|
||||||
const currency = contractTermsAmount.currency;
|
*/
|
||||||
|
interface SelResult {
|
||||||
let tally: CoinSelectionTally = {
|
/**
|
||||||
amountPayRemaining: contractTermsAmount,
|
* Map from denomination public key hashes
|
||||||
amountWireFeeLimitRemaining: wireFeeLimit,
|
* to an array of contributions.
|
||||||
amountDepositFeeLimitRemaining: depositFeeLimit,
|
*/
|
||||||
customerDepositFees: Amounts.getZero(currency),
|
[dph: string]: AmountJson[];
|
||||||
customerWireFees: Amounts.getZero(currency),
|
}
|
||||||
wireFeeCoveredForExchange: new Set(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const prevPayCoins = req.prevPayCoins ?? [];
|
|
||||||
|
|
||||||
// Look at existing pay coin selection and tally up
|
|
||||||
for (const prev of prevPayCoins) {
|
|
||||||
tally = tallyFees(
|
|
||||||
tally,
|
|
||||||
wireFeesPerExchange,
|
|
||||||
wireFeeAmortization,
|
|
||||||
prev.exchangeBaseUrl,
|
|
||||||
prev.feeDeposit,
|
|
||||||
);
|
|
||||||
tally.amountPayRemaining = Amounts.sub(
|
|
||||||
tally.amountPayRemaining,
|
|
||||||
prev.contribution,
|
|
||||||
).amount;
|
|
||||||
|
|
||||||
coinPubs.push(prev.coinPub);
|
|
||||||
coinContributions.push(prev.contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by available amount (descending), deposit fee (ascending) and
|
|
||||||
// denomPub (ascending) if deposit fee is the same
|
|
||||||
// (to guarantee deterministic results)
|
|
||||||
candidateDenoms.sort(
|
|
||||||
(o1, o2) =>
|
|
||||||
-Amounts.cmp(o1.value, o2.value) ||
|
|
||||||
Amounts.cmp(o1.feeDeposit, o2.feeDeposit) ||
|
|
||||||
strcmp(o1.denomPubHash, o2.denomPubHash),
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Here, we should select coins in a smarter way.
|
|
||||||
// Instead of always spending the next-largest coin,
|
|
||||||
// we should try to find the smallest coin that covers the
|
|
||||||
// amount.
|
|
||||||
|
|
||||||
const selectedDenom: {
|
|
||||||
[dph: string]: AmountJson[];
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
|
export function selectGreedy(
|
||||||
|
req: SelectPayCoinRequestNg,
|
||||||
|
candidateDenoms: AvailableDenom[],
|
||||||
|
wireFeesPerExchange: Record<string, AmountJson>,
|
||||||
|
tally: CoinSelectionTally,
|
||||||
|
): SelResult | undefined {
|
||||||
|
const { wireFeeAmortization } = req;
|
||||||
|
const selectedDenom: SelResult = {};
|
||||||
for (const aci of candidateDenoms) {
|
for (const aci of candidateDenoms) {
|
||||||
const contributions: AmountJson[] = [];
|
const contributions: AmountJson[] = [];
|
||||||
for (let i = 0; i < aci.numAvailable; i++) {
|
for (let i = 0; i < aci.numAvailable; i++) {
|
||||||
@ -1193,37 +1150,153 @@ export async function selectPayCoinsNew(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Amounts.isZero(tally.amountPayRemaining)) {
|
if (Amounts.isZero(tally.amountPayRemaining)) {
|
||||||
await ws.db
|
return selectedDenom;
|
||||||
.mktx((x) => [x.coins, x.denominations])
|
|
||||||
.runReadOnly(async (tx) => {
|
|
||||||
for (const dph of Object.keys(selectedDenom)) {
|
|
||||||
const contributions = selectedDenom[dph];
|
|
||||||
const coins = await tx.coins.indexes.byDenomPubHashAndStatus.getAll(
|
|
||||||
[dph, CoinStatus.Fresh],
|
|
||||||
contributions.length,
|
|
||||||
);
|
|
||||||
if (coins.length != contributions.length) {
|
|
||||||
throw Error(
|
|
||||||
`coin selection failed (not available anymore, got only ${coins.length}/${contributions.length})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
coinPubs.push(...coins.map((x) => x.coinPub));
|
|
||||||
coinContributions.push(...contributions);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
paymentAmount: contractTermsAmount,
|
|
||||||
coinContributions,
|
|
||||||
coinPubs,
|
|
||||||
customerDepositFees: tally.customerDepositFees,
|
|
||||||
customerWireFees: tally.customerWireFees,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectForced(
|
||||||
|
req: SelectPayCoinRequestNg,
|
||||||
|
candidateDenoms: AvailableDenom[],
|
||||||
|
): SelResult | undefined {
|
||||||
|
const selectedDenom: SelResult = {};
|
||||||
|
|
||||||
|
const forcedSelection = req.forcedSelection;
|
||||||
|
checkLogicInvariant(!!forcedSelection);
|
||||||
|
|
||||||
|
for (const forcedCoin of forcedSelection.coins) {
|
||||||
|
let found = false;
|
||||||
|
for (const aci of candidateDenoms) {
|
||||||
|
if (aci.numAvailable <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Amounts.cmp(aci.value, forcedCoin.value) === 0) {
|
||||||
|
aci.numAvailable--;
|
||||||
|
const contributions = selectedDenom[aci.denomPubHash] ?? [];
|
||||||
|
contributions.push(Amounts.parseOrThrow(forcedCoin.value));
|
||||||
|
selectedDenom[aci.denomPubHash] = contributions;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
throw Error("can't find coin for forced coin selection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedDenom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of candidate coins, select coins to spend under the merchant's
|
||||||
|
* constraints.
|
||||||
|
*
|
||||||
|
* The prevPayCoins can be specified to "repair" a coin selection
|
||||||
|
* by adding additional coins, after a broken (e.g. double-spent) coin
|
||||||
|
* has been removed from the selection.
|
||||||
|
*
|
||||||
|
* This function is only exported for the sake of unit tests.
|
||||||
|
*/
|
||||||
|
export async function selectPayCoinsNew(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: SelectPayCoinRequestNg,
|
||||||
|
): Promise<PayCoinSelection | undefined> {
|
||||||
|
const {
|
||||||
|
contractTermsAmount,
|
||||||
|
depositFeeLimit,
|
||||||
|
wireFeeLimit,
|
||||||
|
wireFeeAmortization,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const [candidateDenoms, wireFeesPerExchange] = await selectCandidates(
|
||||||
|
ws,
|
||||||
|
req,
|
||||||
|
);
|
||||||
|
|
||||||
|
const coinPubs: string[] = [];
|
||||||
|
const coinContributions: AmountJson[] = [];
|
||||||
|
const currency = contractTermsAmount.currency;
|
||||||
|
|
||||||
|
let tally: CoinSelectionTally = {
|
||||||
|
amountPayRemaining: contractTermsAmount,
|
||||||
|
amountWireFeeLimitRemaining: wireFeeLimit,
|
||||||
|
amountDepositFeeLimitRemaining: depositFeeLimit,
|
||||||
|
customerDepositFees: Amounts.getZero(currency),
|
||||||
|
customerWireFees: Amounts.getZero(currency),
|
||||||
|
wireFeeCoveredForExchange: new Set(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevPayCoins = req.prevPayCoins ?? [];
|
||||||
|
|
||||||
|
// Look at existing pay coin selection and tally up
|
||||||
|
for (const prev of prevPayCoins) {
|
||||||
|
tally = tallyFees(
|
||||||
|
tally,
|
||||||
|
wireFeesPerExchange,
|
||||||
|
wireFeeAmortization,
|
||||||
|
prev.exchangeBaseUrl,
|
||||||
|
prev.feeDeposit,
|
||||||
|
);
|
||||||
|
tally.amountPayRemaining = Amounts.sub(
|
||||||
|
tally.amountPayRemaining,
|
||||||
|
prev.contribution,
|
||||||
|
).amount;
|
||||||
|
|
||||||
|
coinPubs.push(prev.coinPub);
|
||||||
|
coinContributions.push(prev.contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedDenom: SelResult | undefined;
|
||||||
|
if (req.forcedSelection) {
|
||||||
|
selectedDenom = selectForced(req, candidateDenoms);
|
||||||
|
} else {
|
||||||
|
// FIXME: Here, we should select coins in a smarter way.
|
||||||
|
// Instead of always spending the next-largest coin,
|
||||||
|
// we should try to find the smallest coin that covers the
|
||||||
|
// amount.
|
||||||
|
selectedDenom = selectGreedy(
|
||||||
|
req,
|
||||||
|
candidateDenoms,
|
||||||
|
wireFeesPerExchange,
|
||||||
|
tally,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedDenom) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalSel = selectedDenom;
|
||||||
|
|
||||||
|
await ws.db
|
||||||
|
.mktx((x) => [x.coins, x.denominations])
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
for (const dph of Object.keys(finalSel)) {
|
||||||
|
const contributions = finalSel[dph];
|
||||||
|
const coins = await tx.coins.indexes.byDenomPubHashAndStatus.getAll(
|
||||||
|
[dph, CoinStatus.Fresh],
|
||||||
|
contributions.length,
|
||||||
|
);
|
||||||
|
if (coins.length != contributions.length) {
|
||||||
|
throw Error(
|
||||||
|
`coin selection failed (not available anymore, got only ${coins.length}/${contributions.length})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
coinPubs.push(...coins.map((x) => x.coinPub));
|
||||||
|
coinContributions.push(...contributions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentAmount: contractTermsAmount,
|
||||||
|
coinContributions,
|
||||||
|
coinPubs,
|
||||||
|
customerDepositFees: tally.customerDepositFees,
|
||||||
|
customerWireFees: tally.customerWireFees,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkPaymentByProposalId(
|
export async function checkPaymentByProposalId(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
proposalId: string,
|
proposalId: string,
|
||||||
@ -1274,24 +1347,16 @@ export async function checkPaymentByProposalId(
|
|||||||
|
|
||||||
if (!purchase) {
|
if (!purchase) {
|
||||||
// If not already paid, check if we could pay for it.
|
// If not already paid, check if we could pay for it.
|
||||||
const candidates = await getCandidatePayCoins(ws, {
|
const res = await selectPayCoinsNew(ws, {
|
||||||
allowedAuditors: contractData.allowedAuditors,
|
auditors: contractData.allowedAuditors,
|
||||||
allowedExchanges: contractData.allowedExchanges,
|
exchanges: contractData.allowedExchanges,
|
||||||
amount: contractData.amount,
|
|
||||||
maxDepositFee: contractData.maxDepositFee,
|
|
||||||
maxWireFee: contractData.maxWireFee,
|
|
||||||
timestamp: contractData.timestamp,
|
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization,
|
|
||||||
wireMethod: contractData.wireMethod,
|
|
||||||
});
|
|
||||||
const res = selectPayCoinsLegacy({
|
|
||||||
candidates,
|
|
||||||
contractTermsAmount: contractData.amount,
|
contractTermsAmount: contractData.amount,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
||||||
wireFeeLimit: contractData.maxWireFee,
|
wireFeeLimit: contractData.maxWireFee,
|
||||||
prevPayCoins: [],
|
prevPayCoins: [],
|
||||||
requiredMinimumAge: contractData.minimumAge,
|
requiredMinimumAge: contractData.minimumAge,
|
||||||
|
wireMethod: contractData.wireMethod,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
@ -1590,39 +1655,20 @@ export async function confirmPay(
|
|||||||
|
|
||||||
const contractData = d.contractData;
|
const contractData = d.contractData;
|
||||||
|
|
||||||
const candidates = await getCandidatePayCoins(ws, {
|
|
||||||
allowedAuditors: contractData.allowedAuditors,
|
|
||||||
allowedExchanges: contractData.allowedExchanges,
|
|
||||||
amount: contractData.amount,
|
|
||||||
maxDepositFee: contractData.maxDepositFee,
|
|
||||||
maxWireFee: contractData.maxWireFee,
|
|
||||||
timestamp: contractData.timestamp,
|
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization,
|
|
||||||
wireMethod: contractData.wireMethod,
|
|
||||||
});
|
|
||||||
|
|
||||||
let res: PayCoinSelection | undefined = undefined;
|
let res: PayCoinSelection | undefined = undefined;
|
||||||
|
|
||||||
if (forcedCoinSel) {
|
res = await selectPayCoinsNew(ws, {
|
||||||
res = selectForcedPayCoins(forcedCoinSel, {
|
auditors: contractData.allowedAuditors,
|
||||||
candidates,
|
exchanges: contractData.allowedExchanges,
|
||||||
contractTermsAmount: contractData.amount,
|
wireMethod: contractData.wireMethod,
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
contractTermsAmount: contractData.amount,
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
depositFeeLimit: contractData.maxDepositFee,
|
||||||
wireFeeLimit: contractData.maxWireFee,
|
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
||||||
requiredMinimumAge: contractData.minimumAge,
|
wireFeeLimit: contractData.maxWireFee,
|
||||||
});
|
prevPayCoins: [],
|
||||||
} else {
|
requiredMinimumAge: contractData.minimumAge,
|
||||||
res = selectPayCoinsLegacy({
|
forcedSelection: forcedCoinSel,
|
||||||
candidates,
|
});
|
||||||
contractTermsAmount: contractData.amount,
|
|
||||||
depositFeeLimit: contractData.maxDepositFee,
|
|
||||||
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
|
||||||
wireFeeLimit: contractData.maxWireFee,
|
|
||||||
prevPayCoins: [],
|
|
||||||
requiredMinimumAge: contractData.minimumAge,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.trace("coin selection result", res);
|
logger.trace("coin selection result", res);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user