@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
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { base } from 'viem/chains';
|
|
4
|
+
import { processConversionRates, getConversionRate, extractCurrencies, getLatestRateForCurrency, getPrimaryConversionRate, getPrimaryRateForCurrency, getPrimaryVerifier, getPrimaryCurrency, convertTokenInputToCurrency, convertCurrencyToTokenOutput, } from './conversionRates.js';
|
|
5
|
+
import { SUPPORTED_CURRENCIES } from './currencies.js';
|
|
6
|
+
import { v2BaseContracts } from './contracts.js';
|
|
7
|
+
describe('conversionRates', () => {
|
|
8
|
+
// Test data
|
|
9
|
+
const USD_CONTRACT_ID = SUPPORTED_CURRENCIES.USD.contractId;
|
|
10
|
+
const EUR_CONTRACT_ID = SUPPORTED_CURRENCIES.EUR.contractId;
|
|
11
|
+
// Use actual v2 verifier addresses from contracts
|
|
12
|
+
const VENMO_VERIFIER = v2BaseContracts.venmoReclaimVerifier[0];
|
|
13
|
+
const ZELLE_VERIFIER = v2BaseContracts.zelleBaseVerifier[0];
|
|
14
|
+
const createMockEvent = (overrides = {}) => ({
|
|
15
|
+
depositConversionRateUpdatedId: 'rate-1',
|
|
16
|
+
currency: USD_CONTRACT_ID,
|
|
17
|
+
verifier: VENMO_VERIFIER,
|
|
18
|
+
value: '1000000000000000000', // 1.0
|
|
19
|
+
orderId: '0x0001',
|
|
20
|
+
depositId: 'deposit-1',
|
|
21
|
+
transaction: {
|
|
22
|
+
hash: '0xhash1',
|
|
23
|
+
block: { timestamp: '1700000000' },
|
|
24
|
+
},
|
|
25
|
+
currentDepositConversionRateUpdatedId: 'rate-1',
|
|
26
|
+
currentDepositConversionRateUpdated: {
|
|
27
|
+
value: '1000000000000000000',
|
|
28
|
+
},
|
|
29
|
+
...overrides,
|
|
30
|
+
});
|
|
31
|
+
const MOCK_TOKEN = {
|
|
32
|
+
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
33
|
+
decimals: 6,
|
|
34
|
+
symbol: 'USDC',
|
|
35
|
+
name: 'USD Coin',
|
|
36
|
+
chainId: base.id,
|
|
37
|
+
};
|
|
38
|
+
describe('processConversionRates', () => {
|
|
39
|
+
it('should process empty events array', () => {
|
|
40
|
+
const result = processConversionRates({
|
|
41
|
+
chainId: base.id,
|
|
42
|
+
events: [],
|
|
43
|
+
});
|
|
44
|
+
assert.equal(result.size, 0);
|
|
45
|
+
});
|
|
46
|
+
it('should process single event', () => {
|
|
47
|
+
const events = [createMockEvent()];
|
|
48
|
+
const result = processConversionRates({
|
|
49
|
+
chainId: base.id,
|
|
50
|
+
events,
|
|
51
|
+
});
|
|
52
|
+
assert.equal(result.size, 1);
|
|
53
|
+
const venmoRates = result.get('venmo');
|
|
54
|
+
assert.ok(venmoRates, 'Should have venmo rates');
|
|
55
|
+
const usdRates = venmoRates.get(USD_CONTRACT_ID);
|
|
56
|
+
assert.ok(usdRates, 'Should have USD rates');
|
|
57
|
+
assert.equal(usdRates.length, 1);
|
|
58
|
+
});
|
|
59
|
+
it('should skip events without currentDepositConversionRateUpdated', () => {
|
|
60
|
+
const events = [
|
|
61
|
+
createMockEvent({ currentDepositConversionRateUpdated: undefined }),
|
|
62
|
+
];
|
|
63
|
+
const result = processConversionRates({
|
|
64
|
+
chainId: base.id,
|
|
65
|
+
events,
|
|
66
|
+
});
|
|
67
|
+
assert.equal(result.size, 0);
|
|
68
|
+
});
|
|
69
|
+
it('should sort events by orderId in ascending order by default', () => {
|
|
70
|
+
const events = [
|
|
71
|
+
createMockEvent({ orderId: '0x0002' }),
|
|
72
|
+
createMockEvent({ orderId: '0x0001' }),
|
|
73
|
+
createMockEvent({ orderId: '0x0003' }),
|
|
74
|
+
];
|
|
75
|
+
const result = processConversionRates({
|
|
76
|
+
chainId: base.id,
|
|
77
|
+
events,
|
|
78
|
+
includeHistorical: true,
|
|
79
|
+
});
|
|
80
|
+
const venmoRates = result.get('venmo')?.get(USD_CONTRACT_ID);
|
|
81
|
+
assert.ok(venmoRates);
|
|
82
|
+
assert.equal(venmoRates[0].orderId, '0x0001');
|
|
83
|
+
assert.equal(venmoRates[1].orderId, '0x0002');
|
|
84
|
+
assert.equal(venmoRates[2].orderId, '0x0003');
|
|
85
|
+
});
|
|
86
|
+
it('should sort events in descending order when specified', () => {
|
|
87
|
+
const events = [
|
|
88
|
+
createMockEvent({ orderId: '0x0002' }),
|
|
89
|
+
createMockEvent({ orderId: '0x0001' }),
|
|
90
|
+
createMockEvent({ orderId: '0x0003' }),
|
|
91
|
+
];
|
|
92
|
+
const result = processConversionRates({
|
|
93
|
+
chainId: base.id,
|
|
94
|
+
events,
|
|
95
|
+
sortOrder: 'desc',
|
|
96
|
+
includeHistorical: true,
|
|
97
|
+
});
|
|
98
|
+
const venmoRates = result.get('venmo')?.get(USD_CONTRACT_ID);
|
|
99
|
+
assert.ok(venmoRates);
|
|
100
|
+
assert.equal(venmoRates[0].orderId, '0x0003');
|
|
101
|
+
});
|
|
102
|
+
it('should keep only latest rate when includeHistorical is false', () => {
|
|
103
|
+
const events = [
|
|
104
|
+
createMockEvent({
|
|
105
|
+
orderId: '0x0001',
|
|
106
|
+
currentDepositConversionRateUpdated: { value: '1000000000000000000' },
|
|
107
|
+
}),
|
|
108
|
+
createMockEvent({
|
|
109
|
+
orderId: '0x0002',
|
|
110
|
+
currentDepositConversionRateUpdated: { value: '1050000000000000000' },
|
|
111
|
+
}),
|
|
112
|
+
];
|
|
113
|
+
const result = processConversionRates({
|
|
114
|
+
chainId: base.id,
|
|
115
|
+
events,
|
|
116
|
+
includeHistorical: false,
|
|
117
|
+
});
|
|
118
|
+
const venmoRates = result.get('venmo')?.get(USD_CONTRACT_ID);
|
|
119
|
+
assert.ok(venmoRates);
|
|
120
|
+
assert.equal(venmoRates.length, 1);
|
|
121
|
+
// Should have the latest rate (sorted asc, so last one replaces)
|
|
122
|
+
assert.equal(venmoRates[0].value, '1050000000000000000');
|
|
123
|
+
});
|
|
124
|
+
it('should group rates by provider', () => {
|
|
125
|
+
const events = [
|
|
126
|
+
createMockEvent({ verifier: VENMO_VERIFIER }),
|
|
127
|
+
createMockEvent({ verifier: ZELLE_VERIFIER }),
|
|
128
|
+
];
|
|
129
|
+
const result = processConversionRates({
|
|
130
|
+
chainId: base.id,
|
|
131
|
+
events,
|
|
132
|
+
});
|
|
133
|
+
assert.ok(result.get('venmo'), 'Should have venmo rates');
|
|
134
|
+
assert.ok(result.get('zelle'), 'Should have zelle rates');
|
|
135
|
+
});
|
|
136
|
+
it('should group rates by currency', () => {
|
|
137
|
+
const events = [
|
|
138
|
+
createMockEvent({ currency: USD_CONTRACT_ID }),
|
|
139
|
+
createMockEvent({ currency: EUR_CONTRACT_ID }),
|
|
140
|
+
];
|
|
141
|
+
const result = processConversionRates({
|
|
142
|
+
chainId: base.id,
|
|
143
|
+
events,
|
|
144
|
+
});
|
|
145
|
+
const venmoRates = result.get('venmo');
|
|
146
|
+
assert.ok(venmoRates);
|
|
147
|
+
assert.ok(venmoRates.get(USD_CONTRACT_ID), 'Should have USD rates');
|
|
148
|
+
assert.ok(venmoRates.get(EUR_CONTRACT_ID), 'Should have EUR rates');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('getConversionRate', () => {
|
|
152
|
+
it('should return null for empty events', () => {
|
|
153
|
+
const result = getConversionRate({
|
|
154
|
+
events: [],
|
|
155
|
+
currency: USD_CONTRACT_ID,
|
|
156
|
+
verifier: VENMO_VERIFIER,
|
|
157
|
+
chainId: base.id,
|
|
158
|
+
});
|
|
159
|
+
assert.equal(result, null);
|
|
160
|
+
});
|
|
161
|
+
it('should return rate for matching currency and verifier', () => {
|
|
162
|
+
const events = [createMockEvent()];
|
|
163
|
+
const result = getConversionRate({
|
|
164
|
+
events,
|
|
165
|
+
currency: USD_CONTRACT_ID,
|
|
166
|
+
verifier: VENMO_VERIFIER,
|
|
167
|
+
chainId: base.id,
|
|
168
|
+
});
|
|
169
|
+
assert.ok(result, 'Should return a rate');
|
|
170
|
+
assert.ok(!Array.isArray(result), 'Should return single rate');
|
|
171
|
+
assert.equal(result.value, '1000000000000000000');
|
|
172
|
+
});
|
|
173
|
+
it('should return null for non-matching currency', () => {
|
|
174
|
+
const events = [createMockEvent({ currency: USD_CONTRACT_ID })];
|
|
175
|
+
const result = getConversionRate({
|
|
176
|
+
events,
|
|
177
|
+
currency: EUR_CONTRACT_ID,
|
|
178
|
+
verifier: VENMO_VERIFIER,
|
|
179
|
+
chainId: base.id,
|
|
180
|
+
});
|
|
181
|
+
assert.equal(result, null);
|
|
182
|
+
});
|
|
183
|
+
it('should return array when includeHistory is true', () => {
|
|
184
|
+
const events = [
|
|
185
|
+
createMockEvent({ orderId: '0x0001' }),
|
|
186
|
+
createMockEvent({ orderId: '0x0002' }),
|
|
187
|
+
];
|
|
188
|
+
const result = getConversionRate({
|
|
189
|
+
events,
|
|
190
|
+
currency: USD_CONTRACT_ID,
|
|
191
|
+
verifier: VENMO_VERIFIER,
|
|
192
|
+
chainId: base.id,
|
|
193
|
+
includeHistory: true,
|
|
194
|
+
});
|
|
195
|
+
assert.ok(Array.isArray(result), 'Should return array');
|
|
196
|
+
assert.equal(result.length, 2);
|
|
197
|
+
});
|
|
198
|
+
it('should use fallback rate when no matching rate found', () => {
|
|
199
|
+
const fallbackRate = {
|
|
200
|
+
currency: USD_CONTRACT_ID,
|
|
201
|
+
value: '999000000000000000',
|
|
202
|
+
verifier: VENMO_VERIFIER,
|
|
203
|
+
};
|
|
204
|
+
const result = getConversionRate({
|
|
205
|
+
events: [],
|
|
206
|
+
currency: USD_CONTRACT_ID,
|
|
207
|
+
verifier: VENMO_VERIFIER,
|
|
208
|
+
chainId: base.id,
|
|
209
|
+
fallbackRate,
|
|
210
|
+
});
|
|
211
|
+
assert.ok(result, 'Should return fallback rate');
|
|
212
|
+
assert.ok(!Array.isArray(result));
|
|
213
|
+
assert.equal(result.value, '999000000000000000');
|
|
214
|
+
});
|
|
215
|
+
it('should not use fallback rate when currency does not match', () => {
|
|
216
|
+
const fallbackRate = {
|
|
217
|
+
currency: EUR_CONTRACT_ID,
|
|
218
|
+
value: '999000000000000000',
|
|
219
|
+
verifier: VENMO_VERIFIER,
|
|
220
|
+
};
|
|
221
|
+
const result = getConversionRate({
|
|
222
|
+
events: [],
|
|
223
|
+
currency: USD_CONTRACT_ID,
|
|
224
|
+
verifier: VENMO_VERIFIER,
|
|
225
|
+
chainId: base.id,
|
|
226
|
+
fallbackRate,
|
|
227
|
+
});
|
|
228
|
+
assert.equal(result, null);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
describe('extractCurrencies', () => {
|
|
232
|
+
it('should extract currencies from provider rates', () => {
|
|
233
|
+
const providerRates = new Map([
|
|
234
|
+
[
|
|
235
|
+
'venmo',
|
|
236
|
+
new Map([
|
|
237
|
+
[USD_CONTRACT_ID, [createMockEvent()]],
|
|
238
|
+
[EUR_CONTRACT_ID, [createMockEvent({ currency: EUR_CONTRACT_ID })]],
|
|
239
|
+
]),
|
|
240
|
+
],
|
|
241
|
+
]);
|
|
242
|
+
const currencies = extractCurrencies({ providerRates });
|
|
243
|
+
assert.equal(currencies.length, 2);
|
|
244
|
+
assert.ok(currencies.includes(USD_CONTRACT_ID));
|
|
245
|
+
assert.ok(currencies.includes(EUR_CONTRACT_ID));
|
|
246
|
+
});
|
|
247
|
+
it('should filter by providers when specified', () => {
|
|
248
|
+
const providerRates = new Map([
|
|
249
|
+
['venmo', new Map([[USD_CONTRACT_ID, [createMockEvent()]]])],
|
|
250
|
+
['zelle', new Map([[EUR_CONTRACT_ID, [createMockEvent({ currency: EUR_CONTRACT_ID })]]])],
|
|
251
|
+
]);
|
|
252
|
+
const currencies = extractCurrencies({
|
|
253
|
+
providerRates,
|
|
254
|
+
providersOnly: ['venmo'],
|
|
255
|
+
});
|
|
256
|
+
assert.equal(currencies.length, 1);
|
|
257
|
+
assert.ok(currencies.includes(USD_CONTRACT_ID));
|
|
258
|
+
});
|
|
259
|
+
it('should filter by currencies when specified', () => {
|
|
260
|
+
const providerRates = new Map([
|
|
261
|
+
[
|
|
262
|
+
'venmo',
|
|
263
|
+
new Map([
|
|
264
|
+
[USD_CONTRACT_ID, [createMockEvent()]],
|
|
265
|
+
[EUR_CONTRACT_ID, [createMockEvent({ currency: EUR_CONTRACT_ID })]],
|
|
266
|
+
]),
|
|
267
|
+
],
|
|
268
|
+
]);
|
|
269
|
+
const currencies = extractCurrencies({
|
|
270
|
+
providerRates,
|
|
271
|
+
currenciesOnly: [USD_CONTRACT_ID],
|
|
272
|
+
});
|
|
273
|
+
assert.equal(currencies.length, 1);
|
|
274
|
+
assert.ok(currencies.includes(USD_CONTRACT_ID));
|
|
275
|
+
});
|
|
276
|
+
it('should return empty array for empty provider rates', () => {
|
|
277
|
+
const currencies = extractCurrencies({ providerRates: new Map() });
|
|
278
|
+
assert.equal(currencies.length, 0);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
describe('getLatestRateForCurrency', () => {
|
|
282
|
+
it('should return null for empty provider rates', () => {
|
|
283
|
+
const result = getLatestRateForCurrency({
|
|
284
|
+
providerRates: new Map(),
|
|
285
|
+
currency: USD_CONTRACT_ID,
|
|
286
|
+
});
|
|
287
|
+
assert.equal(result, null);
|
|
288
|
+
});
|
|
289
|
+
it('should return latest rate by timestamp', () => {
|
|
290
|
+
const oldRate = createMockEvent({
|
|
291
|
+
transaction: { hash: '0x1', block: { timestamp: '1700000000' } },
|
|
292
|
+
});
|
|
293
|
+
const newRate = createMockEvent({
|
|
294
|
+
transaction: { hash: '0x2', block: { timestamp: '1700001000' } },
|
|
295
|
+
verifier: ZELLE_VERIFIER,
|
|
296
|
+
});
|
|
297
|
+
const providerRates = new Map([
|
|
298
|
+
['venmo', new Map([[USD_CONTRACT_ID, oldRate]])],
|
|
299
|
+
['zelle', new Map([[USD_CONTRACT_ID, newRate]])],
|
|
300
|
+
]);
|
|
301
|
+
const result = getLatestRateForCurrency({
|
|
302
|
+
providerRates,
|
|
303
|
+
currency: USD_CONTRACT_ID,
|
|
304
|
+
});
|
|
305
|
+
assert.ok(result);
|
|
306
|
+
assert.equal(result.transaction.block.timestamp, '1700001000');
|
|
307
|
+
});
|
|
308
|
+
it('should prefer specified provider when timestamps match', () => {
|
|
309
|
+
const rate = createMockEvent({
|
|
310
|
+
transaction: { hash: '0x1', block: { timestamp: '1700000000' } },
|
|
311
|
+
});
|
|
312
|
+
const providerRates = new Map([
|
|
313
|
+
['venmo', new Map([[USD_CONTRACT_ID, rate]])],
|
|
314
|
+
['zelle', new Map([[USD_CONTRACT_ID, { ...rate, verifier: ZELLE_VERIFIER }]])],
|
|
315
|
+
]);
|
|
316
|
+
const result = getLatestRateForCurrency({
|
|
317
|
+
providerRates,
|
|
318
|
+
currency: USD_CONTRACT_ID,
|
|
319
|
+
preferredProvider: 'venmo',
|
|
320
|
+
});
|
|
321
|
+
assert.ok(result);
|
|
322
|
+
assert.equal(result.verifier, VENMO_VERIFIER);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
describe('getPrimaryConversionRate', () => {
|
|
326
|
+
it('should return null for empty provider rates', () => {
|
|
327
|
+
const result = getPrimaryConversionRate(new Map());
|
|
328
|
+
assert.equal(result, null);
|
|
329
|
+
});
|
|
330
|
+
it('should prioritize USD over other currencies', () => {
|
|
331
|
+
const usdRate = createMockEvent({ currency: USD_CONTRACT_ID });
|
|
332
|
+
const eurRate = createMockEvent({ currency: EUR_CONTRACT_ID });
|
|
333
|
+
const providerRates = new Map([
|
|
334
|
+
['venmo', new Map([[EUR_CONTRACT_ID, eurRate]])],
|
|
335
|
+
['zelle', new Map([[USD_CONTRACT_ID, usdRate]])],
|
|
336
|
+
]);
|
|
337
|
+
const result = getPrimaryConversionRate(providerRates);
|
|
338
|
+
assert.ok(result);
|
|
339
|
+
assert.equal(result.currency, USD_CONTRACT_ID);
|
|
340
|
+
});
|
|
341
|
+
it('should return first available rate if no USD', () => {
|
|
342
|
+
const eurRate = createMockEvent({ currency: EUR_CONTRACT_ID });
|
|
343
|
+
const providerRates = new Map([
|
|
344
|
+
['venmo', new Map([[EUR_CONTRACT_ID, eurRate]])],
|
|
345
|
+
]);
|
|
346
|
+
const result = getPrimaryConversionRate(providerRates);
|
|
347
|
+
assert.ok(result);
|
|
348
|
+
assert.equal(result.currency, EUR_CONTRACT_ID);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
describe('getPrimaryRateForCurrency', () => {
|
|
352
|
+
it('should return rate for specified currency', () => {
|
|
353
|
+
const eurRate = createMockEvent({ currency: EUR_CONTRACT_ID });
|
|
354
|
+
const providerRates = new Map([
|
|
355
|
+
['venmo', new Map([[EUR_CONTRACT_ID, eurRate]])],
|
|
356
|
+
]);
|
|
357
|
+
const result = getPrimaryRateForCurrency(providerRates, EUR_CONTRACT_ID);
|
|
358
|
+
assert.ok(result);
|
|
359
|
+
assert.equal(result.currency, EUR_CONTRACT_ID);
|
|
360
|
+
});
|
|
361
|
+
it('should return null if currency not found', () => {
|
|
362
|
+
const usdRate = createMockEvent({ currency: USD_CONTRACT_ID });
|
|
363
|
+
const providerRates = new Map([
|
|
364
|
+
['venmo', new Map([[USD_CONTRACT_ID, usdRate]])],
|
|
365
|
+
]);
|
|
366
|
+
const result = getPrimaryRateForCurrency(providerRates, EUR_CONTRACT_ID);
|
|
367
|
+
assert.equal(result, null);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
describe('getPrimaryVerifier', () => {
|
|
371
|
+
it('should return verifier from primary rate', () => {
|
|
372
|
+
const rate = createMockEvent({ verifier: VENMO_VERIFIER });
|
|
373
|
+
const providerRates = new Map([
|
|
374
|
+
['venmo', new Map([[USD_CONTRACT_ID, rate]])],
|
|
375
|
+
]);
|
|
376
|
+
const result = getPrimaryVerifier(providerRates);
|
|
377
|
+
assert.equal(result, VENMO_VERIFIER);
|
|
378
|
+
});
|
|
379
|
+
it('should return null for empty rates', () => {
|
|
380
|
+
const result = getPrimaryVerifier(new Map());
|
|
381
|
+
assert.equal(result, null);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
describe('getPrimaryCurrency', () => {
|
|
385
|
+
it('should return currency from primary rate', () => {
|
|
386
|
+
const rate = createMockEvent({ currency: USD_CONTRACT_ID });
|
|
387
|
+
const providerRates = new Map([
|
|
388
|
+
['venmo', new Map([[USD_CONTRACT_ID, rate]])],
|
|
389
|
+
]);
|
|
390
|
+
const result = getPrimaryCurrency(providerRates);
|
|
391
|
+
assert.equal(result, USD_CONTRACT_ID);
|
|
392
|
+
});
|
|
393
|
+
it('should return null for empty rates', () => {
|
|
394
|
+
const result = getPrimaryCurrency(new Map());
|
|
395
|
+
assert.equal(result, null);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
describe('convertTokenInputToCurrency', () => {
|
|
399
|
+
it('should convert token amount at 1:1 rate', () => {
|
|
400
|
+
const rate = 10n ** 18n; // 1.0 rate
|
|
401
|
+
const amountOutInt = 100000000n; // 100 USDC
|
|
402
|
+
const result = convertTokenInputToCurrency({
|
|
403
|
+
token: MOCK_TOKEN,
|
|
404
|
+
amountOutInt,
|
|
405
|
+
rate,
|
|
406
|
+
});
|
|
407
|
+
// At 1:1, 100 tokens should give ~100 fiat units
|
|
408
|
+
// Result is rounded up to nearest cent (10^4 for USDC with 6 decimals)
|
|
409
|
+
assert.equal(result, 100000000n);
|
|
410
|
+
});
|
|
411
|
+
it('should convert token amount at 1.05 rate', () => {
|
|
412
|
+
const rate = 1050000000000000000n; // 1.05 rate
|
|
413
|
+
const amountOutInt = 100000000n; // 100 USDC
|
|
414
|
+
const result = convertTokenInputToCurrency({
|
|
415
|
+
token: MOCK_TOKEN,
|
|
416
|
+
amountOutInt,
|
|
417
|
+
rate,
|
|
418
|
+
});
|
|
419
|
+
// 100 * 1.05 = 105, rounded up to nearest cent
|
|
420
|
+
assert.equal(result, 105000000n);
|
|
421
|
+
});
|
|
422
|
+
it('should round up to nearest cent', () => {
|
|
423
|
+
const rate = 10n ** 18n;
|
|
424
|
+
const amountOutInt = 100000001n; // 100.000001 USDC
|
|
425
|
+
const result = convertTokenInputToCurrency({
|
|
426
|
+
token: MOCK_TOKEN,
|
|
427
|
+
amountOutInt,
|
|
428
|
+
rate,
|
|
429
|
+
});
|
|
430
|
+
// Should round up from 100.000001 to 100.01 (nearest cent in 6 decimals)
|
|
431
|
+
assert.equal(result, 100010000n);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
describe('convertCurrencyToTokenOutput', () => {
|
|
435
|
+
it('should convert fiat to tokens at 1:1 rate', () => {
|
|
436
|
+
const rate = 10n ** 18n;
|
|
437
|
+
const currencyAmountInt = 10000n; // $100.00 in cents
|
|
438
|
+
const result = convertCurrencyToTokenOutput({
|
|
439
|
+
token: MOCK_TOKEN,
|
|
440
|
+
currency: SUPPORTED_CURRENCIES.USD,
|
|
441
|
+
currencyAmountInt,
|
|
442
|
+
rate,
|
|
443
|
+
});
|
|
444
|
+
// At 1:1, $100 should give 100 USDC = 100_000000
|
|
445
|
+
assert.equal(result, 100000000n);
|
|
446
|
+
});
|
|
447
|
+
it('should convert fiat to tokens at 1.05 rate', () => {
|
|
448
|
+
const rate = 1050000000000000000n; // 1.05 rate
|
|
449
|
+
const currencyAmountInt = 10500n; // $105.00 in cents
|
|
450
|
+
const result = convertCurrencyToTokenOutput({
|
|
451
|
+
token: MOCK_TOKEN,
|
|
452
|
+
currency: SUPPORTED_CURRENCIES.USD,
|
|
453
|
+
currencyAmountInt,
|
|
454
|
+
rate,
|
|
455
|
+
});
|
|
456
|
+
// $105 at 1.05 rate should give 100 USDC
|
|
457
|
+
assert.equal(result, 100000000n);
|
|
458
|
+
});
|
|
459
|
+
it('should floor to smallest token unit', () => {
|
|
460
|
+
const rate = 10n ** 18n;
|
|
461
|
+
const currencyAmountInt = 10001n; // $100.01
|
|
462
|
+
const result = convertCurrencyToTokenOutput({
|
|
463
|
+
token: MOCK_TOKEN,
|
|
464
|
+
currency: SUPPORTED_CURRENCIES.USD,
|
|
465
|
+
currencyAmountInt,
|
|
466
|
+
rate,
|
|
467
|
+
});
|
|
468
|
+
// $100.01 at 1:1 = 100.01 USDC = 100_010000 base units
|
|
469
|
+
// Floored to smallest unit (10^4 for 6-2 decimal difference)
|
|
470
|
+
assert.equal(result, 100010000n);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|
package/dist/src/currencies.d.ts
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
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 { type Hex } from 'viem';
|
|
2
16
|
import { type FormatRateDisplayOptions } from './units.js';
|
|
3
17
|
/**
|
|
4
|
-
*
|
|
18
|
+
* Currency information interface for display and calculations.
|
|
19
|
+
* Used for both the Currency class instances and serialized JSON representations.
|
|
5
20
|
*/
|
|
6
21
|
export interface CurrencyInfo {
|
|
22
|
+
/** ISO 4217 currency code (e.g., 'USD', 'EUR') */
|
|
7
23
|
ticker: string;
|
|
24
|
+
/** Currency symbol for display (e.g., '$', '€', '£') */
|
|
8
25
|
symbol: string;
|
|
26
|
+
/** Full currency name (e.g., 'US Dollar') */
|
|
9
27
|
name: string;
|
|
28
|
+
/** Number of decimal places (2 for most, 0 for JPY/KRW) */
|
|
10
29
|
decimals: number;
|
|
30
|
+
/** keccak256 hash of the currency code, used in smart contracts */
|
|
11
31
|
contractId: Hex;
|
|
32
|
+
/** Flag image URL or data URL for display */
|
|
12
33
|
flag: string;
|
|
34
|
+
/** Zoom level for flag display (1 for square flags USD/EUR, 1.5 for rectangular) */
|
|
35
|
+
zoom: number;
|
|
13
36
|
}
|
|
14
37
|
/**
|
|
15
38
|
* Country info from the countries.json file
|
|
@@ -28,12 +51,20 @@ export interface CountryInfo {
|
|
|
28
51
|
*/
|
|
29
52
|
export declare const tickerToContractId: (ticker: string) => Hex;
|
|
30
53
|
/**
|
|
31
|
-
* Currency class
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
54
|
+
* Currency class representing a fiat currency with parsing and formatting utilities.
|
|
55
|
+
*
|
|
56
|
+
* Each currency has:
|
|
57
|
+
* - A unique ticker (ISO 4217 code)
|
|
58
|
+
* - A display symbol
|
|
59
|
+
* - A contract ID (keccak256 hash for on-chain use)
|
|
60
|
+
* - Decimal precision for amount formatting
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const usd = SUPPORTED_CURRENCIES.USD
|
|
65
|
+
* const amount = usd.parseAmount('100.50') // 10050n (2 decimals)
|
|
66
|
+
* const display = usd.formatAmount(10050n) // '100.50'
|
|
67
|
+
* ```
|
|
37
68
|
*/
|
|
38
69
|
export declare class Currency implements CurrencyInfo {
|
|
39
70
|
readonly ticker: string;
|
|
@@ -42,7 +73,18 @@ export declare class Currency implements CurrencyInfo {
|
|
|
42
73
|
readonly decimals: number;
|
|
43
74
|
readonly contractId: Hex;
|
|
44
75
|
readonly flag: string;
|
|
45
|
-
|
|
76
|
+
readonly zoom: number;
|
|
77
|
+
/**
|
|
78
|
+
* Create a new Currency instance.
|
|
79
|
+
*
|
|
80
|
+
* @param code - ISO 4217 currency code (converted to uppercase)
|
|
81
|
+
* @param symbol - Display symbol (e.g., '$')
|
|
82
|
+
* @param name - Full currency name
|
|
83
|
+
* @param decimals - Number of decimal places
|
|
84
|
+
* @param flag - Optional flag image URL
|
|
85
|
+
* @param zoom - Optional zoom level for flag display (default: 1.5)
|
|
86
|
+
*/
|
|
87
|
+
constructor(code: string, symbol: string, name: string, decimals: number, flag?: string, zoom?: number);
|
|
46
88
|
/**
|
|
47
89
|
* Generate a contract ID for the currency
|
|
48
90
|
* @returns The contract ID
|
|
@@ -71,6 +113,30 @@ export declare class Currency implements CurrencyInfo {
|
|
|
71
113
|
* @param currencyCode - The currency code
|
|
72
114
|
*/
|
|
73
115
|
export declare function generateCurrencyId(currencyCode: string): string;
|
|
116
|
+
/**
|
|
117
|
+
* Registry of all supported fiat currencies.
|
|
118
|
+
*
|
|
119
|
+
* Each currency is a Currency instance with:
|
|
120
|
+
* - Correct decimal precision (0 for JPY/KRW/VND/UGX, 2 for others)
|
|
121
|
+
* - Unique contract ID (keccak256 hash)
|
|
122
|
+
* - Flag image for UI display
|
|
123
|
+
*
|
|
124
|
+
* @remarks
|
|
125
|
+
* Payment providers support different subsets of currencies.
|
|
126
|
+
* Use `getCurrenciesForProvider()` in payment.ts to get supported currencies
|
|
127
|
+
* for a specific payment method.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* // Get USD currency info
|
|
132
|
+
* const usd = SUPPORTED_CURRENCIES.USD
|
|
133
|
+
* console.log(usd.symbol) // '$'
|
|
134
|
+
* console.log(usd.contractId) // '0x...' (keccak256('USD'))
|
|
135
|
+
*
|
|
136
|
+
* // Parse user input
|
|
137
|
+
* const amount = usd.parseAmount('100.50')
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
74
140
|
export declare const SUPPORTED_CURRENCIES: {
|
|
75
141
|
readonly USD: Currency;
|
|
76
142
|
readonly EUR: Currency;
|
|
@@ -109,9 +175,31 @@ export declare const SUPPORTED_CURRENCIES: {
|
|
|
109
175
|
readonly RON: Currency;
|
|
110
176
|
readonly HUF: Currency;
|
|
111
177
|
};
|
|
112
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* Union type of all supported currency codes.
|
|
180
|
+
* Use for type-safe currency code parameters.
|
|
181
|
+
*/
|
|
113
182
|
export type SupportedCurrencyCode = keyof typeof SUPPORTED_CURRENCIES;
|
|
183
|
+
/**
|
|
184
|
+
* Map from contract ID (keccak256 hash) to Currency instance.
|
|
185
|
+
* Used for O(1) lookup of currency info from on-chain data.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* const contractId = '0x...' // from blockchain event
|
|
190
|
+
* const currency = currencyById.get(contractId)
|
|
191
|
+
* if (currency) {
|
|
192
|
+
* console.log(currency.ticker) // 'USD'
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
114
196
|
export declare const currencyById: Map<`0x${string}`, Currency>;
|
|
197
|
+
/**
|
|
198
|
+
* Get the currency name/ticker by contract ID
|
|
199
|
+
* @param contractId - The contract ID (keccak256 hash)
|
|
200
|
+
* @returns The currency ticker or the original contractId if not found
|
|
201
|
+
*/
|
|
202
|
+
export declare function getCurrencyNameByContractId(contractId: string): string;
|
|
115
203
|
/**
|
|
116
204
|
* Get the currency info for a currency code
|
|
117
205
|
* @param currencyCode - The currency code
|
|
@@ -142,3 +230,19 @@ export declare function getAllSupportedCurrencies(): CurrencyInfo[];
|
|
|
142
230
|
* @returns True if the currency is supported, false otherwise
|
|
143
231
|
*/
|
|
144
232
|
export declare function isSupportedCurrency(currencyCode: string): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Format the default conversion rate based on currency decimals.
|
|
235
|
+
* Returns "1" for zero-decimal currencies (JPY, KRW, VND, UGX),
|
|
236
|
+
* or "1.00" for standard 2-decimal currencies (USD, EUR, etc.).
|
|
237
|
+
*
|
|
238
|
+
* @param currency - The currency to format the default rate for
|
|
239
|
+
* @returns Formatted default rate string matching the currency's decimal precision
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* formatDefaultRate(SUPPORTED_CURRENCIES.JPY) // "1" (0 decimals)
|
|
244
|
+
* formatDefaultRate(SUPPORTED_CURRENCIES.USD) // "1.00" (2 decimals)
|
|
245
|
+
* formatDefaultRate(SUPPORTED_CURRENCIES.EUR) // "1.00" (2 decimals)
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export declare function formatDefaultRate(currency: Currency | CurrencyInfo): string;
|