@provex/utils 1.0.16 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/chain.d.ts +436 -4
- package/dist/src/chain.js +80 -0
- package/dist/src/chain.test.d.ts +1 -0
- package/dist/src/chain.test.js +112 -0
- package/dist/src/contracts.d.ts +196 -20
- package/dist/src/contracts.js +245 -17
- package/dist/src/contracts.test.d.ts +1 -0
- package/dist/src/contracts.test.js +243 -0
- package/dist/src/conversionRates.d.ts +99 -8
- package/dist/src/conversionRates.js +74 -4
- package/dist/src/conversionRates.test.d.ts +1 -0
- package/dist/src/conversionRates.test.js +473 -0
- package/dist/src/currencies.d.ts +113 -9
- package/dist/src/currencies.js +119 -38
- package/dist/src/currencies.test.d.ts +1 -0
- package/dist/src/currencies.test.js +343 -0
- package/dist/src/data/countries.json +166 -166
- package/dist/src/deposit.d.ts +69 -1
- package/dist/src/deposit.js +52 -1
- package/dist/src/depositKeys.d.ts +154 -0
- package/dist/src/depositKeys.js +104 -0
- package/dist/src/depositKeys.test.d.ts +1 -0
- package/dist/src/depositKeys.test.js +231 -0
- package/dist/src/endpoints.d.ts +51 -0
- package/dist/src/endpoints.js +70 -0
- package/dist/src/endpoints.test.d.ts +1 -0
- package/dist/src/endpoints.test.js +54 -0
- package/dist/src/events.d.ts +40 -2
- package/dist/src/events.js +86 -10
- package/dist/src/events.test.d.ts +1 -0
- package/dist/src/events.test.js +111 -0
- package/dist/src/fees.d.ts +136 -0
- package/dist/src/fees.js +178 -0
- package/dist/src/fees.test.d.ts +1 -0
- package/dist/src/fees.test.js +165 -0
- package/dist/src/ids.d.ts +125 -22
- package/dist/src/ids.js +211 -61
- package/dist/src/ids.test.d.ts +1 -0
- package/dist/src/ids.test.js +607 -0
- package/dist/src/index.d.ts +30 -1
- package/dist/src/index.js +30 -1
- package/dist/src/input.d.ts +23 -4
- package/dist/src/input.js +23 -4
- package/dist/src/intent.d.ts +38 -2
- package/dist/src/intent.js +28 -1
- package/dist/src/intent.test.d.ts +1 -0
- package/dist/src/intent.test.js +119 -0
- package/dist/src/json.d.ts +38 -1
- package/dist/src/json.js +38 -2
- package/dist/src/normalization.d.ts +224 -0
- package/dist/src/normalization.js +119 -0
- package/dist/src/normalization.test.d.ts +1 -0
- package/dist/src/normalization.test.js +284 -0
- package/dist/src/participants.d.ts +19 -4
- package/dist/src/participants.js +19 -4
- package/dist/src/payment.d.ts +333 -16
- package/dist/src/payment.js +768 -58
- package/dist/src/payment.test.d.ts +1 -0
- package/dist/src/payment.test.js +272 -0
- package/dist/src/peer-types.d.ts +327 -0
- package/dist/src/peer-types.js +169 -0
- package/dist/src/profit.d.ts +26 -5
- package/dist/src/profit.js +31 -8
- package/dist/src/profit.test.d.ts +1 -0
- package/dist/src/profit.test.js +315 -0
- package/dist/src/reputation.d.ts +125 -0
- package/dist/src/reputation.js +188 -0
- package/dist/src/reputation.test.d.ts +1 -0
- package/dist/src/reputation.test.js +156 -0
- package/dist/src/sort.d.ts +49 -10
- package/dist/src/sort.js +40 -9
- package/dist/src/sort.test.d.ts +1 -0
- package/dist/src/sort.test.js +216 -0
- package/dist/src/stateKey.d.ts +35 -0
- package/dist/src/stateKey.js +36 -0
- package/dist/src/stats.d.ts +55 -1
- package/dist/src/stats.js +46 -1
- package/dist/src/stats.test.d.ts +1 -0
- package/dist/src/stats.test.js +104 -0
- package/dist/src/time.d.ts +59 -9
- package/dist/src/time.js +71 -15
- package/dist/src/time.test.d.ts +1 -0
- package/dist/src/time.test.js +231 -0
- package/dist/src/tokens.d.ts +11972 -145
- package/dist/src/tokens.js +98 -17
- package/dist/src/tokens.test.d.ts +1 -0
- package/dist/src/tokens.test.js +193 -0
- package/dist/src/units.d.ts +183 -0
- package/dist/src/units.js +206 -6
- package/dist/src/units.test.d.ts +1 -0
- package/dist/src/units.test.js +327 -0
- package/dist/src/zkp2p-types.d.ts +273 -4
- package/dist/src/zkp2p-types.js +169 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +116 -26
- package/scripts/convert-svg.ts +0 -22
- package/src/chain.ts +0 -27
- package/src/contracts.ts +0 -89
- package/src/conversionRates.ts +0 -356
- package/src/currencies.ts +0 -286
- package/src/data/countries.json +0 -1099
- package/src/deposit.ts +0 -2
- package/src/events.ts +0 -21
- package/src/flags/eur.svg +0 -4
- package/src/flags/usd.svg +0 -25
- package/src/flags.ts +0 -4
- package/src/global.d.ts +0 -9
- package/src/ids.ts +0 -137
- package/src/index.ts +0 -16
- package/src/input.ts +0 -35
- package/src/intent.ts +0 -15
- package/src/json.ts +0 -42
- package/src/participants.ts +0 -14
- package/src/payment.ts +0 -573
- package/src/profit.ts +0 -55
- package/src/sort.ts +0 -49
- package/src/stats.ts +0 -10
- package/src/time.ts +0 -123
- package/src/tokens.ts +0 -159
- package/src/units.ts +0 -120
- package/src/zkp2p-types.ts +0 -55
- package/tsconfig.json +0 -18
package/dist/src/currencies.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fiat currency definitions and utilities for the Provex protocol.
|
|
3
|
+
*
|
|
4
|
+
* This module defines supported fiat currencies with their symbols, decimals,
|
|
5
|
+
* and contract IDs (keccak256 hashes used for on-chain identification).
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* - Currency contract IDs are keccak256(currencyCode) used in smart contracts
|
|
9
|
+
* - Most currencies use 2 decimal places, but JPY/KRW/VND/UGX use 0
|
|
10
|
+
* - The Currency class provides parsing/formatting utilities for amounts
|
|
11
|
+
*
|
|
12
|
+
* @see {@link payment} for payment provider currency support
|
|
13
|
+
* @see {@link conversionRates} for exchange rate calculations
|
|
14
|
+
*/
|
|
1
15
|
import { isHash, keccak256, toBytes } from 'viem';
|
|
2
16
|
import { addThousandsSeparator, formatUnits, parseUnits } from './units.js';
|
|
3
17
|
import countriesData from './data/countries.json' with { type: 'json' };
|
|
4
18
|
import { USD_FLAG_SVG, EUR_FLAG_SVG } from './flags.js';
|
|
5
|
-
|
|
19
|
+
/** Pre-computed data URL for USD flag (custom square design) */
|
|
6
20
|
const USD_FLAG_DATA_URL = `data:image/svg+xml;base64,${btoa(USD_FLAG_SVG)}`;
|
|
21
|
+
/** Pre-computed data URL for EUR flag (custom square design) */
|
|
7
22
|
const EUR_FLAG_DATA_URL = `data:image/svg+xml;base64,${btoa(EUR_FLAG_SVG)}`;
|
|
8
23
|
const countries = countriesData;
|
|
9
24
|
/**
|
|
@@ -15,12 +30,20 @@ export const tickerToContractId = (ticker) => {
|
|
|
15
30
|
return keccak256(toBytes(ticker.toUpperCase()));
|
|
16
31
|
};
|
|
17
32
|
/**
|
|
18
|
-
* Currency class
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
33
|
+
* Currency class representing a fiat currency with parsing and formatting utilities.
|
|
34
|
+
*
|
|
35
|
+
* Each currency has:
|
|
36
|
+
* - A unique ticker (ISO 4217 code)
|
|
37
|
+
* - A display symbol
|
|
38
|
+
* - A contract ID (keccak256 hash for on-chain use)
|
|
39
|
+
* - Decimal precision for amount formatting
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const usd = SUPPORTED_CURRENCIES.USD
|
|
44
|
+
* const amount = usd.parseAmount('100.50') // 10050n (2 decimals)
|
|
45
|
+
* const display = usd.formatAmount(10050n) // '100.50'
|
|
46
|
+
* ```
|
|
24
47
|
*/
|
|
25
48
|
export class Currency {
|
|
26
49
|
ticker;
|
|
@@ -29,12 +52,24 @@ export class Currency {
|
|
|
29
52
|
decimals;
|
|
30
53
|
contractId;
|
|
31
54
|
flag;
|
|
32
|
-
|
|
55
|
+
zoom;
|
|
56
|
+
/**
|
|
57
|
+
* Create a new Currency instance.
|
|
58
|
+
*
|
|
59
|
+
* @param code - ISO 4217 currency code (converted to uppercase)
|
|
60
|
+
* @param symbol - Display symbol (e.g., '$')
|
|
61
|
+
* @param name - Full currency name
|
|
62
|
+
* @param decimals - Number of decimal places
|
|
63
|
+
* @param flag - Optional flag image URL
|
|
64
|
+
* @param zoom - Optional zoom level for flag display (default: 1.5)
|
|
65
|
+
*/
|
|
66
|
+
constructor(code, symbol, name, decimals, flag, zoom) {
|
|
33
67
|
this.ticker = code.toUpperCase();
|
|
34
68
|
this.symbol = symbol;
|
|
35
69
|
this.name = name;
|
|
36
70
|
this.decimals = decimals;
|
|
37
|
-
this.flag = flag || '';
|
|
71
|
+
this.flag = flag || '';
|
|
72
|
+
this.zoom = zoom ?? 1.5;
|
|
38
73
|
this.contractId = this.generateContractId();
|
|
39
74
|
}
|
|
40
75
|
/**
|
|
@@ -64,21 +99,9 @@ export class Currency {
|
|
|
64
99
|
* @returns The amount as a string
|
|
65
100
|
*/
|
|
66
101
|
formatAmount(amount, options = {}) {
|
|
67
|
-
|
|
68
|
-
if (amount === null) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
return formatUnits(amount, {
|
|
72
|
-
decimals: this.decimals,
|
|
73
|
-
truncateDecimals: 2,
|
|
74
|
-
padDecimals: 2,
|
|
75
|
-
...options,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
console.error('Error formatting amount: %o', amount, err);
|
|
102
|
+
if (amount === null)
|
|
80
103
|
return null;
|
|
81
|
-
}
|
|
104
|
+
return formatUnits(amount, { decimals: this.decimals, truncateDecimals: 2, padDecimals: 2, ...options });
|
|
82
105
|
}
|
|
83
106
|
/**
|
|
84
107
|
* Convert to plain object for compatibility
|
|
@@ -92,6 +115,7 @@ export class Currency {
|
|
|
92
115
|
decimals: this.decimals,
|
|
93
116
|
contractId: this.contractId,
|
|
94
117
|
flag: this.flag,
|
|
118
|
+
zoom: this.zoom,
|
|
95
119
|
};
|
|
96
120
|
}
|
|
97
121
|
}
|
|
@@ -109,13 +133,35 @@ export function generateCurrencyId(currencyCode) {
|
|
|
109
133
|
* @returns The flag image path
|
|
110
134
|
*/
|
|
111
135
|
const flagFromCountries = (countryCode) => {
|
|
112
|
-
|
|
113
|
-
return country?.flag || '';
|
|
136
|
+
return countries.find((country) => country.code === countryCode)?.flag ?? '';
|
|
114
137
|
};
|
|
115
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Registry of all supported fiat currencies.
|
|
140
|
+
*
|
|
141
|
+
* Each currency is a Currency instance with:
|
|
142
|
+
* - Correct decimal precision (0 for JPY/KRW/VND/UGX, 2 for others)
|
|
143
|
+
* - Unique contract ID (keccak256 hash)
|
|
144
|
+
* - Flag image for UI display
|
|
145
|
+
*
|
|
146
|
+
* @remarks
|
|
147
|
+
* Payment providers support different subsets of currencies.
|
|
148
|
+
* Use `getCurrenciesForProvider()` in payment.ts to get supported currencies
|
|
149
|
+
* for a specific payment method.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* // Get USD currency info
|
|
154
|
+
* const usd = SUPPORTED_CURRENCIES.USD
|
|
155
|
+
* console.log(usd.symbol) // '$'
|
|
156
|
+
* console.log(usd.contractId) // '0x...' (keccak256('USD'))
|
|
157
|
+
*
|
|
158
|
+
* // Parse user input
|
|
159
|
+
* const amount = usd.parseAmount('100.50')
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
116
162
|
export const SUPPORTED_CURRENCIES = {
|
|
117
|
-
USD: new Currency('USD', '$', 'US Dollar', 2, USD_FLAG_DATA_URL),
|
|
118
|
-
EUR: new Currency('EUR', '€', 'Euro', 2, EUR_FLAG_DATA_URL),
|
|
163
|
+
USD: new Currency('USD', '$', 'US Dollar', 2, USD_FLAG_DATA_URL, 1),
|
|
164
|
+
EUR: new Currency('EUR', '€', 'Euro', 2, EUR_FLAG_DATA_URL, 1),
|
|
119
165
|
GBP: new Currency('GBP', '£', 'British Pound', 2, flagFromCountries('GBP')),
|
|
120
166
|
AUD: new Currency('AUD', 'A$', 'Australian Dollar', 2, flagFromCountries('AUD')),
|
|
121
167
|
CAD: new Currency('CAD', 'C$', 'Canadian Dollar', 2, flagFromCountries('CAD')),
|
|
@@ -151,17 +197,33 @@ export const SUPPORTED_CURRENCIES = {
|
|
|
151
197
|
RON: new Currency('RON', 'RON', 'Romanian Leu', 2, flagFromCountries('RON')),
|
|
152
198
|
HUF: new Currency('HUF', 'Ft', 'Hungarian Forint', 2, flagFromCountries('HUF')),
|
|
153
199
|
};
|
|
154
|
-
/**
|
|
200
|
+
/** Sorted entries for consistent iteration order */
|
|
155
201
|
const entries = Object.entries(SUPPORTED_CURRENCIES).sort(([a], [b]) => a.localeCompare(b));
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Map from contract ID (keccak256 hash) to Currency instance.
|
|
204
|
+
* Used for O(1) lookup of currency info from on-chain data.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const contractId = '0x...' // from blockchain event
|
|
209
|
+
* const currency = currencyById.get(contractId)
|
|
210
|
+
* if (currency) {
|
|
211
|
+
* console.log(currency.ticker) // 'USD'
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
162
215
|
export const currencyById = new Map(entries.map(([, currency]) => {
|
|
163
216
|
return [currency.contractId, currency];
|
|
164
217
|
}));
|
|
218
|
+
/**
|
|
219
|
+
* Get the currency name/ticker by contract ID
|
|
220
|
+
* @param contractId - The contract ID (keccak256 hash)
|
|
221
|
+
* @returns The currency ticker or the original contractId if not found
|
|
222
|
+
*/
|
|
223
|
+
export function getCurrencyNameByContractId(contractId) {
|
|
224
|
+
const currency = currencyById.get(contractId);
|
|
225
|
+
return currency?.ticker ?? contractId;
|
|
226
|
+
}
|
|
165
227
|
/**
|
|
166
228
|
* Get the currency info for a currency code
|
|
167
229
|
* @param currencyCode - The currency code
|
|
@@ -169,7 +231,7 @@ export const currencyById = new Map(entries.map(([, currency]) => {
|
|
|
169
231
|
*/
|
|
170
232
|
export function getCurrencyInfo(currencyCode) {
|
|
171
233
|
const currency = SUPPORTED_CURRENCIES[currencyCode.toUpperCase()];
|
|
172
|
-
return currency
|
|
234
|
+
return currency?.toJSON() ?? null;
|
|
173
235
|
}
|
|
174
236
|
/**
|
|
175
237
|
* Get the currency info for a contract ID
|
|
@@ -180,8 +242,7 @@ export function getCurrencyByContractId(contractId) {
|
|
|
180
242
|
if (!isHash(contractId)) {
|
|
181
243
|
throw new Error('Invalid contract ID');
|
|
182
244
|
}
|
|
183
|
-
|
|
184
|
-
return currency ? currency : null;
|
|
245
|
+
return currencyById.get(contractId) ?? null;
|
|
185
246
|
}
|
|
186
247
|
/**
|
|
187
248
|
* Format currency amount safely without Number() constructor, preserving decimal places
|
|
@@ -232,3 +293,23 @@ export function getAllSupportedCurrencies() {
|
|
|
232
293
|
export function isSupportedCurrency(currencyCode) {
|
|
233
294
|
return currencyCode.toUpperCase() in SUPPORTED_CURRENCIES;
|
|
234
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Format the default conversion rate based on currency decimals.
|
|
298
|
+
* Returns "1" for zero-decimal currencies (JPY, KRW, VND, UGX),
|
|
299
|
+
* or "1.00" for standard 2-decimal currencies (USD, EUR, etc.).
|
|
300
|
+
*
|
|
301
|
+
* @param currency - The currency to format the default rate for
|
|
302
|
+
* @returns Formatted default rate string matching the currency's decimal precision
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```ts
|
|
306
|
+
* formatDefaultRate(SUPPORTED_CURRENCIES.JPY) // "1" (0 decimals)
|
|
307
|
+
* formatDefaultRate(SUPPORTED_CURRENCIES.USD) // "1.00" (2 decimals)
|
|
308
|
+
* formatDefaultRate(SUPPORTED_CURRENCIES.EUR) // "1.00" (2 decimals)
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export function formatDefaultRate(currency) {
|
|
312
|
+
if (currency.decimals === 0)
|
|
313
|
+
return '1';
|
|
314
|
+
return `1.${'0'.repeat(currency.decimals)}`;
|
|
315
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { SUPPORTED_CURRENCIES, getCurrencyInfo, getCurrencyByContractId, getCurrencyNameByContractId, getAllSupportedCurrencies, isSupportedCurrency, formatCurrencyAmount, tickerToContractId, generateCurrencyId, formatDefaultRate, Currency, } from './currencies.js';
|
|
4
|
+
describe('currencies', () => {
|
|
5
|
+
describe('SUPPORTED_CURRENCIES', () => {
|
|
6
|
+
it('should have exactly 36 supported currencies', () => {
|
|
7
|
+
const count = Object.keys(SUPPORTED_CURRENCIES).length;
|
|
8
|
+
assert.equal(count, 36, `Expected 36 currencies, but found ${count}`);
|
|
9
|
+
});
|
|
10
|
+
it('should have USD as the first major currency', () => {
|
|
11
|
+
assert.ok(SUPPORTED_CURRENCIES.USD);
|
|
12
|
+
assert.equal(SUPPORTED_CURRENCIES.USD.ticker, 'USD');
|
|
13
|
+
assert.equal(SUPPORTED_CURRENCIES.USD.symbol, '$');
|
|
14
|
+
assert.equal(SUPPORTED_CURRENCIES.USD.decimals, 2);
|
|
15
|
+
});
|
|
16
|
+
it('should have EUR as a supported currency', () => {
|
|
17
|
+
assert.ok(SUPPORTED_CURRENCIES.EUR);
|
|
18
|
+
assert.equal(SUPPORTED_CURRENCIES.EUR.ticker, 'EUR');
|
|
19
|
+
assert.equal(SUPPORTED_CURRENCIES.EUR.symbol, '€');
|
|
20
|
+
assert.equal(SUPPORTED_CURRENCIES.EUR.decimals, 2);
|
|
21
|
+
});
|
|
22
|
+
it('should include zero-decimal currencies', () => {
|
|
23
|
+
assert.equal(SUPPORTED_CURRENCIES.JPY.decimals, 0);
|
|
24
|
+
assert.equal(SUPPORTED_CURRENCIES.KRW.decimals, 0);
|
|
25
|
+
assert.equal(SUPPORTED_CURRENCIES.VND.decimals, 0);
|
|
26
|
+
assert.equal(SUPPORTED_CURRENCIES.UGX.decimals, 0);
|
|
27
|
+
});
|
|
28
|
+
it('should have unique contract IDs for all currencies', () => {
|
|
29
|
+
const contractIds = new Set();
|
|
30
|
+
const currencies = Object.values(SUPPORTED_CURRENCIES);
|
|
31
|
+
for (const currency of currencies) {
|
|
32
|
+
assert.ok(!contractIds.has(currency.contractId), `Duplicate contract ID found for ${currency.ticker}`);
|
|
33
|
+
contractIds.add(currency.contractId);
|
|
34
|
+
}
|
|
35
|
+
assert.equal(contractIds.size, currencies.length);
|
|
36
|
+
});
|
|
37
|
+
it('should have flags for all currencies', () => {
|
|
38
|
+
const currencies = Object.values(SUPPORTED_CURRENCIES);
|
|
39
|
+
for (const currency of currencies) {
|
|
40
|
+
assert.ok(currency.flag, `Missing flag for ${currency.ticker}`);
|
|
41
|
+
assert.ok(currency.flag.length > 0, `Empty flag for ${currency.ticker}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
it('should have valid ticker codes', () => {
|
|
45
|
+
const currencies = Object.values(SUPPORTED_CURRENCIES);
|
|
46
|
+
for (const currency of currencies) {
|
|
47
|
+
assert.ok(currency.ticker.length === 3, `Invalid ticker length for ${currency.ticker}`);
|
|
48
|
+
assert.equal(currency.ticker, currency.ticker.toUpperCase(), `Ticker should be uppercase: ${currency.ticker}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('getCurrencyInfo', () => {
|
|
53
|
+
it('should return currency info for valid currency code', () => {
|
|
54
|
+
const usd = getCurrencyInfo('USD');
|
|
55
|
+
assert.ok(usd);
|
|
56
|
+
assert.equal(usd.ticker, 'USD');
|
|
57
|
+
assert.equal(usd.symbol, '$');
|
|
58
|
+
assert.equal(usd.decimals, 2);
|
|
59
|
+
});
|
|
60
|
+
it('should be case-insensitive', () => {
|
|
61
|
+
const usdLower = getCurrencyInfo('usd');
|
|
62
|
+
const usdUpper = getCurrencyInfo('USD');
|
|
63
|
+
const usdMixed = getCurrencyInfo('UsD');
|
|
64
|
+
assert.deepEqual(usdLower, usdUpper);
|
|
65
|
+
assert.deepEqual(usdLower, usdMixed);
|
|
66
|
+
});
|
|
67
|
+
it('should return null for invalid currency code', () => {
|
|
68
|
+
const invalid = getCurrencyInfo('INVALID');
|
|
69
|
+
assert.equal(invalid, null);
|
|
70
|
+
});
|
|
71
|
+
it('should return null for empty string', () => {
|
|
72
|
+
const empty = getCurrencyInfo('');
|
|
73
|
+
assert.equal(empty, null);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('getCurrencyByContractId', () => {
|
|
77
|
+
it('should return currency for valid contract ID', () => {
|
|
78
|
+
const usdContractId = SUPPORTED_CURRENCIES.USD.contractId;
|
|
79
|
+
const currency = getCurrencyByContractId(usdContractId);
|
|
80
|
+
assert.ok(currency);
|
|
81
|
+
assert.equal(currency.ticker, 'USD');
|
|
82
|
+
});
|
|
83
|
+
it('should return null for invalid contract ID', () => {
|
|
84
|
+
const fakeHash = '0x' + '0'.repeat(64);
|
|
85
|
+
const currency = getCurrencyByContractId(fakeHash);
|
|
86
|
+
assert.equal(currency, null);
|
|
87
|
+
});
|
|
88
|
+
it('should throw for non-hash input', () => {
|
|
89
|
+
assert.throws(() => {
|
|
90
|
+
getCurrencyByContractId('not-a-hash');
|
|
91
|
+
}, /Invalid contract ID/);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('getCurrencyNameByContractId', () => {
|
|
95
|
+
it('should return ticker for valid contract ID', () => {
|
|
96
|
+
const usdContractId = SUPPORTED_CURRENCIES.USD.contractId;
|
|
97
|
+
const name = getCurrencyNameByContractId(usdContractId);
|
|
98
|
+
assert.equal(name, 'USD');
|
|
99
|
+
});
|
|
100
|
+
it('should return original contract ID for unknown ID', () => {
|
|
101
|
+
const fakeHash = '0x' + '1'.repeat(64);
|
|
102
|
+
const name = getCurrencyNameByContractId(fakeHash);
|
|
103
|
+
assert.equal(name, fakeHash);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('getAllSupportedCurrencies', () => {
|
|
107
|
+
it('should return all currencies', () => {
|
|
108
|
+
const all = getAllSupportedCurrencies();
|
|
109
|
+
assert.equal(all.length, 36);
|
|
110
|
+
});
|
|
111
|
+
it('should return CurrencyInfo objects', () => {
|
|
112
|
+
const all = getAllSupportedCurrencies();
|
|
113
|
+
const first = all[0];
|
|
114
|
+
assert.ok(first.ticker);
|
|
115
|
+
assert.ok(first.symbol);
|
|
116
|
+
assert.ok(first.name);
|
|
117
|
+
assert.ok(typeof first.decimals === 'number');
|
|
118
|
+
assert.ok(first.contractId);
|
|
119
|
+
assert.ok(first.flag);
|
|
120
|
+
});
|
|
121
|
+
it('should return sorted currencies', () => {
|
|
122
|
+
const all = getAllSupportedCurrencies();
|
|
123
|
+
const tickers = all.map(c => c.ticker);
|
|
124
|
+
const sorted = [...tickers].sort();
|
|
125
|
+
assert.deepEqual(tickers, sorted);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('isSupportedCurrency', () => {
|
|
129
|
+
it('should return true for supported currencies', () => {
|
|
130
|
+
assert.equal(isSupportedCurrency('USD'), true);
|
|
131
|
+
assert.equal(isSupportedCurrency('EUR'), true);
|
|
132
|
+
assert.equal(isSupportedCurrency('GBP'), true);
|
|
133
|
+
});
|
|
134
|
+
it('should be case-insensitive', () => {
|
|
135
|
+
assert.equal(isSupportedCurrency('usd'), true);
|
|
136
|
+
assert.equal(isSupportedCurrency('USD'), true);
|
|
137
|
+
assert.equal(isSupportedCurrency('UsD'), true);
|
|
138
|
+
});
|
|
139
|
+
it('should return false for unsupported currencies', () => {
|
|
140
|
+
assert.equal(isSupportedCurrency('INVALID'), false);
|
|
141
|
+
assert.equal(isSupportedCurrency(''), false);
|
|
142
|
+
assert.equal(isSupportedCurrency('XXX'), false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('formatCurrencyAmount', () => {
|
|
146
|
+
it('should format USD amounts with 2 decimals', () => {
|
|
147
|
+
assert.equal(formatCurrencyAmount(100, 'USD'), '$100.00');
|
|
148
|
+
assert.equal(formatCurrencyAmount(1000, 'USD'), '$1,000.00');
|
|
149
|
+
assert.equal(formatCurrencyAmount(1234.56, 'USD'), '$1,234.56');
|
|
150
|
+
});
|
|
151
|
+
it('should format JPY amounts with 0 decimals', () => {
|
|
152
|
+
assert.equal(formatCurrencyAmount(100, 'JPY'), '¥100');
|
|
153
|
+
assert.equal(formatCurrencyAmount(1000, 'JPY'), '¥1,000');
|
|
154
|
+
assert.equal(formatCurrencyAmount(1234, 'JPY'), '¥1,234');
|
|
155
|
+
});
|
|
156
|
+
it('should format EUR amounts correctly', () => {
|
|
157
|
+
assert.equal(formatCurrencyAmount(100, 'EUR'), '€100.00');
|
|
158
|
+
assert.equal(formatCurrencyAmount(1234.56, 'EUR'), '€1,234.56');
|
|
159
|
+
});
|
|
160
|
+
it('should handle string inputs', () => {
|
|
161
|
+
assert.equal(formatCurrencyAmount('100', 'USD'), '$100.00');
|
|
162
|
+
assert.equal(formatCurrencyAmount('1234.56', 'USD'), '$1,234.56');
|
|
163
|
+
});
|
|
164
|
+
it('should handle unknown currencies', () => {
|
|
165
|
+
const result = formatCurrencyAmount(100, 'UNKNOWN');
|
|
166
|
+
assert.equal(result, '100 UNKNOWN');
|
|
167
|
+
});
|
|
168
|
+
it('should add thousands separators', () => {
|
|
169
|
+
assert.equal(formatCurrencyAmount(1000000, 'USD'), '$1,000,000.00');
|
|
170
|
+
assert.equal(formatCurrencyAmount(123456789, 'JPY'), '¥123,456,789');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('tickerToContractId', () => {
|
|
174
|
+
it('should generate consistent contract IDs', () => {
|
|
175
|
+
const id1 = tickerToContractId('USD');
|
|
176
|
+
const id2 = tickerToContractId('USD');
|
|
177
|
+
assert.equal(id1, id2);
|
|
178
|
+
});
|
|
179
|
+
it('should be case-insensitive', () => {
|
|
180
|
+
const idUpper = tickerToContractId('USD');
|
|
181
|
+
const idLower = tickerToContractId('usd');
|
|
182
|
+
const idMixed = tickerToContractId('UsD');
|
|
183
|
+
assert.equal(idUpper, idLower);
|
|
184
|
+
assert.equal(idUpper, idMixed);
|
|
185
|
+
});
|
|
186
|
+
it('should generate different IDs for different tickers', () => {
|
|
187
|
+
const usdId = tickerToContractId('USD');
|
|
188
|
+
const eurId = tickerToContractId('EUR');
|
|
189
|
+
const gbpId = tickerToContractId('GBP');
|
|
190
|
+
assert.notEqual(usdId, eurId);
|
|
191
|
+
assert.notEqual(usdId, gbpId);
|
|
192
|
+
assert.notEqual(eurId, gbpId);
|
|
193
|
+
});
|
|
194
|
+
it('should generate 32-byte hashes', () => {
|
|
195
|
+
const id = tickerToContractId('USD');
|
|
196
|
+
assert.equal(id.length, 66); // '0x' + 64 hex chars
|
|
197
|
+
assert.ok(id.startsWith('0x'));
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('Currency class', () => {
|
|
201
|
+
it('should parse amounts correctly', () => {
|
|
202
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
203
|
+
const amount = usd.parseAmount('100.50');
|
|
204
|
+
assert.equal(amount, 10050n); // 100.50 with 2 decimals
|
|
205
|
+
});
|
|
206
|
+
it('should return null for invalid amounts', () => {
|
|
207
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
208
|
+
const amount = usd.parseAmount('invalid');
|
|
209
|
+
assert.equal(amount, null);
|
|
210
|
+
});
|
|
211
|
+
it('should format amounts correctly', () => {
|
|
212
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
213
|
+
const formatted = usd.formatAmount(10050n);
|
|
214
|
+
assert.equal(formatted, '100.50');
|
|
215
|
+
});
|
|
216
|
+
it('should handle null amounts', () => {
|
|
217
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
218
|
+
const formatted = usd.formatAmount(null);
|
|
219
|
+
assert.equal(formatted, null);
|
|
220
|
+
});
|
|
221
|
+
it('should format with custom options', () => {
|
|
222
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
223
|
+
const formatted = usd.formatAmount(10050n, { truncateDecimals: 0, padDecimals: 0 });
|
|
224
|
+
assert.ok(formatted, 'formatted should be truthy');
|
|
225
|
+
assert.ok(!formatted.includes('.'), `formatted should not include '.', but got: ${formatted}`);
|
|
226
|
+
});
|
|
227
|
+
it('should have default zoom of 1.5 when not specified', () => {
|
|
228
|
+
// GBP uses flagFromCountries and no explicit zoom
|
|
229
|
+
const gbp = SUPPORTED_CURRENCIES.GBP;
|
|
230
|
+
assert.equal(gbp.zoom, 1.5);
|
|
231
|
+
});
|
|
232
|
+
it('should use explicit zoom when specified', () => {
|
|
233
|
+
// USD has explicit zoom of 1
|
|
234
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
235
|
+
assert.equal(usd.zoom, 1);
|
|
236
|
+
});
|
|
237
|
+
it('should have flag from countries data', () => {
|
|
238
|
+
// GBP gets flag from countries.json
|
|
239
|
+
const gbp = SUPPORTED_CURRENCIES.GBP;
|
|
240
|
+
assert.ok(gbp.flag.length > 0);
|
|
241
|
+
});
|
|
242
|
+
it('should have explicit flag for USD/EUR', () => {
|
|
243
|
+
const usd = SUPPORTED_CURRENCIES.USD;
|
|
244
|
+
const eur = SUPPORTED_CURRENCIES.EUR;
|
|
245
|
+
assert.ok(usd.flag.startsWith('data:'));
|
|
246
|
+
assert.ok(eur.flag.startsWith('data:'));
|
|
247
|
+
});
|
|
248
|
+
it('should default flag to empty string when not provided', () => {
|
|
249
|
+
const currency = new Currency('TST', '$', 'Test', 2);
|
|
250
|
+
assert.equal(currency.flag, '');
|
|
251
|
+
});
|
|
252
|
+
it('should default zoom to 1.5 when not provided', () => {
|
|
253
|
+
const currency = new Currency('TST', '$', 'Test', 2);
|
|
254
|
+
assert.equal(currency.zoom, 1.5);
|
|
255
|
+
});
|
|
256
|
+
it('should use provided flag when specified', () => {
|
|
257
|
+
const currency = new Currency('TST', '$', 'Test', 2, 'custom-flag.png');
|
|
258
|
+
assert.equal(currency.flag, 'custom-flag.png');
|
|
259
|
+
});
|
|
260
|
+
it('should use provided zoom when specified', () => {
|
|
261
|
+
const currency = new Currency('TST', '$', 'Test', 2, '', 2.0);
|
|
262
|
+
assert.equal(currency.zoom, 2.0);
|
|
263
|
+
});
|
|
264
|
+
it('should use provided flag with default zoom', () => {
|
|
265
|
+
const currency = new Currency('TST', '$', 'Test', 2, 'flag.png');
|
|
266
|
+
assert.equal(currency.flag, 'flag.png');
|
|
267
|
+
assert.equal(currency.zoom, 1.5); // default
|
|
268
|
+
});
|
|
269
|
+
it('should handle empty string flag', () => {
|
|
270
|
+
const currency = new Currency('TST', '$', 'Test', 2, '');
|
|
271
|
+
assert.equal(currency.flag, '');
|
|
272
|
+
assert.equal(currency.zoom, 1.5);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('generateCurrencyId', () => {
|
|
276
|
+
it('should generate consistent IDs', () => {
|
|
277
|
+
const id1 = generateCurrencyId('USD');
|
|
278
|
+
const id2 = generateCurrencyId('USD');
|
|
279
|
+
assert.equal(id1, id2);
|
|
280
|
+
});
|
|
281
|
+
it('should be case insensitive', () => {
|
|
282
|
+
const upper = generateCurrencyId('USD');
|
|
283
|
+
const lower = generateCurrencyId('usd');
|
|
284
|
+
const mixed = generateCurrencyId('UsD');
|
|
285
|
+
assert.equal(upper, lower);
|
|
286
|
+
assert.equal(upper, mixed);
|
|
287
|
+
});
|
|
288
|
+
it('should generate different IDs for different currencies', () => {
|
|
289
|
+
const usd = generateCurrencyId('USD');
|
|
290
|
+
const eur = generateCurrencyId('EUR');
|
|
291
|
+
assert.notEqual(usd, eur);
|
|
292
|
+
});
|
|
293
|
+
it('should match tickerToContractId output', () => {
|
|
294
|
+
const fromGenerate = generateCurrencyId('USD');
|
|
295
|
+
const fromTicker = tickerToContractId('USD');
|
|
296
|
+
assert.equal(fromGenerate, fromTicker);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
describe('formatDefaultRate', () => {
|
|
300
|
+
it('returns "1" for zero-decimal currencies (JPY)', () => {
|
|
301
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.JPY);
|
|
302
|
+
assert.equal(result, '1');
|
|
303
|
+
});
|
|
304
|
+
it('returns "1" for zero-decimal currencies (KRW)', () => {
|
|
305
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.KRW);
|
|
306
|
+
assert.equal(result, '1');
|
|
307
|
+
});
|
|
308
|
+
it('returns "1" for zero-decimal currencies (VND)', () => {
|
|
309
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.VND);
|
|
310
|
+
assert.equal(result, '1');
|
|
311
|
+
});
|
|
312
|
+
it('returns "1" for zero-decimal currencies (UGX)', () => {
|
|
313
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.UGX);
|
|
314
|
+
assert.equal(result, '1');
|
|
315
|
+
});
|
|
316
|
+
it('returns "1.00" for 2-decimal currencies (USD)', () => {
|
|
317
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.USD);
|
|
318
|
+
assert.equal(result, '1.00');
|
|
319
|
+
});
|
|
320
|
+
it('returns "1.00" for 2-decimal currencies (EUR)', () => {
|
|
321
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.EUR);
|
|
322
|
+
assert.equal(result, '1.00');
|
|
323
|
+
});
|
|
324
|
+
it('returns "1.00" for 2-decimal currencies (GBP)', () => {
|
|
325
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.GBP);
|
|
326
|
+
assert.equal(result, '1.00');
|
|
327
|
+
});
|
|
328
|
+
it('returns "1.00" for 2-decimal currencies (ARS)', () => {
|
|
329
|
+
const result = formatDefaultRate(SUPPORTED_CURRENCIES.ARS);
|
|
330
|
+
assert.equal(result, '1.00');
|
|
331
|
+
});
|
|
332
|
+
it('works with CurrencyInfo (toJSON output)', () => {
|
|
333
|
+
const currencyInfo = SUPPORTED_CURRENCIES.USD.toJSON();
|
|
334
|
+
const result = formatDefaultRate(currencyInfo);
|
|
335
|
+
assert.equal(result, '1.00');
|
|
336
|
+
});
|
|
337
|
+
it('handles custom Currency with different decimals', () => {
|
|
338
|
+
const customCurrency = new Currency('TST', '$', 'Test', 3);
|
|
339
|
+
const result = formatDefaultRate(customCurrency);
|
|
340
|
+
assert.equal(result, '1.000');
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
});
|