create a fee description timeline for global fee and wire fees
This commit is contained in:
parent
cb44202440
commit
610df1c9cf
@ -1091,17 +1091,21 @@ export interface BackupExchangeWireFee {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export interface BackupExchangeGlobalFees {
|
export interface BackupExchangeGlobalFees {
|
||||||
start_date: TalerProtocolTimestamp;
|
startDate: TalerProtocolTimestamp;
|
||||||
end_date: TalerProtocolTimestamp;
|
endDate: TalerProtocolTimestamp;
|
||||||
kyc_fee: BackupAmountString;
|
|
||||||
history_fee: BackupAmountString;
|
kycFee: BackupAmountString;
|
||||||
account_fee: BackupAmountString;
|
historyFee: BackupAmountString;
|
||||||
purse_fee: BackupAmountString;
|
accountFee: BackupAmountString;
|
||||||
history_expiration: TalerProtocolDuration;
|
purseFee: BackupAmountString;
|
||||||
account_kyc_timeout: TalerProtocolDuration;
|
|
||||||
purse_account_limit: number;
|
historyTimeout: TalerProtocolDuration;
|
||||||
purse_timeout: TalerProtocolDuration;
|
kycTimeout: TalerProtocolDuration;
|
||||||
master_sig: string;
|
purseTimeout: TalerProtocolDuration;
|
||||||
|
|
||||||
|
purseLimit: number;
|
||||||
|
|
||||||
|
signature: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Structure of one exchange signing key in the /keys response.
|
* Structure of one exchange signing key in the /keys response.
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
AbsoluteTime,
|
AbsoluteTime,
|
||||||
codecForAbsoluteTime,
|
codecForAbsoluteTime,
|
||||||
codecForTimestamp,
|
codecForTimestamp,
|
||||||
|
TalerProtocolDuration,
|
||||||
TalerProtocolTimestamp,
|
TalerProtocolTimestamp,
|
||||||
} from "./time.js";
|
} from "./time.js";
|
||||||
import {
|
import {
|
||||||
@ -673,6 +674,23 @@ export interface WireInfo {
|
|||||||
accounts: ExchangeAccount[];
|
accounts: ExchangeAccount[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExchangeGlobalFees {
|
||||||
|
startDate: TalerProtocolTimestamp;
|
||||||
|
endDate: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
kycFee: AmountJson;
|
||||||
|
historyFee: AmountJson;
|
||||||
|
accountFee: AmountJson;
|
||||||
|
purseFee: AmountJson;
|
||||||
|
|
||||||
|
historyTimeout: TalerProtocolDuration;
|
||||||
|
kycTimeout: TalerProtocolDuration;
|
||||||
|
purseTimeout: TalerProtocolDuration;
|
||||||
|
|
||||||
|
purseLimit: number;
|
||||||
|
|
||||||
|
signature: string;
|
||||||
|
}
|
||||||
const codecForExchangeAccount = (): Codec<ExchangeAccount> =>
|
const codecForExchangeAccount = (): Codec<ExchangeAccount> =>
|
||||||
buildCodecForObject<ExchangeAccount>()
|
buildCodecForObject<ExchangeAccount>()
|
||||||
.property("payto_uri", codecForString())
|
.property("payto_uri", codecForString())
|
||||||
@ -752,28 +770,31 @@ export interface DenominationInfo {
|
|||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Operation = "deposit" | "withdraw" | "refresh" | "refund";
|
export type DenomOperation = "deposit" | "withdraw" | "refresh" | "refund";
|
||||||
export type OperationMap<T> = { [op in Operation]: T };
|
export type DenomOperationMap<T> = { [op in DenomOperation]: T };
|
||||||
|
|
||||||
export interface FeeDescription {
|
export interface FeeDescription {
|
||||||
value: AmountJson;
|
group: string;
|
||||||
from: AbsoluteTime;
|
from: AbsoluteTime;
|
||||||
until: AbsoluteTime;
|
until: AbsoluteTime;
|
||||||
fee?: AmountJson;
|
fee?: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FeeDescriptionPair {
|
export interface FeeDescriptionPair {
|
||||||
value: AmountJson;
|
group: string;
|
||||||
from: AbsoluteTime;
|
from: AbsoluteTime;
|
||||||
until: AbsoluteTime;
|
until: AbsoluteTime;
|
||||||
left?: AmountJson;
|
left?: AmountJson;
|
||||||
right?: AmountJson;
|
right?: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimePoint {
|
export interface TimePoint<T> {
|
||||||
|
id: string;
|
||||||
|
group: string;
|
||||||
|
fee: AmountJson;
|
||||||
type: "start" | "end";
|
type: "start" | "end";
|
||||||
moment: AbsoluteTime;
|
moment: AbsoluteTime;
|
||||||
denom: DenominationInfo;
|
denom: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeFullDetails {
|
export interface ExchangeFullDetails {
|
||||||
@ -783,7 +804,9 @@ export interface ExchangeFullDetails {
|
|||||||
tos: ExchangeTos;
|
tos: ExchangeTos;
|
||||||
auditors: ExchangeAuditor[];
|
auditors: ExchangeAuditor[];
|
||||||
wireInfo: WireInfo;
|
wireInfo: WireInfo;
|
||||||
feesDescription: OperationMap<FeeDescription[]>;
|
denomFees: DenomOperationMap<FeeDescription[]>;
|
||||||
|
transferFees: Record<string, FeeDescription[]>;
|
||||||
|
globalFees: FeeDescription[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeListItem {
|
export interface ExchangeListItem {
|
||||||
@ -816,7 +839,7 @@ const codecForExchangeTos = (): Codec<ExchangeTos> =>
|
|||||||
|
|
||||||
export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> =>
|
export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> =>
|
||||||
buildCodecForObject<FeeDescriptionPair>()
|
buildCodecForObject<FeeDescriptionPair>()
|
||||||
.property("value", codecForAmountJson())
|
.property("group", codecForString())
|
||||||
.property("from", codecForAbsoluteTime)
|
.property("from", codecForAbsoluteTime)
|
||||||
.property("until", codecForAbsoluteTime)
|
.property("until", codecForAbsoluteTime)
|
||||||
.property("left", codecOptional(codecForAmountJson()))
|
.property("left", codecOptional(codecForAmountJson()))
|
||||||
@ -825,21 +848,21 @@ export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> =>
|
|||||||
|
|
||||||
export const codecForFeeDescription = (): Codec<FeeDescription> =>
|
export const codecForFeeDescription = (): Codec<FeeDescription> =>
|
||||||
buildCodecForObject<FeeDescription>()
|
buildCodecForObject<FeeDescription>()
|
||||||
.property("value", codecForAmountJson())
|
.property("group", codecForString())
|
||||||
.property("from", codecForAbsoluteTime)
|
.property("from", codecForAbsoluteTime)
|
||||||
.property("until", codecForAbsoluteTime)
|
.property("until", codecForAbsoluteTime)
|
||||||
.property("fee", codecOptional(codecForAmountJson()))
|
.property("fee", codecOptional(codecForAmountJson()))
|
||||||
.build("FeeDescription");
|
.build("FeeDescription");
|
||||||
|
|
||||||
export const codecForFeesByOperations = (): Codec<
|
export const codecForFeesByOperations = (): Codec<
|
||||||
OperationMap<FeeDescription[]>
|
DenomOperationMap<FeeDescription[]>
|
||||||
> =>
|
> =>
|
||||||
buildCodecForObject<OperationMap<FeeDescription[]>>()
|
buildCodecForObject<DenomOperationMap<FeeDescription[]>>()
|
||||||
.property("deposit", codecForList(codecForFeeDescription()))
|
.property("deposit", codecForList(codecForFeeDescription()))
|
||||||
.property("withdraw", codecForList(codecForFeeDescription()))
|
.property("withdraw", codecForList(codecForFeeDescription()))
|
||||||
.property("refresh", codecForList(codecForFeeDescription()))
|
.property("refresh", codecForList(codecForFeeDescription()))
|
||||||
.property("refund", codecForList(codecForFeeDescription()))
|
.property("refund", codecForList(codecForFeeDescription()))
|
||||||
.build("FeesByOperations");
|
.build("DenomOperationMap");
|
||||||
|
|
||||||
export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> =>
|
export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> =>
|
||||||
buildCodecForObject<ExchangeFullDetails>()
|
buildCodecForObject<ExchangeFullDetails>()
|
||||||
@ -849,7 +872,12 @@ export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> =>
|
|||||||
.property("tos", codecForExchangeTos())
|
.property("tos", codecForExchangeTos())
|
||||||
.property("auditors", codecForList(codecForExchangeAuditor()))
|
.property("auditors", codecForList(codecForExchangeAuditor()))
|
||||||
.property("wireInfo", codecForWireInfo())
|
.property("wireInfo", codecForWireInfo())
|
||||||
.property("feesDescription", codecForFeesByOperations())
|
.property("denomFees", codecForFeesByOperations())
|
||||||
|
.property(
|
||||||
|
"transferFees",
|
||||||
|
codecForMap(codecForList(codecForFeeDescription())),
|
||||||
|
)
|
||||||
|
.property("globalFees", codecForList(codecForFeeDescription()))
|
||||||
.build("ExchangeFullDetails");
|
.build("ExchangeFullDetails");
|
||||||
|
|
||||||
export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
|
export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
|
||||||
|
@ -53,7 +53,6 @@ export class CryptoRpcClient {
|
|||||||
this.proc.unref();
|
this.proc.unref();
|
||||||
|
|
||||||
this.proc.stdout.on("data", (x) => {
|
this.proc.stdout.on("data", (x) => {
|
||||||
// console.log("got chunk", x.toString("utf-8"));
|
|
||||||
if (x instanceof Buffer) {
|
if (x instanceof Buffer) {
|
||||||
const nlIndex = x.indexOf("\n");
|
const nlIndex = x.indexOf("\n");
|
||||||
if (nlIndex >= 0) {
|
if (nlIndex >= 0) {
|
||||||
|
@ -46,6 +46,7 @@ import {
|
|||||||
WireInfo,
|
WireInfo,
|
||||||
DenominationInfo,
|
DenominationInfo,
|
||||||
GlobalFees,
|
GlobalFees,
|
||||||
|
ExchangeGlobalFees,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { RetryInfo, RetryTags } from "./util/retries.js";
|
import { RetryInfo, RetryTags } from "./util/retries.js";
|
||||||
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
||||||
@ -428,7 +429,7 @@ export interface ExchangeDetailsRecord {
|
|||||||
/**
|
/**
|
||||||
* Fees for exchange services
|
* Fees for exchange services
|
||||||
*/
|
*/
|
||||||
globalFees: GlobalFees[];
|
globalFees: ExchangeGlobalFees[];
|
||||||
/**
|
/**
|
||||||
* Signing keys we got from the exchange, can also contain
|
* Signing keys we got from the exchange, can also contain
|
||||||
* older signing keys that are not returned by /keys anymore.
|
* older signing keys that are not returned by /keys anymore.
|
||||||
|
@ -345,7 +345,19 @@ export async function exportBackup(
|
|||||||
stamp_expire: x.stamp_expire,
|
stamp_expire: x.stamp_expire,
|
||||||
stamp_start: x.stamp_start,
|
stamp_start: x.stamp_start,
|
||||||
})),
|
})),
|
||||||
global_fees: ex.globalFees,
|
global_fees: ex.globalFees.map((x) => ({
|
||||||
|
accountFee: Amounts.stringify(x.accountFee),
|
||||||
|
historyFee: Amounts.stringify(x.historyFee),
|
||||||
|
kycFee: Amounts.stringify(x.kycFee),
|
||||||
|
purseFee: Amounts.stringify(x.purseFee),
|
||||||
|
kycTimeout: x.kycTimeout,
|
||||||
|
endDate: x.endDate,
|
||||||
|
historyTimeout: x.historyTimeout,
|
||||||
|
signature: x.signature,
|
||||||
|
purseLimit: x.purseLimit,
|
||||||
|
purseTimeout: x.purseTimeout,
|
||||||
|
startDate: x.startDate,
|
||||||
|
})),
|
||||||
tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
|
tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
|
||||||
tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
|
tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
|
||||||
denominations:
|
denominations:
|
||||||
|
@ -405,7 +405,20 @@ export async function importBackup(
|
|||||||
masterPublicKey: backupExchangeDetails.master_public_key,
|
masterPublicKey: backupExchangeDetails.master_public_key,
|
||||||
protocolVersion: backupExchangeDetails.protocol_version,
|
protocolVersion: backupExchangeDetails.protocol_version,
|
||||||
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
|
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
|
||||||
globalFees: backupExchangeDetails.global_fees,
|
globalFees: backupExchangeDetails.global_fees.map((x) => ({
|
||||||
|
accountFee: Amounts.parseOrThrow(x.accountFee),
|
||||||
|
historyFee: Amounts.parseOrThrow(x.historyFee),
|
||||||
|
kycFee: Amounts.parseOrThrow(x.kycFee),
|
||||||
|
purseFee: Amounts.parseOrThrow(x.purseFee),
|
||||||
|
kycTimeout: x.kycTimeout,
|
||||||
|
endDate: x.endDate,
|
||||||
|
historyTimeout: x.historyTimeout,
|
||||||
|
signature: x.signature,
|
||||||
|
purseLimit: x.purseLimit,
|
||||||
|
purseTimeout: x.purseTimeout,
|
||||||
|
startDate: x.startDate,
|
||||||
|
})),
|
||||||
|
|
||||||
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
|
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
|
||||||
key: x.key,
|
key: x.key,
|
||||||
master_sig: x.master_sig,
|
master_sig: x.master_sig,
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
encodeCrock,
|
encodeCrock,
|
||||||
ExchangeAuditor,
|
ExchangeAuditor,
|
||||||
ExchangeDenomination,
|
ExchangeDenomination,
|
||||||
|
ExchangeGlobalFees,
|
||||||
ExchangeSignKeyJson,
|
ExchangeSignKeyJson,
|
||||||
ExchangeWireJson,
|
ExchangeWireJson,
|
||||||
GlobalFees,
|
GlobalFees,
|
||||||
@ -274,7 +275,8 @@ async function validateGlobalFees(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
fees: GlobalFees[],
|
fees: GlobalFees[],
|
||||||
masterPub: string,
|
masterPub: string,
|
||||||
): Promise<GlobalFees[]> {
|
): Promise<ExchangeGlobalFees[]> {
|
||||||
|
const egf: ExchangeGlobalFees[] = [];
|
||||||
for (const gf of fees) {
|
for (const gf of fees) {
|
||||||
logger.trace("validating exchange global fees");
|
logger.trace("validating exchange global fees");
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
@ -291,9 +293,22 @@ async function validateGlobalFees(
|
|||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
throw Error("exchange global fees signature invalid: " + gf.master_sig);
|
throw Error("exchange global fees signature invalid: " + gf.master_sig);
|
||||||
}
|
}
|
||||||
|
egf.push({
|
||||||
|
accountFee: Amounts.parseOrThrow(gf.account_fee),
|
||||||
|
historyFee: Amounts.parseOrThrow(gf.history_fee),
|
||||||
|
purseFee: Amounts.parseOrThrow(gf.purse_fee),
|
||||||
|
kycFee: Amounts.parseOrThrow(gf.kyc_fee),
|
||||||
|
startDate: gf.start_date,
|
||||||
|
endDate: gf.end_date,
|
||||||
|
signature: gf.master_sig,
|
||||||
|
historyTimeout: gf.history_expiration,
|
||||||
|
kycTimeout: gf.account_kyc_timeout,
|
||||||
|
purseLimit: gf.purse_account_limit,
|
||||||
|
purseTimeout: gf.purse_timeout,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return fees;
|
return egf;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeInfo {
|
export interface ExchangeInfo {
|
||||||
|
@ -28,8 +28,9 @@ import {
|
|||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
// import { expect } from "chai";
|
// import { expect } from "chai";
|
||||||
import {
|
import {
|
||||||
createDenominationPairTimeline,
|
createPairTimeline,
|
||||||
createDenominationTimeline,
|
createTimeline,
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
} from "./denominations.js";
|
} from "./denominations.js";
|
||||||
import test, { ExecutionContext } from "ava";
|
import test, { ExecutionContext } from "ava";
|
||||||
|
|
||||||
@ -42,8 +43,14 @@ const VALUES = Array.from({ length: 10 }).map((undef, t) =>
|
|||||||
const TIMESTAMPS = Array.from({ length: 20 }).map((undef, t_s) => ({ t_s }));
|
const TIMESTAMPS = Array.from({ length: 20 }).map((undef, t_s) => ({ t_s }));
|
||||||
const ABS_TIME = TIMESTAMPS.map((m) => AbsoluteTime.fromTimestamp(m));
|
const ABS_TIME = TIMESTAMPS.map((m) => AbsoluteTime.fromTimestamp(m));
|
||||||
|
|
||||||
function normalize(list: DenominationInfo[]): DenominationInfo[] {
|
function normalize(
|
||||||
return list.map((e, idx) => ({ ...e, denomPubHash: `id${idx}` }));
|
list: DenominationInfo[],
|
||||||
|
): (DenominationInfo & { group: string })[] {
|
||||||
|
return list.map((e, idx) => ({
|
||||||
|
...e,
|
||||||
|
denomPubHash: `id${idx}`,
|
||||||
|
group: Amounts.stringifyValue(e.value),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Avoiding to make an error-prone/time-consuming refactor
|
//Avoiding to make an error-prone/time-consuming refactor
|
||||||
@ -61,7 +68,7 @@ function expect(t: ExecutionContext, thing: any): any {
|
|||||||
// describe("single value example", (t) => {
|
// describe("single value example", (t) => {
|
||||||
|
|
||||||
test("should have one row with start and exp", (t) => {
|
test("should have one row with start and exp", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -70,13 +77,17 @@ test("should have one row with start and exp", (t) => {
|
|||||||
feeDeposit: VALUES[1],
|
feeDeposit: VALUES[1],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -85,7 +96,7 @@ test("should have one row with start and exp", (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should have two rows with the second denom in the middle if second is better", (t) => {
|
test("should have two rows with the second denom in the middle if second is better", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -100,19 +111,23 @@ test("should have two rows with the second denom in the middle if second is bett
|
|||||||
feeDeposit: VALUES[2],
|
feeDeposit: VALUES[2],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[3],
|
from: ABS_TIME[3],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -121,7 +136,7 @@ test("should have two rows with the second denom in the middle if second is bett
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should have two rows with the first denom in the middle if second is worse", (t) => {
|
test("should have two rows with the first denom in the middle if second is worse", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -136,19 +151,23 @@ test("should have two rows with the first denom in the middle if second is worse
|
|||||||
feeDeposit: VALUES[1],
|
feeDeposit: VALUES[1],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -157,7 +176,7 @@ test("should have two rows with the first denom in the middle if second is worse
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should add a gap when there no fee", (t) => {
|
test("should add a gap when there no fee", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -172,24 +191,28 @@ test("should add a gap when there no fee", (t) => {
|
|||||||
feeDeposit: VALUES[1],
|
feeDeposit: VALUES[1],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[3],
|
from: ABS_TIME[3],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -198,7 +221,7 @@ test("should add a gap when there no fee", (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should have three rows when first denom is between second and second is worse", (t) => {
|
test("should have three rows when first denom is between second and second is worse", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -213,24 +236,28 @@ test("should have three rows when first denom is between second and second is wo
|
|||||||
feeDeposit: VALUES[2],
|
feeDeposit: VALUES[2],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[3],
|
from: ABS_TIME[3],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -239,7 +266,7 @@ test("should have three rows when first denom is between second and second is wo
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should have one row when first denom is between second and second is better", (t) => {
|
test("should have one row when first denom is between second and second is better", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -254,13 +281,17 @@ test("should have one row when first denom is between second and second is bette
|
|||||||
feeDeposit: VALUES[1],
|
feeDeposit: VALUES[1],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -269,7 +300,7 @@ test("should have one row when first denom is between second and second is bette
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should only add the best1", (t) => {
|
test("should only add the best1", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -290,19 +321,23 @@ test("should only add the best1", (t) => {
|
|||||||
feeDeposit: VALUES[2],
|
feeDeposit: VALUES[2],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -311,7 +346,7 @@ test("should only add the best1", (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should only add the best2", (t) => {
|
test("should only add the best2", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -338,25 +373,29 @@ test("should only add the best2", (t) => {
|
|||||||
feeDeposit: VALUES[3],
|
feeDeposit: VALUES[3],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[5],
|
until: ABS_TIME[5],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[5],
|
from: ABS_TIME[5],
|
||||||
until: ABS_TIME[6],
|
until: ABS_TIME[6],
|
||||||
fee: VALUES[3],
|
fee: VALUES[3],
|
||||||
@ -365,7 +404,7 @@ test("should only add the best2", (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should only add the best3", (t) => {
|
test("should only add the best3", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -386,13 +425,17 @@ test("should only add the best3", (t) => {
|
|||||||
feeDeposit: VALUES[2],
|
feeDeposit: VALUES[2],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[5],
|
until: ABS_TIME[5],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -406,7 +449,7 @@ test("should only add the best3", (t) => {
|
|||||||
//TODO: test the same start but different value
|
//TODO: test the same start but different value
|
||||||
|
|
||||||
test("should not merge when there is different value", (t) => {
|
test("should not merge when there is different value", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -421,19 +464,23 @@ test("should not merge when there is different value", (t) => {
|
|||||||
feeDeposit: VALUES[2],
|
feeDeposit: VALUES[2],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -442,7 +489,7 @@ test("should not merge when there is different value", (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should not merge when there is different value (with duplicates)", (t) => {
|
test("should not merge when there is different value (with duplicates)", (t) => {
|
||||||
const timeline = createDenominationTimeline(
|
const timeline = createTimeline(
|
||||||
normalize([
|
normalize([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
value: VALUES[1],
|
||||||
@ -469,19 +516,23 @@ test("should not merge when there is different value (with duplicates)", (t) =>
|
|||||||
feeDeposit: VALUES[2],
|
feeDeposit: VALUES[2],
|
||||||
} as Partial<DenominationInfo> as DenominationInfo,
|
} as Partial<DenominationInfo> as DenominationInfo,
|
||||||
]),
|
]),
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(t, timeline).deep.equal([
|
expect(t, timeline).deep.equal([
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -519,7 +570,7 @@ test("should return empty", (t) => {
|
|||||||
const left = [] as FeeDescription[];
|
const left = [] as FeeDescription[];
|
||||||
const right = [] as FeeDescription[];
|
const right = [] as FeeDescription[];
|
||||||
|
|
||||||
const pairs = createDenominationPairTimeline(left, right);
|
const pairs = createPairTimeline(left, right);
|
||||||
|
|
||||||
expect(t, pairs).deep.equals([]);
|
expect(t, pairs).deep.equals([]);
|
||||||
});
|
});
|
||||||
@ -527,7 +578,7 @@ test("should return empty", (t) => {
|
|||||||
test("should return first element", (t) => {
|
test("should return first element", (t) => {
|
||||||
const left = [
|
const left = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -537,24 +588,24 @@ test("should return first element", (t) => {
|
|||||||
const right = [] as FeeDescription[];
|
const right = [] as FeeDescription[];
|
||||||
|
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(left, right);
|
const pairs = createPairTimeline(left, right);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
] as FeeDescriptionPair[]);
|
] as FeeDescriptionPair[]);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(right, left);
|
const pairs = createPairTimeline(right, left);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
right: VALUES[1],
|
right: VALUES[1],
|
||||||
left: undefined,
|
left: undefined,
|
||||||
},
|
},
|
||||||
@ -565,7 +616,7 @@ test("should return first element", (t) => {
|
|||||||
test("should add both to the same row", (t) => {
|
test("should add both to the same row", (t) => {
|
||||||
const left = [
|
const left = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -574,7 +625,7 @@ test("should add both to the same row", (t) => {
|
|||||||
|
|
||||||
const right = [
|
const right = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -582,24 +633,24 @@ test("should add both to the same row", (t) => {
|
|||||||
] as FeeDescription[];
|
] as FeeDescription[];
|
||||||
|
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(left, right);
|
const pairs = createPairTimeline(left, right);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: VALUES[2],
|
right: VALUES[2],
|
||||||
},
|
},
|
||||||
] as FeeDescriptionPair[]);
|
] as FeeDescriptionPair[]);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(right, left);
|
const pairs = createPairTimeline(right, left);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[2],
|
left: VALUES[2],
|
||||||
right: VALUES[1],
|
right: VALUES[1],
|
||||||
},
|
},
|
||||||
@ -610,7 +661,7 @@ test("should add both to the same row", (t) => {
|
|||||||
test("should repeat the first and change the second", (t) => {
|
test("should repeat the first and change the second", (t) => {
|
||||||
const left = [
|
const left = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[5],
|
until: ABS_TIME[5],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -619,18 +670,18 @@ test("should repeat the first and change the second", (t) => {
|
|||||||
|
|
||||||
const right = [
|
const right = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[3],
|
from: ABS_TIME[3],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[3],
|
fee: VALUES[3],
|
||||||
@ -638,33 +689,33 @@ test("should repeat the first and change the second", (t) => {
|
|||||||
] as FeeDescription[];
|
] as FeeDescription[];
|
||||||
|
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(left, right);
|
const pairs = createPairTimeline(left, right);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: VALUES[2],
|
right: VALUES[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[3],
|
from: ABS_TIME[3],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: VALUES[3],
|
right: VALUES[3],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[4],
|
from: ABS_TIME[4],
|
||||||
until: ABS_TIME[5],
|
until: ABS_TIME[5],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
@ -679,7 +730,7 @@ test("should repeat the first and change the second", (t) => {
|
|||||||
test("should separate denominations of different value", (t) => {
|
test("should separate denominations of different value", (t) => {
|
||||||
const left = [
|
const left = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
@ -688,7 +739,7 @@ test("should separate denominations of different value", (t) => {
|
|||||||
|
|
||||||
const right = [
|
const right = [
|
||||||
{
|
{
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -696,38 +747,38 @@ test("should separate denominations of different value", (t) => {
|
|||||||
] as FeeDescription[];
|
] as FeeDescription[];
|
||||||
|
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(left, right);
|
const pairs = createPairTimeline(left, right);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
left: undefined,
|
left: undefined,
|
||||||
right: VALUES[2],
|
right: VALUES[2],
|
||||||
},
|
},
|
||||||
] as FeeDescriptionPair[]);
|
] as FeeDescriptionPair[]);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(right, left);
|
const pairs = createPairTimeline(right, left);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: undefined,
|
left: undefined,
|
||||||
right: VALUES[1],
|
right: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
left: VALUES[2],
|
left: VALUES[2],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
@ -738,13 +789,13 @@ test("should separate denominations of different value", (t) => {
|
|||||||
test("should separate denominations of different value2", (t) => {
|
test("should separate denominations of different value2", (t) => {
|
||||||
const left = [
|
const left = [
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
fee: VALUES[1],
|
fee: VALUES[1],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -753,7 +804,7 @@ test("should separate denominations of different value2", (t) => {
|
|||||||
|
|
||||||
const right = [
|
const right = [
|
||||||
{
|
{
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
fee: VALUES[2],
|
fee: VALUES[2],
|
||||||
@ -761,26 +812,26 @@ test("should separate denominations of different value2", (t) => {
|
|||||||
] as FeeDescription[];
|
] as FeeDescription[];
|
||||||
|
|
||||||
{
|
{
|
||||||
const pairs = createDenominationPairTimeline(left, right);
|
const pairs = createPairTimeline(left, right);
|
||||||
expect(t, pairs).deep.equals([
|
expect(t, pairs).deep.equals([
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[2],
|
until: ABS_TIME[2],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[1],
|
left: VALUES[1],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[2],
|
from: ABS_TIME[2],
|
||||||
until: ABS_TIME[4],
|
until: ABS_TIME[4],
|
||||||
value: VALUES[1],
|
group: Amounts.stringifyValue(VALUES[1]),
|
||||||
left: VALUES[2],
|
left: VALUES[2],
|
||||||
right: undefined,
|
right: undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: ABS_TIME[1],
|
from: ABS_TIME[1],
|
||||||
until: ABS_TIME[3],
|
until: ABS_TIME[3],
|
||||||
value: VALUES[2],
|
group: Amounts.stringifyValue(VALUES[2]),
|
||||||
left: undefined,
|
left: undefined,
|
||||||
right: VALUES[2],
|
right: VALUES[2],
|
||||||
},
|
},
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
FeeDescriptionPair,
|
FeeDescriptionPair,
|
||||||
TalerProtocolTimestamp,
|
TalerProtocolTimestamp,
|
||||||
TimePoint,
|
TimePoint,
|
||||||
|
WireFee,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,9 +34,9 @@ import {
|
|||||||
* @param list denominations of same value
|
* @param list denominations of same value
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function selectBestForOverlappingDenominations(
|
export function selectBestForOverlappingDenominations<
|
||||||
list: DenominationInfo[],
|
T extends DenominationInfo,
|
||||||
): DenominationInfo | undefined {
|
>(list: T[]): T | undefined {
|
||||||
let minDeposit: DenominationInfo | undefined = undefined;
|
let minDeposit: DenominationInfo | undefined = undefined;
|
||||||
//TODO: improve denomination selection, this is a trivial implementation
|
//TODO: improve denomination selection, this is a trivial implementation
|
||||||
list.forEach((e) => {
|
list.forEach((e) => {
|
||||||
@ -50,6 +51,23 @@ function selectBestForOverlappingDenominations(
|
|||||||
return minDeposit;
|
return minDeposit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectMinimumFee<T extends { fee: AmountJson }>(
|
||||||
|
list: T[],
|
||||||
|
): T | undefined {
|
||||||
|
let minFee: T | undefined = undefined;
|
||||||
|
//TODO: improve denomination selection, this is a trivial implementation
|
||||||
|
list.forEach((e) => {
|
||||||
|
if (minFee === undefined) {
|
||||||
|
minFee = e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Amounts.cmp(minFee.fee, e.fee) > -1) {
|
||||||
|
minFee = e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return minFee;
|
||||||
|
}
|
||||||
|
|
||||||
type PropsWithReturnType<T extends object, F> = Exclude<
|
type PropsWithReturnType<T extends object, F> = Exclude<
|
||||||
{
|
{
|
||||||
[K in keyof T]: T[K] extends F ? K : never;
|
[K in keyof T]: T[K] extends F ? K : never;
|
||||||
@ -58,17 +76,19 @@ type PropsWithReturnType<T extends object, F> = Exclude<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes two list and create one with one timeline.
|
* Takes two timelines and create one to compare them.
|
||||||
* For any element in the position "p" on the left or right "list", then
|
|
||||||
* list[p].until should be equal to list[p+1].from
|
|
||||||
*
|
*
|
||||||
* @see {createDenominationTimeline}
|
* For both lists the next condition should be true:
|
||||||
|
* for any element in the position "idx" then
|
||||||
|
* list[idx].until === list[idx+1].from
|
||||||
|
*
|
||||||
|
* @see {createTimeline}
|
||||||
*
|
*
|
||||||
* @param left list denominations @type {FeeDescription}
|
* @param left list denominations @type {FeeDescription}
|
||||||
* @param right list denominations @type {FeeDescription}
|
* @param right list denominations @type {FeeDescription}
|
||||||
* @returns list of pairs for the same time
|
* @returns list of pairs for the same time
|
||||||
*/
|
*/
|
||||||
export function createDenominationPairTimeline(
|
export function createPairTimeline(
|
||||||
left: FeeDescription[],
|
left: FeeDescription[],
|
||||||
right: FeeDescription[],
|
right: FeeDescription[],
|
||||||
): FeeDescriptionPair[] {
|
): FeeDescriptionPair[] {
|
||||||
@ -81,23 +101,15 @@ export function createDenominationPairTimeline(
|
|||||||
let ri = 0;
|
let ri = 0;
|
||||||
|
|
||||||
while (li < left.length && ri < right.length) {
|
while (li < left.length && ri < right.length) {
|
||||||
const currentValue =
|
const currentGroup =
|
||||||
Amounts.cmp(left[li].value, right[ri].value) < 0
|
left[li].group < right[ri].group ? left[li].group : right[ri].group;
|
||||||
? left[li].value
|
|
||||||
: right[ri].value;
|
|
||||||
|
|
||||||
let ll = 0; //left length (until next value)
|
let ll = 0; //left length (until next value)
|
||||||
while (
|
while (li + ll < left.length && left[li + ll].group === currentGroup) {
|
||||||
li + ll < left.length &&
|
|
||||||
Amounts.cmp(left[li + ll].value, currentValue) === 0
|
|
||||||
) {
|
|
||||||
ll++;
|
ll++;
|
||||||
}
|
}
|
||||||
let rl = 0; //right length (until next value)
|
let rl = 0; //right length (until next value)
|
||||||
while (
|
while (ri + rl < right.length && right[ri + rl].group === currentGroup) {
|
||||||
ri + rl < right.length &&
|
|
||||||
Amounts.cmp(right[ri + rl].value, currentValue) === 0
|
|
||||||
) {
|
|
||||||
rl++;
|
rl++;
|
||||||
}
|
}
|
||||||
const leftIsEmpty = ll === 0;
|
const leftIsEmpty = ll === 0;
|
||||||
@ -120,7 +132,7 @@ export function createDenominationPairTimeline(
|
|||||||
right.splice(ri, 0, {
|
right.splice(ri, 0, {
|
||||||
from: leftStarts,
|
from: leftStarts,
|
||||||
until: ends,
|
until: ends,
|
||||||
value: left[li].value,
|
group: left[li].group,
|
||||||
});
|
});
|
||||||
rl++;
|
rl++;
|
||||||
|
|
||||||
@ -132,7 +144,7 @@ export function createDenominationPairTimeline(
|
|||||||
left.splice(li, 0, {
|
left.splice(li, 0, {
|
||||||
from: rightStarts,
|
from: rightStarts,
|
||||||
until: ends,
|
until: ends,
|
||||||
value: right[ri].value,
|
group: right[ri].group,
|
||||||
});
|
});
|
||||||
ll++;
|
ll++;
|
||||||
|
|
||||||
@ -148,7 +160,7 @@ export function createDenominationPairTimeline(
|
|||||||
right.splice(ri + rl, 0, {
|
right.splice(ri + rl, 0, {
|
||||||
from: rightEnds,
|
from: rightEnds,
|
||||||
until: leftEnds,
|
until: leftEnds,
|
||||||
value: left[0].value,
|
group: left[0].group,
|
||||||
});
|
});
|
||||||
rl++;
|
rl++;
|
||||||
}
|
}
|
||||||
@ -156,7 +168,7 @@ export function createDenominationPairTimeline(
|
|||||||
left.splice(li + ll, 0, {
|
left.splice(li + ll, 0, {
|
||||||
from: leftEnds,
|
from: leftEnds,
|
||||||
until: rightEnds,
|
until: rightEnds,
|
||||||
value: right[0].value,
|
group: right[0].group,
|
||||||
});
|
});
|
||||||
ll++;
|
ll++;
|
||||||
}
|
}
|
||||||
@ -165,7 +177,7 @@ export function createDenominationPairTimeline(
|
|||||||
while (
|
while (
|
||||||
li < left.length &&
|
li < left.length &&
|
||||||
ri < right.length &&
|
ri < right.length &&
|
||||||
Amounts.cmp(left[li].value, right[ri].value) === 0
|
left[li].group === right[ri].group
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
AbsoluteTime.cmp(left[li].from, timeCut) !== 0 &&
|
AbsoluteTime.cmp(left[li].from, timeCut) !== 0 &&
|
||||||
@ -186,7 +198,7 @@ export function createDenominationPairTimeline(
|
|||||||
right: right[ri].fee,
|
right: right[ri].fee,
|
||||||
from: timeCut,
|
from: timeCut,
|
||||||
until: AbsoluteTime.never(),
|
until: AbsoluteTime.never(),
|
||||||
value: currentValue,
|
group: currentGroup,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (left[li].until.t_ms === right[ri].until.t_ms) {
|
if (left[li].until.t_ms === right[ri].until.t_ms) {
|
||||||
@ -204,7 +216,7 @@ export function createDenominationPairTimeline(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
li < left.length &&
|
li < left.length &&
|
||||||
Amounts.cmp(left[li].value, pairList[pairList.length - 1].value) !== 0
|
left[li].group !== pairList[pairList.length - 1].group
|
||||||
) {
|
) {
|
||||||
//value changed, should break
|
//value changed, should break
|
||||||
//this if will catch when both (left and right) change at the same time
|
//this if will catch when both (left and right) change at the same time
|
||||||
@ -217,7 +229,7 @@ export function createDenominationPairTimeline(
|
|||||||
if (li < left.length) {
|
if (li < left.length) {
|
||||||
let timeCut =
|
let timeCut =
|
||||||
pairList.length > 0 &&
|
pairList.length > 0 &&
|
||||||
Amounts.cmp(pairList[pairList.length - 1].value, left[li].value) === 0
|
pairList[pairList.length - 1].group === left[li].group
|
||||||
? pairList[pairList.length - 1].until
|
? pairList[pairList.length - 1].until
|
||||||
: left[li].from;
|
: left[li].from;
|
||||||
while (li < left.length) {
|
while (li < left.length) {
|
||||||
@ -226,7 +238,7 @@ export function createDenominationPairTimeline(
|
|||||||
right: undefined,
|
right: undefined,
|
||||||
from: timeCut,
|
from: timeCut,
|
||||||
until: left[li].until,
|
until: left[li].until,
|
||||||
value: left[li].value,
|
group: left[li].group,
|
||||||
});
|
});
|
||||||
timeCut = left[li].until;
|
timeCut = left[li].until;
|
||||||
li++;
|
li++;
|
||||||
@ -235,7 +247,7 @@ export function createDenominationPairTimeline(
|
|||||||
if (ri < right.length) {
|
if (ri < right.length) {
|
||||||
let timeCut =
|
let timeCut =
|
||||||
pairList.length > 0 &&
|
pairList.length > 0 &&
|
||||||
Amounts.cmp(pairList[pairList.length - 1].value, right[ri].value) === 0
|
pairList[pairList.length - 1].group === right[ri].group
|
||||||
? pairList[pairList.length - 1].until
|
? pairList[pairList.length - 1].until
|
||||||
: right[ri].from;
|
: right[ri].from;
|
||||||
while (ri < right.length) {
|
while (ri < right.length) {
|
||||||
@ -244,7 +256,7 @@ export function createDenominationPairTimeline(
|
|||||||
left: undefined,
|
left: undefined,
|
||||||
from: timeCut,
|
from: timeCut,
|
||||||
until: right[ri].until,
|
until: right[ri].until,
|
||||||
value: right[ri].value,
|
group: right[ri].group,
|
||||||
});
|
});
|
||||||
timeCut = right[ri].until;
|
timeCut = right[ri].until;
|
||||||
ri++;
|
ri++;
|
||||||
@ -254,42 +266,70 @@ export function createDenominationPairTimeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a usage timeline with the denominations given.
|
* Create a usage timeline with the entity given.
|
||||||
*
|
*
|
||||||
* If there are multiple denominations that can be used, the list will
|
* If there are multiple entities that can be used in the same period,
|
||||||
* contain the one that minimize the fee cost. @see selectBestForOverlappingDenominations
|
* the list will contain the one that minimize the fee cost.
|
||||||
|
* @see selectBestForOverlappingDenominations
|
||||||
*
|
*
|
||||||
* @param list list of denominations
|
* @param list list of entities
|
||||||
* @param periodProp property of element of the list that will be used as end of the usage period
|
* @param idProp property used for identification
|
||||||
|
* @param periodStartProp property of element of the list that will be used as start of the usage period
|
||||||
|
* @param periodEndProp property of element of the list that will be used as end of the usage period
|
||||||
* @param feeProp property of the element of the list that will be used as fee reference
|
* @param feeProp property of the element of the list that will be used as fee reference
|
||||||
|
* @param groupProp property of the element of the list that will be used for grouping
|
||||||
* @returns list of @type {FeeDescription} sorted by usage period
|
* @returns list of @type {FeeDescription} sorted by usage period
|
||||||
*/
|
*/
|
||||||
export function createDenominationTimeline(
|
export function createTimeline<Type extends object>(
|
||||||
list: DenominationInfo[],
|
list: Type[],
|
||||||
periodProp: PropsWithReturnType<DenominationInfo, TalerProtocolTimestamp>,
|
idProp: PropsWithReturnType<Type, string>,
|
||||||
feeProp: PropsWithReturnType<DenominationInfo, AmountJson>,
|
periodStartProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
|
||||||
|
periodEndProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
|
||||||
|
feeProp: PropsWithReturnType<Type, AmountJson>,
|
||||||
|
groupProp: PropsWithReturnType<Type, string> | undefined,
|
||||||
|
selectBestForOverlapping: (l: Type[]) => Type | undefined,
|
||||||
): FeeDescription[] {
|
): FeeDescription[] {
|
||||||
const points = list
|
/**
|
||||||
|
* First we create a list with with point in the timeline sorted
|
||||||
|
* by time and categorized by starting or ending.
|
||||||
|
*/
|
||||||
|
const sortedPointsInTime = list
|
||||||
.reduce((ps, denom) => {
|
.reduce((ps, denom) => {
|
||||||
//exclude denoms with bad configuration
|
//exclude denoms with bad configuration
|
||||||
if (denom.stampStart.t_s >= denom[periodProp].t_s) {
|
const id = denom[idProp] as string;
|
||||||
throw Error(`denom ${denom.denomPubHash} has start after the end`);
|
const stampStart = denom[periodStartProp] as TalerProtocolTimestamp;
|
||||||
// return ps;
|
const stampEnd = denom[periodEndProp] as TalerProtocolTimestamp;
|
||||||
|
const fee = denom[feeProp] as AmountJson;
|
||||||
|
const group = !groupProp ? "" : (denom[groupProp] as string);
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw Error(
|
||||||
|
`denomination without hash ${JSON.stringify(denom, undefined, 2)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (stampStart.t_s >= stampEnd.t_s) {
|
||||||
|
throw Error(`denom ${id} has start after the end`);
|
||||||
}
|
}
|
||||||
ps.push({
|
ps.push({
|
||||||
type: "start",
|
type: "start",
|
||||||
moment: AbsoluteTime.fromTimestamp(denom.stampStart),
|
fee,
|
||||||
|
group,
|
||||||
|
id,
|
||||||
|
moment: AbsoluteTime.fromTimestamp(stampStart),
|
||||||
denom,
|
denom,
|
||||||
});
|
});
|
||||||
ps.push({
|
ps.push({
|
||||||
type: "end",
|
type: "end",
|
||||||
moment: AbsoluteTime.fromTimestamp(denom[periodProp]),
|
fee,
|
||||||
|
group,
|
||||||
|
id,
|
||||||
|
moment: AbsoluteTime.fromTimestamp(stampEnd),
|
||||||
denom,
|
denom,
|
||||||
});
|
});
|
||||||
return ps;
|
return ps;
|
||||||
}, [] as TimePoint[])
|
}, [] as TimePoint<Type>[])
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const v = Amounts.cmp(a.denom.value, b.denom.value);
|
const v = a.group == b.group ? 0 : a.group > b.group ? 1 : -1;
|
||||||
if (v != 0) return v;
|
if (v != 0) return v;
|
||||||
const t = AbsoluteTime.cmp(a.moment, b.moment);
|
const t = AbsoluteTime.cmp(a.moment, b.moment);
|
||||||
if (t != 0) return t;
|
if (t != 0) return t;
|
||||||
@ -297,21 +337,15 @@ export function createDenominationTimeline(
|
|||||||
return a.type === "start" ? 1 : -1;
|
return a.type === "start" ? 1 : -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeAtTheSameTime: DenominationInfo[] = [];
|
const activeAtTheSameTime: Type[] = [];
|
||||||
return points.reduce((result, cursor, idx) => {
|
return sortedPointsInTime.reduce((result, cursor, idx) => {
|
||||||
const hash = cursor.denom.denomPubHash;
|
/**
|
||||||
if (!hash)
|
* Now that we have move one step forward, we should
|
||||||
throw Error(
|
* update the previous element ending period with the
|
||||||
`denomination without hash ${JSON.stringify(
|
* current start time.
|
||||||
cursor.denom,
|
*/
|
||||||
undefined,
|
|
||||||
2,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
let prev = result.length > 0 ? result[result.length - 1] : undefined;
|
let prev = result.length > 0 ? result[result.length - 1] : undefined;
|
||||||
const prevHasSameValue =
|
const prevHasSameValue = prev && prev.group == cursor.group;
|
||||||
prev && Amounts.cmp(prev.value, cursor.denom.value) === 0;
|
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (prevHasSameValue) {
|
if (prevHasSameValue) {
|
||||||
prev.until = cursor.moment;
|
prev.until = cursor.moment;
|
||||||
@ -326,11 +360,15 @@ export function createDenominationTimeline(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//update the activeAtTheSameTime list
|
/**
|
||||||
|
* With the current moment in the iteration we
|
||||||
|
* should keep updated which entities are current
|
||||||
|
* active in this period of time.
|
||||||
|
*/
|
||||||
if (cursor.type === "end") {
|
if (cursor.type === "end") {
|
||||||
const loc = activeAtTheSameTime.findIndex((v) => v.denomPubHash === hash);
|
const loc = activeAtTheSameTime.findIndex((v) => v[idProp] === cursor.id);
|
||||||
if (loc === -1) {
|
if (loc === -1) {
|
||||||
throw Error(`denomination ${hash} has an end but no start`);
|
throw Error(`denomination ${cursor.id} has an end but no start`);
|
||||||
}
|
}
|
||||||
activeAtTheSameTime.splice(loc, 1);
|
activeAtTheSameTime.splice(loc, 1);
|
||||||
} else if (cursor.type === "start") {
|
} else if (cursor.type === "start") {
|
||||||
@ -340,12 +378,16 @@ export function createDenominationTimeline(
|
|||||||
throw new Error(`not TimePoint defined for type: ${exhaustiveCheck}`);
|
throw new Error(`not TimePoint defined for type: ${exhaustiveCheck}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx == points.length - 1) {
|
if (idx == sortedPointsInTime.length - 1) {
|
||||||
//this is the last element in the list, prevent adding
|
/**
|
||||||
//a gap in the end
|
* This is the last element in the list, if we continue
|
||||||
|
* a gap will normally be added which is not necessary.
|
||||||
|
* Also, the last element should be ending and the list of active
|
||||||
|
* element should be empty
|
||||||
|
*/
|
||||||
if (cursor.type !== "end") {
|
if (cursor.type !== "end") {
|
||||||
throw Error(
|
throw Error(
|
||||||
`denomination ${hash} starts after ending or doesn't have an ending`,
|
`denomination ${cursor.id} starts after ending or doesn't have an ending`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (activeAtTheSameTime.length > 0) {
|
if (activeAtTheSameTime.length > 0) {
|
||||||
@ -356,26 +398,36 @@ export function createDenominationTimeline(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = selectBestForOverlappingDenominations(activeAtTheSameTime);
|
const current = selectBestForOverlapping(activeAtTheSameTime);
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
|
/**
|
||||||
|
* We have a candidate to add in the list, check that we are
|
||||||
|
* not adding a duplicate.
|
||||||
|
* Next element in the list will defined the ending.
|
||||||
|
*/
|
||||||
|
const currentFee = current[feeProp] as AmountJson;
|
||||||
if (
|
if (
|
||||||
prev === undefined || //is the first
|
prev === undefined || //is the first
|
||||||
!prev.fee || //is a gap
|
!prev.fee || //is a gap
|
||||||
Amounts.cmp(prev.fee, current[feeProp]) !== 0 // prev has the same fee
|
Amounts.cmp(prev.fee, currentFee) !== 0 // prev has different fee
|
||||||
) {
|
) {
|
||||||
result.push({
|
result.push({
|
||||||
value: cursor.denom.value,
|
group: cursor.group,
|
||||||
from: cursor.moment,
|
from: cursor.moment,
|
||||||
until: AbsoluteTime.never(), //not yet known
|
until: AbsoluteTime.never(), //not yet known
|
||||||
fee: current[feeProp],
|
fee: currentFee,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
prev.until = cursor.moment;
|
prev.until = cursor.moment;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
/**
|
||||||
|
* No active element in this period of time, so we add a gap (no fee)
|
||||||
|
* Next element in the list will defined the ending.
|
||||||
|
*/
|
||||||
result.push({
|
result.push({
|
||||||
value: cursor.denom.value,
|
group: cursor.group,
|
||||||
from: cursor.moment,
|
from: cursor.moment,
|
||||||
until: AbsoluteTime.never(), //not yet known
|
until: AbsoluteTime.never(), //not yet known
|
||||||
});
|
});
|
||||||
|
@ -72,6 +72,7 @@ import {
|
|||||||
CoinDumpJson,
|
CoinDumpJson,
|
||||||
CoreApiResponse,
|
CoreApiResponse,
|
||||||
DenominationInfo,
|
DenominationInfo,
|
||||||
|
DenomOperationMap,
|
||||||
Duration,
|
Duration,
|
||||||
durationFromSpec,
|
durationFromSpec,
|
||||||
durationMin,
|
durationMin,
|
||||||
@ -86,11 +87,9 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
ManualWithdrawalDetails,
|
ManualWithdrawalDetails,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
OperationMap,
|
|
||||||
parsePaytoUri,
|
parsePaytoUri,
|
||||||
RefreshReason,
|
RefreshReason,
|
||||||
TalerErrorCode,
|
TalerErrorCode,
|
||||||
TalerErrorDetail,
|
|
||||||
URL,
|
URL,
|
||||||
WalletCoreVersion,
|
WalletCoreVersion,
|
||||||
WalletNotification,
|
WalletNotification,
|
||||||
@ -103,7 +102,6 @@ import {
|
|||||||
import { clearDatabase } from "./db-utils.js";
|
import { clearDatabase } from "./db-utils.js";
|
||||||
import {
|
import {
|
||||||
AuditorTrustRecord,
|
AuditorTrustRecord,
|
||||||
CoinRecord,
|
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
CoinStatus,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
@ -111,11 +109,7 @@ import {
|
|||||||
importDb,
|
importDb,
|
||||||
WalletStoresV1,
|
WalletStoresV1,
|
||||||
} from "./db.js";
|
} from "./db.js";
|
||||||
import {
|
import { getErrorDetailFromException, TalerError } from "./errors.js";
|
||||||
getErrorDetailFromException,
|
|
||||||
makeErrorDetail,
|
|
||||||
TalerError,
|
|
||||||
} from "./errors.js";
|
|
||||||
import {
|
import {
|
||||||
ActiveLongpollInfo,
|
ActiveLongpollInfo,
|
||||||
ExchangeOperations,
|
ExchangeOperations,
|
||||||
@ -142,11 +136,7 @@ import {
|
|||||||
} from "./operations/backup/index.js";
|
} from "./operations/backup/index.js";
|
||||||
import { setWalletDeviceId } from "./operations/backup/state.js";
|
import { setWalletDeviceId } from "./operations/backup/state.js";
|
||||||
import { getBalances } from "./operations/balance.js";
|
import { getBalances } from "./operations/balance.js";
|
||||||
import {
|
import { runOperationWithErrorReporting } from "./operations/common.js";
|
||||||
runOperationWithErrorReporting,
|
|
||||||
storeOperationError,
|
|
||||||
storeOperationPending,
|
|
||||||
} from "./operations/common.js";
|
|
||||||
import {
|
import {
|
||||||
createDepositGroup,
|
createDepositGroup,
|
||||||
getFeeForDeposit,
|
getFeeForDeposit,
|
||||||
@ -216,23 +206,23 @@ import {
|
|||||||
} from "./operations/withdraw.js";
|
} from "./operations/withdraw.js";
|
||||||
import { PendingTaskInfo, PendingTaskType } from "./pending-types.js";
|
import { PendingTaskInfo, PendingTaskType } from "./pending-types.js";
|
||||||
import { assertUnreachable } from "./util/assertUnreachable.js";
|
import { assertUnreachable } from "./util/assertUnreachable.js";
|
||||||
import { createDenominationTimeline } from "./util/denominations.js";
|
import {
|
||||||
|
createTimeline,
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
|
selectMinimumFee,
|
||||||
|
} from "./util/denominations.js";
|
||||||
import {
|
import {
|
||||||
HttpRequestLibrary,
|
HttpRequestLibrary,
|
||||||
readSuccessResponseJsonOrThrow,
|
readSuccessResponseJsonOrThrow,
|
||||||
} from "./util/http.js";
|
} from "./util/http.js";
|
||||||
import { checkDbInvariant, checkLogicInvariant } from "./util/invariants.js";
|
import { checkDbInvariant } from "./util/invariants.js";
|
||||||
import {
|
import {
|
||||||
AsyncCondition,
|
AsyncCondition,
|
||||||
OpenedPromise,
|
OpenedPromise,
|
||||||
openPromise,
|
openPromise,
|
||||||
} from "./util/promiseUtils.js";
|
} from "./util/promiseUtils.js";
|
||||||
import { DbAccess, GetReadWriteAccess } from "./util/query.js";
|
import { DbAccess, GetReadWriteAccess } from "./util/query.js";
|
||||||
import {
|
import { OperationAttemptResult } from "./util/retries.js";
|
||||||
OperationAttemptResult,
|
|
||||||
OperationAttemptResultType,
|
|
||||||
RetryInfo,
|
|
||||||
} from "./util/retries.js";
|
|
||||||
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
||||||
import {
|
import {
|
||||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||||
@ -702,6 +692,7 @@ async function getExchangeDetailedInfo(
|
|||||||
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
||||||
auditors: exchangeDetails.auditors,
|
auditors: exchangeDetails.auditors,
|
||||||
wireInfo: exchangeDetails.wireInfo,
|
wireInfo: exchangeDetails.wireInfo,
|
||||||
|
globalFees: exchangeDetails.globalFees,
|
||||||
},
|
},
|
||||||
denominations,
|
denominations,
|
||||||
};
|
};
|
||||||
@ -711,32 +702,111 @@ async function getExchangeDetailedInfo(
|
|||||||
throw Error(`exchange with base url "${exchangeBaseurl}" not found`);
|
throw Error(`exchange with base url "${exchangeBaseurl}" not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const feesDescription: OperationMap<FeeDescription[]> = {
|
const denoms = exchange.denominations.map((d) => ({
|
||||||
deposit: createDenominationTimeline(
|
...d,
|
||||||
exchange.denominations,
|
group: Amounts.stringifyValue(d.value),
|
||||||
|
}));
|
||||||
|
const denomFees: DenomOperationMap<FeeDescription[]> = {
|
||||||
|
deposit: createTimeline(
|
||||||
|
denoms,
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireDeposit",
|
"stampExpireDeposit",
|
||||||
"feeDeposit",
|
"feeDeposit",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
),
|
),
|
||||||
refresh: createDenominationTimeline(
|
refresh: createTimeline(
|
||||||
exchange.denominations,
|
denoms,
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireWithdraw",
|
"stampExpireWithdraw",
|
||||||
"feeRefresh",
|
"feeRefresh",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
),
|
),
|
||||||
refund: createDenominationTimeline(
|
refund: createTimeline(
|
||||||
exchange.denominations,
|
denoms,
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireWithdraw",
|
"stampExpireWithdraw",
|
||||||
"feeRefund",
|
"feeRefund",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
),
|
),
|
||||||
withdraw: createDenominationTimeline(
|
withdraw: createTimeline(
|
||||||
exchange.denominations,
|
denoms,
|
||||||
|
"denomPubHash",
|
||||||
|
"stampStart",
|
||||||
"stampExpireWithdraw",
|
"stampExpireWithdraw",
|
||||||
"feeWithdraw",
|
"feeWithdraw",
|
||||||
|
"group",
|
||||||
|
selectBestForOverlappingDenominations,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const transferFees = Object.entries(
|
||||||
|
exchange.info.wireInfo.feesForType,
|
||||||
|
).reduce((prev, [wireType, infoForType]) => {
|
||||||
|
const feesByGroup = [
|
||||||
|
...infoForType.map((w) => ({
|
||||||
|
...w,
|
||||||
|
fee: w.closingFee,
|
||||||
|
group: "closing",
|
||||||
|
})),
|
||||||
|
...infoForType.map((w) => ({ ...w, fee: w.wadFee, group: "wad" })),
|
||||||
|
...infoForType.map((w) => ({ ...w, fee: w.wireFee, group: "wire" })),
|
||||||
|
];
|
||||||
|
prev[wireType] = createTimeline(
|
||||||
|
feesByGroup,
|
||||||
|
"sig",
|
||||||
|
"startStamp",
|
||||||
|
"endStamp",
|
||||||
|
"fee",
|
||||||
|
"group",
|
||||||
|
selectMinimumFee,
|
||||||
|
);
|
||||||
|
return prev;
|
||||||
|
}, {} as Record<string, FeeDescription[]>);
|
||||||
|
|
||||||
|
const globalFeesByGroup = [
|
||||||
|
...exchange.info.globalFees.map((w) => ({
|
||||||
|
...w,
|
||||||
|
fee: w.accountFee,
|
||||||
|
group: "account",
|
||||||
|
})),
|
||||||
|
...exchange.info.globalFees.map((w) => ({
|
||||||
|
...w,
|
||||||
|
fee: w.historyFee,
|
||||||
|
group: "history",
|
||||||
|
})),
|
||||||
|
...exchange.info.globalFees.map((w) => ({
|
||||||
|
...w,
|
||||||
|
fee: w.kycFee,
|
||||||
|
group: "kyc",
|
||||||
|
})),
|
||||||
|
...exchange.info.globalFees.map((w) => ({
|
||||||
|
...w,
|
||||||
|
fee: w.purseFee,
|
||||||
|
group: "purse",
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
const globalFees = createTimeline(
|
||||||
|
globalFeesByGroup,
|
||||||
|
"signature",
|
||||||
|
"startDate",
|
||||||
|
"endDate",
|
||||||
|
"fee",
|
||||||
|
"group",
|
||||||
|
selectMinimumFee,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...exchange.info,
|
...exchange.info,
|
||||||
feesDescription,
|
denomFees,
|
||||||
|
transferFees,
|
||||||
|
globalFees,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,12 +46,14 @@ const exchanges: ExchangeFullDetails[] = [
|
|||||||
denomination_keys: [],
|
denomination_keys: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
feesDescription: {
|
denomFees: {
|
||||||
deposit: [],
|
deposit: [],
|
||||||
refresh: [],
|
refresh: [],
|
||||||
refund: [],
|
refund: [],
|
||||||
withdraw: [],
|
withdraw: [],
|
||||||
},
|
},
|
||||||
|
globalFees: [],
|
||||||
|
transferFees: {},
|
||||||
wireInfo: {
|
wireInfo: {
|
||||||
accounts: [],
|
accounts: [],
|
||||||
feesForType: {},
|
feesForType: {},
|
||||||
|
@ -15,15 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FeeDescription,
|
DenomOperationMap,
|
||||||
FeeDescriptionPair,
|
|
||||||
AbsoluteTime,
|
|
||||||
ExchangeFullDetails,
|
ExchangeFullDetails,
|
||||||
OperationMap,
|
ExchangeListItem, FeeDescriptionPair
|
||||||
ExchangeListItem,
|
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
|
import {
|
||||||
|
State as SelectExchangeState
|
||||||
|
} from "../../hooks/useSelectedExchange.js";
|
||||||
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
@ -32,7 +32,7 @@ import {
|
|||||||
ComparingView,
|
ComparingView,
|
||||||
ErrorLoadingView,
|
ErrorLoadingView,
|
||||||
NoExchangesView,
|
NoExchangesView,
|
||||||
ReadyView,
|
ReadyView
|
||||||
} from "./views.js";
|
} from "./views.js";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -41,9 +41,6 @@ export interface Props {
|
|||||||
onCancel: () => Promise<void>;
|
onCancel: () => Promise<void>;
|
||||||
onSelection: (exchange: string) => Promise<void>;
|
onSelection: (exchange: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
import {
|
|
||||||
State as SelectExchangeState
|
|
||||||
} from "../../hooks/useSelectedExchange.js";
|
|
||||||
|
|
||||||
export type State =
|
export type State =
|
||||||
| State.Loading
|
| State.Loading
|
||||||
@ -71,13 +68,12 @@ export namespace State {
|
|||||||
|
|
||||||
export interface Ready extends BaseInfo {
|
export interface Ready extends BaseInfo {
|
||||||
status: "ready";
|
status: "ready";
|
||||||
timeline: OperationMap<FeeDescription[]>;
|
|
||||||
onClose: ButtonHandler;
|
onClose: ButtonHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Comparing extends BaseInfo {
|
export interface Comparing extends BaseInfo {
|
||||||
status: "comparing";
|
status: "comparing";
|
||||||
pairTimeline: OperationMap<FeeDescriptionPair[]>;
|
pairTimeline: DenomOperationMap<FeeDescriptionPair[]>;
|
||||||
onReset: ButtonHandler;
|
onReset: ButtonHandler;
|
||||||
onSelect: ButtonHandler;
|
onSelect: ButtonHandler;
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FeeDescription, OperationMap } from "@gnu-taler/taler-util";
|
import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util";
|
||||||
import { createDenominationPairTimeline } from "@gnu-taler/taler-wallet-core";
|
import { createPairTimeline } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
import * as wxApi from "../../wxApi.js";
|
import * as wxApi from "../../wxApi.js";
|
||||||
@ -94,27 +94,26 @@ export function useComponentState(
|
|||||||
onClick: onCancel,
|
onClick: onCancel,
|
||||||
},
|
},
|
||||||
selected,
|
selected,
|
||||||
timeline: selected.feesDescription,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const pairTimeline: OperationMap<FeeDescription[]> = {
|
const pairTimeline: DenomOperationMap<FeeDescription[]> = {
|
||||||
deposit: createDenominationPairTimeline(
|
deposit: createPairTimeline(
|
||||||
selected.feesDescription.deposit,
|
selected.denomFees.deposit,
|
||||||
original.feesDescription.deposit,
|
original.denomFees.deposit,
|
||||||
),
|
),
|
||||||
refresh: createDenominationPairTimeline(
|
refresh: createPairTimeline(
|
||||||
selected.feesDescription.refresh,
|
selected.denomFees.refresh,
|
||||||
original.feesDescription.refresh,
|
original.denomFees.refresh,
|
||||||
),
|
),
|
||||||
refund: createDenominationPairTimeline(
|
refund: createPairTimeline(
|
||||||
selected.feesDescription.refund,
|
selected.denomFees.refund,
|
||||||
original.feesDescription.refund,
|
original.denomFees.refund,
|
||||||
),
|
|
||||||
withdraw: createDenominationPairTimeline(
|
|
||||||
selected.feesDescription.withdraw,
|
|
||||||
original.feesDescription.withdraw,
|
|
||||||
),
|
),
|
||||||
|
withdraw: createPairTimeline(
|
||||||
|
selected.denomFees.withdraw,
|
||||||
|
original.denomFees.withdraw,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -28,71 +28,72 @@ export default {
|
|||||||
|
|
||||||
export const Bitcoin1 = createExample(ReadyView, {
|
export const Bitcoin1 = createExample(ReadyView, {
|
||||||
exchanges: {
|
exchanges: {
|
||||||
list: { "http://exchange": "http://exchange" },
|
list: { "0": "https://exchange.taler.ar" },
|
||||||
value: "http://exchange",
|
value: "0",
|
||||||
},
|
},
|
||||||
selected: {
|
selected: {
|
||||||
currency: "BITCOINBTC",
|
currency: "BITCOINBTC",
|
||||||
auditors: [],
|
auditors: [],
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
denomFees: timelineExample(),
|
||||||
|
transferFees: {},
|
||||||
|
globalFees: [],
|
||||||
} as any,
|
} as any,
|
||||||
onClose: {},
|
onClose: {},
|
||||||
timeline: {
|
|
||||||
deposit: [],
|
|
||||||
refresh: [],
|
|
||||||
refund: [],
|
|
||||||
withdraw: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
export const Bitcoin2 = createExample(ReadyView, {
|
export const Bitcoin2 = createExample(ReadyView, {
|
||||||
exchanges: {
|
exchanges: {
|
||||||
list: { "http://exchange": "http://exchange" },
|
list: {
|
||||||
value: "http://exchange",
|
"https://exchange.taler.ar": "https://exchange.taler.ar",
|
||||||
|
"https://exchange-btc.taler.ar": "https://exchange-btc.taler.ar",
|
||||||
|
},
|
||||||
|
value: "https://exchange.taler.ar",
|
||||||
},
|
},
|
||||||
selected: {
|
selected: {
|
||||||
currency: "BITCOINBTC",
|
currency: "BITCOINBTC",
|
||||||
auditors: [],
|
auditors: [],
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
denomFees: timelineExample(),
|
||||||
|
transferFees: {},
|
||||||
|
globalFees: [],
|
||||||
} as any,
|
} as any,
|
||||||
onClose: {},
|
onClose: {},
|
||||||
timeline: {
|
|
||||||
deposit: [],
|
|
||||||
refresh: [],
|
|
||||||
refund: [],
|
|
||||||
withdraw: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Kudos1 = createExample(ReadyView, {
|
export const Kudos1 = createExample(ReadyView, {
|
||||||
exchanges: {
|
exchanges: {
|
||||||
list: { "http://exchange": "http://exchange" },
|
list: {
|
||||||
value: "http://exchange",
|
"https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar",
|
||||||
|
},
|
||||||
|
value: "https://exchange-kudos.taler.ar",
|
||||||
},
|
},
|
||||||
selected: {
|
selected: {
|
||||||
currency: "BITCOINBTC",
|
currency: "BITCOINBTC",
|
||||||
auditors: [],
|
auditors: [],
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
denomFees: timelineExample(),
|
||||||
|
transferFees: {},
|
||||||
|
globalFees: [],
|
||||||
} as any,
|
} as any,
|
||||||
onClose: {},
|
onClose: {},
|
||||||
timeline: {
|
|
||||||
deposit: [],
|
|
||||||
refresh: [],
|
|
||||||
refund: [],
|
|
||||||
withdraw: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
export const Kudos2 = createExample(ReadyView, {
|
export const Kudos2 = createExample(ReadyView, {
|
||||||
exchanges: {
|
exchanges: {
|
||||||
list: { "http://exchange": "http://exchange" },
|
list: {
|
||||||
value: "http://exchange",
|
"https://exchange-kudos.taler.ar": "https://exchange-kudos.taler.ar",
|
||||||
|
"https://exchange-kudos2.taler.ar": "https://exchange-kudos2.taler.ar",
|
||||||
|
},
|
||||||
|
value: "https://exchange-kudos.taler.ar",
|
||||||
},
|
},
|
||||||
selected: {
|
selected: {
|
||||||
currency: "BITCOINBTC",
|
currency: "BITCOINBTC",
|
||||||
auditors: [],
|
auditors: [],
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
denomFees: timelineExample(),
|
||||||
|
transferFees: {},
|
||||||
|
globalFees: [],
|
||||||
} as any,
|
} as any,
|
||||||
onClose: {},
|
onClose: {},
|
||||||
timeline: {
|
|
||||||
deposit: [],
|
|
||||||
refresh: [],
|
|
||||||
refund: [],
|
|
||||||
withdraw: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
export const ComparingBitcoin = createExample(ComparingView, {
|
export const ComparingBitcoin = createExample(ComparingView, {
|
||||||
exchanges: {
|
exchanges: {
|
||||||
@ -102,6 +103,9 @@ export const ComparingBitcoin = createExample(ComparingView, {
|
|||||||
selected: {
|
selected: {
|
||||||
currency: "BITCOINBTC",
|
currency: "BITCOINBTC",
|
||||||
auditors: [],
|
auditors: [],
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
transferFees: {},
|
||||||
|
globalFees: [],
|
||||||
} as any,
|
} as any,
|
||||||
onReset: {},
|
onReset: {},
|
||||||
onSelect: {},
|
onSelect: {},
|
||||||
@ -121,6 +125,9 @@ export const ComparingKudos = createExample(ComparingView, {
|
|||||||
selected: {
|
selected: {
|
||||||
currency: "KUDOS",
|
currency: "KUDOS",
|
||||||
auditors: [],
|
auditors: [],
|
||||||
|
exchangeBaseUrl: "https://exchange.taler.ar",
|
||||||
|
transferFees: {},
|
||||||
|
globalFees: [],
|
||||||
} as any,
|
} as any,
|
||||||
onReset: {},
|
onReset: {},
|
||||||
onSelect: {},
|
onSelect: {},
|
||||||
@ -132,3 +139,400 @@ export const ComparingKudos = createExample(ComparingView, {
|
|||||||
withdraw: [],
|
withdraw: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function timelineExample() {
|
||||||
|
return {
|
||||||
|
deposit: [
|
||||||
|
{
|
||||||
|
group: "0.1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1916386904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1916386904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "10",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1916386904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1000",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1916386904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "2",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1916386904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "5",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1916386904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
refresh: [
|
||||||
|
{
|
||||||
|
group: "0.1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "10",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1000",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "2",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "5",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
refund: [
|
||||||
|
{
|
||||||
|
group: "0.1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "10",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1000",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "2",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "5",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
withdraw: [
|
||||||
|
{
|
||||||
|
group: "0.1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "10",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "1000",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "2",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "5",
|
||||||
|
from: {
|
||||||
|
t_ms: 1664098904000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1758706904000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
wad: [
|
||||||
|
{
|
||||||
|
group: "iban",
|
||||||
|
from: {
|
||||||
|
t_ms: 1640995200000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1798761600000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
wire: [
|
||||||
|
{
|
||||||
|
group: "iban",
|
||||||
|
from: {
|
||||||
|
t_ms: 1640995200000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1798761600000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
closing: [
|
||||||
|
{
|
||||||
|
group: "iban",
|
||||||
|
from: {
|
||||||
|
t_ms: 1640995200000,
|
||||||
|
},
|
||||||
|
until: {
|
||||||
|
t_ms: 1798761600000,
|
||||||
|
},
|
||||||
|
fee: {
|
||||||
|
currency: "KUDOS",
|
||||||
|
fraction: 1000000,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -31,9 +31,7 @@ import { useTranslationContext } from "../../context/translation.js";
|
|||||||
import { Button } from "../../mui/Button.js";
|
import { Button } from "../../mui/Button.js";
|
||||||
import arrowDown from "../../svg/chevron-down.svg";
|
import arrowDown from "../../svg/chevron-down.svg";
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
import {
|
import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js";
|
||||||
State as SelectExchangeState
|
|
||||||
} from "../../hooks/useSelectedExchange.js";
|
|
||||||
|
|
||||||
const ButtonGroup = styled.div`
|
const ButtonGroup = styled.div`
|
||||||
& > button {
|
& > button {
|
||||||
@ -59,7 +57,7 @@ const FeeDescriptionTable = styled.table`
|
|||||||
}
|
}
|
||||||
td.value {
|
td.value {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 1%;
|
width: 15%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
td.icon {
|
td.icon {
|
||||||
@ -109,26 +107,28 @@ export function ErrorLoadingView({ error }: State.LoadingUriError): VNode {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingError
|
<LoadingError
|
||||||
title={<i18n.Translate>Could not load tip status</i18n.Translate>}
|
title={<i18n.Translate>Could not load exchange fees</i18n.Translate>}
|
||||||
error={error}
|
error={error}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function NoExchangesView({
|
||||||
|
currency,
|
||||||
export function NoExchangesView({currency}: SelectExchangeState.NoExchange): VNode {
|
}: SelectExchangeState.NoExchange): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
if (!currency) {
|
if (!currency) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<i18n.Translate>could not find any exchange</i18n.Translate>
|
<i18n.Translate>could not find any exchange</i18n.Translate>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<i18n.Translate>could not find any exchange for the currency {currency}</i18n.Translate>
|
<i18n.Translate>
|
||||||
|
could not find any exchange for the currency {currency}
|
||||||
|
</i18n.Translate>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -356,7 +356,6 @@ export function ReadyView({
|
|||||||
exchanges,
|
exchanges,
|
||||||
selected,
|
selected,
|
||||||
onClose,
|
onClose,
|
||||||
timeline,
|
|
||||||
}: State.Ready): VNode {
|
}: State.Ready): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
@ -365,7 +364,10 @@ export function ReadyView({
|
|||||||
<h2>
|
<h2>
|
||||||
<i18n.Translate>Service fee description</i18n.Translate>
|
<i18n.Translate>Service fee description</i18n.Translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
<p>
|
||||||
|
All fee indicated below are in the same and only currency the exchange
|
||||||
|
works.
|
||||||
|
</p>
|
||||||
<section>
|
<section>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -375,21 +377,27 @@ export function ReadyView({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>
|
{Object.keys(exchanges.list).length === 1 ? (
|
||||||
<Input>
|
<Fragment>
|
||||||
<SelectList
|
<p>Exchange: {selected.exchangeBaseUrl}</p>
|
||||||
label={
|
</Fragment>
|
||||||
<i18n.Translate>
|
) : (
|
||||||
Select {selected.currency} exchange
|
<p>
|
||||||
</i18n.Translate>
|
<Input>
|
||||||
}
|
<SelectList
|
||||||
list={exchanges.list}
|
label={
|
||||||
name="lang"
|
<i18n.Translate>
|
||||||
value={exchanges.value}
|
Select {selected.currency} exchange
|
||||||
onChange={exchanges.onChange}
|
</i18n.Translate>
|
||||||
/>
|
}
|
||||||
</Input>
|
list={exchanges.list}
|
||||||
</p>
|
name="lang"
|
||||||
|
value={exchanges.value}
|
||||||
|
onChange={exchanges.onChange}
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<Button variant="outlined" onClick={onClose.onClick}>
|
<Button variant="outlined" onClick={onClose.onClick}>
|
||||||
<i18n.Translate>Close</i18n.Translate>
|
<i18n.Translate>Close</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
@ -411,16 +419,25 @@ export function ReadyView({
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<i18n.Translate>currency</i18n.Translate>
|
<i18n.Translate>Currency</i18n.Translate>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b>{selected.currency}</b>
|
||||||
</td>
|
</td>
|
||||||
<td>{selected.currency}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
<i18n.Translate>Operations</i18n.Translate>
|
<i18n.Translate>Coin operations</i18n.Translate>
|
||||||
</h2>
|
</h2>
|
||||||
|
<p>
|
||||||
|
<i18n.Translate>
|
||||||
|
Every operation in this section may be different by denomination
|
||||||
|
value and is valid for a period of time. The exchange will charge
|
||||||
|
the indicated amount every time a coin is used in such operation.
|
||||||
|
</i18n.Translate>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<i18n.Translate>Deposits</i18n.Translate>
|
<i18n.Translate>Deposits</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
@ -440,7 +457,10 @@ export function ReadyView({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<RenderFeeDescriptionByValue first={timeline.deposit} />
|
<RenderFeeDescriptionByValue
|
||||||
|
list={selected.denomFees.deposit}
|
||||||
|
sorting={(a, b) => Number(a) - Number(b)}
|
||||||
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</FeeDescriptionTable>
|
</FeeDescriptionTable>
|
||||||
<p>
|
<p>
|
||||||
@ -462,7 +482,10 @@ export function ReadyView({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<RenderFeeDescriptionByValue first={timeline.withdraw} />
|
<RenderFeeDescriptionByValue
|
||||||
|
list={selected.denomFees.withdraw}
|
||||||
|
sorting={(a, b) => Number(a) - Number(b)}
|
||||||
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</FeeDescriptionTable>
|
</FeeDescriptionTable>
|
||||||
<p>
|
<p>
|
||||||
@ -484,7 +507,10 @@ export function ReadyView({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<RenderFeeDescriptionByValue first={timeline.refund} />
|
<RenderFeeDescriptionByValue
|
||||||
|
list={selected.denomFees.refund}
|
||||||
|
sorting={(a, b) => Number(a) - Number(b)}
|
||||||
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</FeeDescriptionTable>{" "}
|
</FeeDescriptionTable>{" "}
|
||||||
<p>
|
<p>
|
||||||
@ -506,53 +532,81 @@ export function ReadyView({
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<RenderFeeDescriptionByValue first={timeline.refresh} />
|
<RenderFeeDescriptionByValue
|
||||||
|
list={selected.denomFees.refresh}
|
||||||
|
sorting={(a, b) => Number(a) - Number(b)}
|
||||||
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</FeeDescriptionTable>{" "}
|
</FeeDescriptionTable>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<table>
|
<h2>
|
||||||
|
<i18n.Translate>Transfer operations</i18n.Translate>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<i18n.Translate>
|
||||||
|
Every operation in this section may be different by transfer type
|
||||||
|
and is valid for a period of time. The exchange will charge the
|
||||||
|
indicated amount every time a transfer is made.
|
||||||
|
</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
{Object.entries(selected.transferFees).map(([type, fees], idx) => {
|
||||||
|
return (
|
||||||
|
<Fragment key={idx}>
|
||||||
|
<p>{type}</p>
|
||||||
|
<FeeDescriptionTable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Operation</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="fee">
|
||||||
|
<i18n.Translate>Fee</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Until</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<RenderFeeDescriptionByValue list={fees} />
|
||||||
|
</tbody>
|
||||||
|
</FeeDescriptionTable>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>
|
||||||
|
<i18n.Translate>Wallet operations</i18n.Translate>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<i18n.Translate>
|
||||||
|
Every operation in this section may be different by transfer type
|
||||||
|
and is valid for a period of time. The exchange will charge the
|
||||||
|
indicated amount every time a transfer is made.
|
||||||
|
</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
<FeeDescriptionTable>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<th> </th>
|
||||||
<i18n.Translate>Wallet operations</i18n.Translate>
|
<th>
|
||||||
</td>
|
<i18n.Translate>Feature</i18n.Translate>
|
||||||
<td>
|
</th>
|
||||||
|
<th class="fee">
|
||||||
<i18n.Translate>Fee</i18n.Translate>
|
<i18n.Translate>Fee</i18n.Translate>
|
||||||
</td>
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Until</i18n.Translate>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<RenderFeeDescriptionByValue list={selected.globalFees} />
|
||||||
<td>history(i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>kyc (i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>account (i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>purse (i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>wire SEPA (i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>closing SEPA(i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>wad SEPA (i) </td>
|
|
||||||
<td>0.1</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</FeeDescriptionTable>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
@ -579,7 +633,7 @@ function FeeDescriptionRowsGroup({
|
|||||||
<tr
|
<tr
|
||||||
key={idx}
|
key={idx}
|
||||||
class="value"
|
class="value"
|
||||||
data-hasMore={!hasMoreInfo}
|
data-hasMore={hasMoreInfo}
|
||||||
data-main={main}
|
data-main={main}
|
||||||
data-hidden={!main && !expanded}
|
data-hidden={!main && !expanded}
|
||||||
onClick={() => setExpand((p) => !p)}
|
onClick={() => setExpand((p) => !p)}
|
||||||
@ -594,9 +648,7 @@ function FeeDescriptionRowsGroup({
|
|||||||
/>
|
/>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</td>
|
</td>
|
||||||
<td class="value">
|
<td class="value">{main ? info.group : ""}</td>
|
||||||
{main ? <Amount value={info.value} hideCurrency /> : ""}
|
|
||||||
</td>
|
|
||||||
{info.fee ? (
|
{info.fee ? (
|
||||||
<td class="fee">{<Amount value={info.fee} hideCurrency />}</td>
|
<td class="fee">{<Amount value={info.fee} hideCurrency />}</td>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
@ -621,7 +673,7 @@ function FeePairRowsGroup({ infos }: { infos: FeeDescriptionPair[] }): VNode {
|
|||||||
<tr
|
<tr
|
||||||
key={idx}
|
key={idx}
|
||||||
class="value"
|
class="value"
|
||||||
data-hasMore={!hasMoreInfo}
|
data-hasMore={hasMoreInfo}
|
||||||
data-main={main}
|
data-main={main}
|
||||||
data-hidden={!main && !expanded}
|
data-hidden={!main && !expanded}
|
||||||
onClick={() => setExpand((p) => !p)}
|
onClick={() => setExpand((p) => !p)}
|
||||||
@ -636,9 +688,7 @@ function FeePairRowsGroup({ infos }: { infos: FeeDescriptionPair[] }): VNode {
|
|||||||
/>
|
/>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</td>
|
</td>
|
||||||
<td class="value">
|
<td class="value">{main ? info.group : ""}</td>
|
||||||
{main ? <Amount value={info.value} hideCurrency /> : ""}
|
|
||||||
</td>
|
|
||||||
{info.left ? (
|
{info.left ? (
|
||||||
<td class="fee">{<Amount value={info.left} hideCurrency />}</td>
|
<td class="fee">{<Amount value={info.left} hideCurrency />}</td>
|
||||||
) : (
|
) : (
|
||||||
@ -673,7 +723,7 @@ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode {
|
|||||||
const next = idx >= list.length - 1 ? undefined : list[idx + 1];
|
const next = idx >= list.length - 1 ? undefined : list[idx + 1];
|
||||||
|
|
||||||
const nextIsMoreInfo =
|
const nextIsMoreInfo =
|
||||||
next !== undefined && Amounts.cmp(next.value, info.value) === 0;
|
next !== undefined && next.group === info.group;
|
||||||
|
|
||||||
prev.rows.push(info);
|
prev.rows.push(info);
|
||||||
|
|
||||||
@ -681,7 +731,7 @@ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode {
|
|||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
prev.rows = [];
|
// prev.rows = [];
|
||||||
prev.views.push(<FeePairRowsGroup infos={prev.rows} />);
|
prev.views.push(<FeePairRowsGroup infos={prev.rows} />);
|
||||||
return prev;
|
return prev;
|
||||||
},
|
},
|
||||||
@ -701,36 +751,21 @@ function RenderFeePairByValue({ list }: { list: FeeDescriptionPair[] }): VNode {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function RenderFeeDescriptionByValue({
|
function RenderFeeDescriptionByValue({
|
||||||
first,
|
list,
|
||||||
|
sorting,
|
||||||
}: {
|
}: {
|
||||||
first: FeeDescription[];
|
list: FeeDescription[];
|
||||||
|
sorting?: (a: string, b: string) => number;
|
||||||
}): VNode {
|
}): VNode {
|
||||||
return (
|
const grouped = list.reduce((prev, cur) => {
|
||||||
<Fragment>
|
if (!prev[cur.group]) {
|
||||||
{
|
prev[cur.group] = [];
|
||||||
first.reduce(
|
}
|
||||||
(prev, info, idx) => {
|
prev[cur.group].push(cur);
|
||||||
const next = idx >= first.length - 1 ? undefined : first[idx + 1];
|
return prev;
|
||||||
|
}, {} as Record<string, FeeDescription[]>);
|
||||||
const nextIsMoreInfo =
|
const p = Object.keys(grouped)
|
||||||
next !== undefined && Amounts.cmp(next.value, info.value) === 0;
|
.sort(sorting)
|
||||||
|
.map((i, idx) => <FeeDescriptionRowsGroup key={idx} infos={grouped[i]} />);
|
||||||
prev.rows.push(info);
|
return <Fragment>{p}</Fragment>;
|
||||||
|
|
||||||
if (nextIsMoreInfo) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev.rows = [];
|
|
||||||
prev.views.push(<FeeDescriptionRowsGroup infos={prev.rows} />);
|
|
||||||
return prev;
|
|
||||||
},
|
|
||||||
{ rows: [], views: [] } as {
|
|
||||||
rows: FeeDescription[];
|
|
||||||
views: h.JSX.Element[];
|
|
||||||
},
|
|
||||||
).views
|
|
||||||
}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user