@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.
Files changed (122) hide show
  1. package/dist/src/chain.d.ts +436 -4
  2. package/dist/src/chain.js +80 -0
  3. package/dist/src/chain.test.d.ts +1 -0
  4. package/dist/src/chain.test.js +112 -0
  5. package/dist/src/contracts.d.ts +196 -20
  6. package/dist/src/contracts.js +245 -17
  7. package/dist/src/contracts.test.d.ts +1 -0
  8. package/dist/src/contracts.test.js +243 -0
  9. package/dist/src/conversionRates.d.ts +99 -8
  10. package/dist/src/conversionRates.js +74 -4
  11. package/dist/src/conversionRates.test.d.ts +1 -0
  12. package/dist/src/conversionRates.test.js +473 -0
  13. package/dist/src/currencies.d.ts +113 -9
  14. package/dist/src/currencies.js +119 -38
  15. package/dist/src/currencies.test.d.ts +1 -0
  16. package/dist/src/currencies.test.js +343 -0
  17. package/dist/src/data/countries.json +166 -166
  18. package/dist/src/deposit.d.ts +69 -1
  19. package/dist/src/deposit.js +52 -1
  20. package/dist/src/depositKeys.d.ts +154 -0
  21. package/dist/src/depositKeys.js +104 -0
  22. package/dist/src/depositKeys.test.d.ts +1 -0
  23. package/dist/src/depositKeys.test.js +231 -0
  24. package/dist/src/endpoints.d.ts +51 -0
  25. package/dist/src/endpoints.js +70 -0
  26. package/dist/src/endpoints.test.d.ts +1 -0
  27. package/dist/src/endpoints.test.js +54 -0
  28. package/dist/src/events.d.ts +40 -2
  29. package/dist/src/events.js +86 -10
  30. package/dist/src/events.test.d.ts +1 -0
  31. package/dist/src/events.test.js +111 -0
  32. package/dist/src/fees.d.ts +136 -0
  33. package/dist/src/fees.js +178 -0
  34. package/dist/src/fees.test.d.ts +1 -0
  35. package/dist/src/fees.test.js +165 -0
  36. package/dist/src/ids.d.ts +125 -22
  37. package/dist/src/ids.js +211 -61
  38. package/dist/src/ids.test.d.ts +1 -0
  39. package/dist/src/ids.test.js +607 -0
  40. package/dist/src/index.d.ts +30 -1
  41. package/dist/src/index.js +30 -1
  42. package/dist/src/input.d.ts +23 -4
  43. package/dist/src/input.js +23 -4
  44. package/dist/src/intent.d.ts +38 -2
  45. package/dist/src/intent.js +28 -1
  46. package/dist/src/intent.test.d.ts +1 -0
  47. package/dist/src/intent.test.js +119 -0
  48. package/dist/src/json.d.ts +38 -1
  49. package/dist/src/json.js +38 -2
  50. package/dist/src/normalization.d.ts +224 -0
  51. package/dist/src/normalization.js +119 -0
  52. package/dist/src/normalization.test.d.ts +1 -0
  53. package/dist/src/normalization.test.js +284 -0
  54. package/dist/src/participants.d.ts +19 -4
  55. package/dist/src/participants.js +19 -4
  56. package/dist/src/payment.d.ts +333 -16
  57. package/dist/src/payment.js +768 -58
  58. package/dist/src/payment.test.d.ts +1 -0
  59. package/dist/src/payment.test.js +272 -0
  60. package/dist/src/peer-types.d.ts +327 -0
  61. package/dist/src/peer-types.js +169 -0
  62. package/dist/src/profit.d.ts +26 -5
  63. package/dist/src/profit.js +31 -8
  64. package/dist/src/profit.test.d.ts +1 -0
  65. package/dist/src/profit.test.js +315 -0
  66. package/dist/src/reputation.d.ts +125 -0
  67. package/dist/src/reputation.js +188 -0
  68. package/dist/src/reputation.test.d.ts +1 -0
  69. package/dist/src/reputation.test.js +156 -0
  70. package/dist/src/sort.d.ts +49 -10
  71. package/dist/src/sort.js +40 -9
  72. package/dist/src/sort.test.d.ts +1 -0
  73. package/dist/src/sort.test.js +216 -0
  74. package/dist/src/stateKey.d.ts +35 -0
  75. package/dist/src/stateKey.js +36 -0
  76. package/dist/src/stats.d.ts +55 -1
  77. package/dist/src/stats.js +46 -1
  78. package/dist/src/stats.test.d.ts +1 -0
  79. package/dist/src/stats.test.js +104 -0
  80. package/dist/src/time.d.ts +59 -9
  81. package/dist/src/time.js +71 -15
  82. package/dist/src/time.test.d.ts +1 -0
  83. package/dist/src/time.test.js +231 -0
  84. package/dist/src/tokens.d.ts +11972 -145
  85. package/dist/src/tokens.js +98 -17
  86. package/dist/src/tokens.test.d.ts +1 -0
  87. package/dist/src/tokens.test.js +193 -0
  88. package/dist/src/units.d.ts +183 -0
  89. package/dist/src/units.js +206 -6
  90. package/dist/src/units.test.d.ts +1 -0
  91. package/dist/src/units.test.js +327 -0
  92. package/dist/src/zkp2p-types.d.ts +273 -4
  93. package/dist/src/zkp2p-types.js +169 -1
  94. package/dist/tsconfig.tsbuildinfo +1 -1
  95. package/package.json +116 -26
  96. package/scripts/convert-svg.ts +0 -22
  97. package/src/chain.ts +0 -27
  98. package/src/contracts.ts +0 -89
  99. package/src/conversionRates.ts +0 -356
  100. package/src/currencies.ts +0 -286
  101. package/src/data/countries.json +0 -1099
  102. package/src/deposit.ts +0 -2
  103. package/src/events.ts +0 -21
  104. package/src/flags/eur.svg +0 -4
  105. package/src/flags/usd.svg +0 -25
  106. package/src/flags.ts +0 -4
  107. package/src/global.d.ts +0 -9
  108. package/src/ids.ts +0 -137
  109. package/src/index.ts +0 -16
  110. package/src/input.ts +0 -35
  111. package/src/intent.ts +0 -15
  112. package/src/json.ts +0 -42
  113. package/src/participants.ts +0 -14
  114. package/src/payment.ts +0 -573
  115. package/src/profit.ts +0 -55
  116. package/src/sort.ts +0 -49
  117. package/src/stats.ts +0 -10
  118. package/src/time.ts +0 -123
  119. package/src/tokens.ts +0 -159
  120. package/src/units.ts +0 -120
  121. package/src/zkp2p-types.ts +0 -55
  122. package/tsconfig.json +0 -18
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @fileoverview Peer Taker Tiers Reputation System
3
+ *
4
+ * Implements the reputation-based rate limiting system for buyers (takers).
5
+ * Based on: https://docs.peer.xyz/guides/for-buyers/reputation
6
+ *
7
+ * The system works by combining:
8
+ * - **Tier configs**: Volume thresholds, base caps, and cooldown periods
9
+ * - **Platform risk configs**: Multipliers and cooldown flags from payment.ts
10
+ *
11
+ * @remarks
12
+ * These limits only apply to Base chain deposits using the peer gating service.
13
+ * Pulsechain deposits do not have reputation-based restrictions.
14
+ *
15
+ * Tier progression (by fulfilled volume in USD):
16
+ * - Peer Peasant: $0+ → $100 cap, 12h cooldown
17
+ * - Peer: $500+ → $250 cap, 6h cooldown
18
+ * - Peer Plus: $2,000+ → $1,000 cap, no cooldown
19
+ * - Peer Pro: $10,000+ → $2,500 cap, no cooldown
20
+ * - Peer Platinum: $25,000+ → $5,000 cap, no cooldown
21
+ * - Peer President: Manually assigned VIP → unlimited cap, no cooldown, bypasses all locks
22
+ *
23
+ * Platform multipliers modify the base cap:
24
+ * - Low risk (Revolut, Wise, Monzo, MercadoPago): 5x multiplier, no cooldown
25
+ * - Medium risk (Zelle): 1.5x multiplier
26
+ * - High risk (Venmo, CashApp): 1x multiplier
27
+ * - Highest risk (PayPal): 0.75x multiplier, requires Peer Plus tier
28
+ *
29
+ * @see {@link payment} for platform risk configurations
30
+ */
31
+ import { type ProviderKey, type TakerTier } from './payment.js';
32
+ /**
33
+ * Configuration for a single reputation tier.
34
+ * Defines the requirements and limits for users at this tier level.
35
+ */
36
+ export interface TierConfig {
37
+ /** Display name for the tier */
38
+ name: string;
39
+ /** Minimum fulfilled volume (USD) to reach this tier. Infinity = manually assigned only. */
40
+ volumeThreshold: number;
41
+ /** Base per-intent cap (USD) before platform multiplier. 0 = unlimited (VIP). */
42
+ baseCap: number;
43
+ /** Cooldown duration in hours (0 = no cooldown) */
44
+ cooldownHours: number;
45
+ /** Volume range display text (e.g. "$500 - $2k") */
46
+ volumeRangeDisplay: string;
47
+ }
48
+ /**
49
+ * Complete tier configuration registry.
50
+ * Maps universal tier names to their configuration.
51
+ *
52
+ * @remarks
53
+ * Tier names in the codebase use short universal names (peasant, neutral, plus, pro, platinum, president)
54
+ * while the UI displays the peer brand names (Peer Peasant, Peer, Peer Plus, etc.)
55
+ */
56
+ export declare const TAKER_TIERS: Record<TakerTier, TierConfig>;
57
+ /**
58
+ * Ordered list of tiers from lowest to highest.
59
+ * president sits above platinum but is never reached via getTierFromVolume.
60
+ */
61
+ export declare const TIER_ORDER: TakerTier[];
62
+ /**
63
+ * Get the volume range display string for a tier.
64
+ * Matches peer.xyz's TR() function behaviour.
65
+ *
66
+ * @example
67
+ * getVolumeRangeDisplay('pro') // "$10k - $25k"
68
+ * getVolumeRangeDisplay('president') // "VIP"
69
+ */
70
+ export declare function getVolumeRangeDisplay(tier: TakerTier): string;
71
+ /**
72
+ * Calculate the effective cap for a tier and platform combination.
73
+ * Derives from tier base cap and platform risk multiplier.
74
+ *
75
+ * @param tier - The user's current tier
76
+ * @param providerKey - The payment platform
77
+ * @returns Effective cap in USD, null if platform is locked for this tier,
78
+ * or Infinity if the user is president (no cap).
79
+ */
80
+ export declare function getEffectiveCap({ tier, providerKey, }: {
81
+ tier: TakerTier;
82
+ providerKey: ProviderKey;
83
+ }): number | null;
84
+ /**
85
+ * Get the cooldown duration for a tier and platform.
86
+ * Derives from tier cooldown hours and platform hasCooldown flag.
87
+ * @returns Cooldown in hours, or 0 if no cooldown.
88
+ */
89
+ export declare function getCooldownHours({ tier, providerKey, }: {
90
+ tier: TakerTier;
91
+ providerKey: ProviderKey;
92
+ }): number;
93
+ /**
94
+ * Format cooldown duration for display
95
+ */
96
+ export declare function formatCooldown(hours: number): string;
97
+ /**
98
+ * Format cap for display
99
+ */
100
+ export declare function formatCap(cap: number | null): string;
101
+ /**
102
+ * Get tier from fulfilled volume.
103
+ * Note: 'president' is never returned here — it is assigned manually.
104
+ */
105
+ export declare function getTierFromVolume(volumeUsd: number): TakerTier;
106
+ /**
107
+ * Complete user reputation state including tier, volume, and cooldown status.
108
+ * Fetched from the peer gating service API.
109
+ *
110
+ * @remarks
111
+ * Volume is measured in USDC which is assumed 1:1 with USD for tier calculations.
112
+ */
113
+ export interface UserReputation {
114
+ /** Current tier based on fulfilled volume */
115
+ tier: TakerTier;
116
+ /** Total fulfilled volume in USDC (6 decimals, 1:1 with USD) */
117
+ fulfilledVolumeUsdc: number;
118
+ lockScore: number;
119
+ cooldownEndsAt: Date | null;
120
+ isOnCooldown: boolean;
121
+ }
122
+ /**
123
+ * Default reputation for new/unknown users (most restrictive)
124
+ */
125
+ export declare const DEFAULT_REPUTATION: UserReputation;
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @fileoverview Peer Taker Tiers Reputation System
3
+ *
4
+ * Implements the reputation-based rate limiting system for buyers (takers).
5
+ * Based on: https://docs.peer.xyz/guides/for-buyers/reputation
6
+ *
7
+ * The system works by combining:
8
+ * - **Tier configs**: Volume thresholds, base caps, and cooldown periods
9
+ * - **Platform risk configs**: Multipliers and cooldown flags from payment.ts
10
+ *
11
+ * @remarks
12
+ * These limits only apply to Base chain deposits using the peer gating service.
13
+ * Pulsechain deposits do not have reputation-based restrictions.
14
+ *
15
+ * Tier progression (by fulfilled volume in USD):
16
+ * - Peer Peasant: $0+ → $100 cap, 12h cooldown
17
+ * - Peer: $500+ → $250 cap, 6h cooldown
18
+ * - Peer Plus: $2,000+ → $1,000 cap, no cooldown
19
+ * - Peer Pro: $10,000+ → $2,500 cap, no cooldown
20
+ * - Peer Platinum: $25,000+ → $5,000 cap, no cooldown
21
+ * - Peer President: Manually assigned VIP → unlimited cap, no cooldown, bypasses all locks
22
+ *
23
+ * Platform multipliers modify the base cap:
24
+ * - Low risk (Revolut, Wise, Monzo, MercadoPago): 5x multiplier, no cooldown
25
+ * - Medium risk (Zelle): 1.5x multiplier
26
+ * - High risk (Venmo, CashApp): 1x multiplier
27
+ * - Highest risk (PayPal): 0.75x multiplier, requires Peer Plus tier
28
+ *
29
+ * @see {@link payment} for platform risk configurations
30
+ */
31
+ import { base } from 'viem/chains';
32
+ import { tiers, getPlatformRisk, } from './payment.js';
33
+ /**
34
+ * Complete tier configuration registry.
35
+ * Maps universal tier names to their configuration.
36
+ *
37
+ * @remarks
38
+ * Tier names in the codebase use short universal names (peasant, neutral, plus, pro, platinum, president)
39
+ * while the UI displays the peer brand names (Peer Peasant, Peer, Peer Plus, etc.)
40
+ */
41
+ export const TAKER_TIERS = {
42
+ [tiers.PEASANT]: {
43
+ name: 'Peer Peasant',
44
+ volumeThreshold: 0,
45
+ baseCap: 100,
46
+ cooldownHours: 12,
47
+ volumeRangeDisplay: '$0 - $500',
48
+ },
49
+ [tiers.NEUTRAL]: {
50
+ name: 'Peer',
51
+ volumeThreshold: 500,
52
+ baseCap: 250,
53
+ cooldownHours: 6,
54
+ volumeRangeDisplay: '$500 - $2k',
55
+ },
56
+ [tiers.PLUS]: {
57
+ name: 'Peer Plus',
58
+ volumeThreshold: 2000,
59
+ baseCap: 1000,
60
+ cooldownHours: 0,
61
+ volumeRangeDisplay: '$2k - $10k',
62
+ },
63
+ [tiers.PRO]: {
64
+ name: 'Peer Pro',
65
+ volumeThreshold: 10000,
66
+ baseCap: 2500,
67
+ cooldownHours: 0,
68
+ volumeRangeDisplay: '$10k - $25k',
69
+ },
70
+ [tiers.PLATINUM]: {
71
+ name: 'Peer Platinum',
72
+ volumeThreshold: 25000,
73
+ baseCap: 5000,
74
+ cooldownHours: 0,
75
+ volumeRangeDisplay: '$25k+',
76
+ },
77
+ [tiers.PRESIDENT]: {
78
+ name: 'Peer President',
79
+ volumeThreshold: Infinity, // Manually assigned — cannot be reached via volume
80
+ baseCap: 0, // 0 = no cap (VIP limits)
81
+ cooldownHours: 0,
82
+ volumeRangeDisplay: 'VIP',
83
+ },
84
+ };
85
+ /**
86
+ * Ordered list of tiers from lowest to highest.
87
+ * president sits above platinum but is never reached via getTierFromVolume.
88
+ */
89
+ export const TIER_ORDER = [
90
+ tiers.PEASANT, tiers.NEUTRAL, tiers.PLUS, tiers.PRO, tiers.PLATINUM, tiers.PRESIDENT,
91
+ ];
92
+ /**
93
+ * Get the volume range display string for a tier.
94
+ * Matches peer.xyz's TR() function behaviour.
95
+ *
96
+ * @example
97
+ * getVolumeRangeDisplay('pro') // "$10k - $25k"
98
+ * getVolumeRangeDisplay('president') // "VIP"
99
+ */
100
+ export function getVolumeRangeDisplay(tier) {
101
+ return TAKER_TIERS[tier].volumeRangeDisplay;
102
+ }
103
+ /**
104
+ * Calculate the effective cap for a tier and platform combination.
105
+ * Derives from tier base cap and platform risk multiplier.
106
+ *
107
+ * @param tier - The user's current tier
108
+ * @param providerKey - The payment platform
109
+ * @returns Effective cap in USD, null if platform is locked for this tier,
110
+ * or Infinity if the user is president (no cap).
111
+ */
112
+ export function getEffectiveCap({ tier, providerKey, }) {
113
+ // President bypasses all caps and locks
114
+ if (tier === tiers.PRESIDENT)
115
+ return Infinity;
116
+ const tierConfig = TAKER_TIERS[tier];
117
+ const platformRisk = getPlatformRisk(base.id, providerKey);
118
+ // Check if platform is locked for this tier
119
+ if (platformRisk.minimumTier) {
120
+ const tierIndex = TIER_ORDER.indexOf(tier);
121
+ const minTierIndex = TIER_ORDER.indexOf(platformRisk.minimumTier);
122
+ if (tierIndex < minTierIndex) {
123
+ return null; // Platform locked
124
+ }
125
+ }
126
+ return Math.floor(tierConfig.baseCap * platformRisk.capMultiplier);
127
+ }
128
+ /**
129
+ * Get the cooldown duration for a tier and platform.
130
+ * Derives from tier cooldown hours and platform hasCooldown flag.
131
+ * @returns Cooldown in hours, or 0 if no cooldown.
132
+ */
133
+ export function getCooldownHours({ tier, providerKey, }) {
134
+ // President has no cooldown on any platform
135
+ if (tier === tiers.PRESIDENT)
136
+ return 0;
137
+ const tierConfig = TAKER_TIERS[tier];
138
+ const platformRisk = getPlatformRisk(base.id, providerKey);
139
+ // Low-risk platforms have no cooldown regardless of tier
140
+ if (!platformRisk.hasCooldown)
141
+ return 0;
142
+ return tierConfig.cooldownHours;
143
+ }
144
+ /**
145
+ * Format cooldown duration for display
146
+ */
147
+ export function formatCooldown(hours) {
148
+ if (hours === 0)
149
+ return 'No cooldown';
150
+ if (hours < 1)
151
+ return `${Math.round(hours * 60)}m cooldown`;
152
+ return `${hours}h cooldown`;
153
+ }
154
+ /**
155
+ * Format cap for display
156
+ */
157
+ export function formatCap(cap) {
158
+ if (cap === null)
159
+ return 'Locked';
160
+ if (cap === Infinity)
161
+ return 'No limit';
162
+ return `$${cap.toLocaleString()} max`;
163
+ }
164
+ /**
165
+ * Get tier from fulfilled volume.
166
+ * Note: 'president' is never returned here — it is assigned manually.
167
+ */
168
+ export function getTierFromVolume(volumeUsd) {
169
+ // Iterate from platinum down (exclude president — manual assignment only)
170
+ const volumeTiers = TIER_ORDER.filter(t => t !== tiers.PRESIDENT);
171
+ for (let i = volumeTiers.length - 1; i >= 0; i--) {
172
+ const tier = volumeTiers[i];
173
+ if (volumeUsd >= TAKER_TIERS[tier].volumeThreshold) {
174
+ return tier;
175
+ }
176
+ }
177
+ return 'peasant';
178
+ }
179
+ /**
180
+ * Default reputation for new/unknown users (most restrictive)
181
+ */
182
+ export const DEFAULT_REPUTATION = {
183
+ tier: 'peasant',
184
+ fulfilledVolumeUsdc: 0,
185
+ lockScore: 0,
186
+ cooldownEndsAt: null,
187
+ isOnCooldown: false,
188
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,156 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { TAKER_TIERS, TIER_ORDER, getTierFromVolume, getEffectiveCap, getCooldownHours, getVolumeRangeDisplay, formatCooldown, formatCap, } from './reputation.js';
4
+ import { tiers } from './payment.js';
5
+ describe('TAKER_TIERS', () => {
6
+ it('should have correct tier names', () => {
7
+ assert.equal(TAKER_TIERS.peasant.name, 'Peer Peasant');
8
+ assert.equal(TAKER_TIERS.neutral.name, 'Peer');
9
+ assert.equal(TAKER_TIERS.plus.name, 'Peer Plus');
10
+ assert.equal(TAKER_TIERS.pro.name, 'Peer Pro');
11
+ assert.equal(TAKER_TIERS.platinum.name, 'Peer Platinum');
12
+ });
13
+ it('should have correct volume thresholds', () => {
14
+ assert.equal(TAKER_TIERS.peasant.volumeThreshold, 0);
15
+ assert.equal(TAKER_TIERS.neutral.volumeThreshold, 500);
16
+ assert.equal(TAKER_TIERS.plus.volumeThreshold, 2000);
17
+ assert.equal(TAKER_TIERS.pro.volumeThreshold, 10000);
18
+ assert.equal(TAKER_TIERS.platinum.volumeThreshold, 25000);
19
+ });
20
+ it('should have correct base caps', () => {
21
+ assert.equal(TAKER_TIERS.peasant.baseCap, 100);
22
+ assert.equal(TAKER_TIERS.neutral.baseCap, 250);
23
+ assert.equal(TAKER_TIERS.plus.baseCap, 1000);
24
+ assert.equal(TAKER_TIERS.pro.baseCap, 2500);
25
+ assert.equal(TAKER_TIERS.platinum.baseCap, 5000);
26
+ });
27
+ it('should have correct cooldown hours', () => {
28
+ assert.equal(TAKER_TIERS.peasant.cooldownHours, 12);
29
+ assert.equal(TAKER_TIERS.neutral.cooldownHours, 6);
30
+ assert.equal(TAKER_TIERS.plus.cooldownHours, 0);
31
+ assert.equal(TAKER_TIERS.pro.cooldownHours, 0);
32
+ assert.equal(TAKER_TIERS.platinum.cooldownHours, 0);
33
+ });
34
+ });
35
+ describe('TIER_ORDER', () => {
36
+ it('should be ordered from lowest to highest with president at top', () => {
37
+ assert.deepEqual(TIER_ORDER, [
38
+ tiers.PEASANT, tiers.NEUTRAL, tiers.PLUS, tiers.PRO, tiers.PLATINUM, tiers.PRESIDENT,
39
+ ]);
40
+ });
41
+ it('should match all keys in TAKER_TIERS', () => {
42
+ const tierKeys = Object.keys(TAKER_TIERS);
43
+ assert.equal(TIER_ORDER.length, tierKeys.length);
44
+ for (const tier of TIER_ORDER) {
45
+ assert.ok(tierKeys.includes(tier), `${tier} should be in TAKER_TIERS`);
46
+ }
47
+ });
48
+ });
49
+ describe('getVolumeRangeDisplay', () => {
50
+ it('should return correct range strings', () => {
51
+ assert.equal(getVolumeRangeDisplay('peasant'), '$0 - $500');
52
+ assert.equal(getVolumeRangeDisplay('neutral'), '$500 - $2k');
53
+ assert.equal(getVolumeRangeDisplay('plus'), '$2k - $10k');
54
+ assert.equal(getVolumeRangeDisplay('pro'), '$10k - $25k');
55
+ assert.equal(getVolumeRangeDisplay('platinum'), '$25k+');
56
+ assert.equal(getVolumeRangeDisplay('president'), 'VIP');
57
+ });
58
+ });
59
+ describe('getTierFromVolume', () => {
60
+ it('should return peasant for 0 volume', () => {
61
+ assert.equal(getTierFromVolume(0), 'peasant');
62
+ });
63
+ it('should return peasant for volume < 500', () => {
64
+ assert.equal(getTierFromVolume(100), 'peasant');
65
+ assert.equal(getTierFromVolume(499), 'peasant');
66
+ });
67
+ it('should return neutral for volume >= 500', () => {
68
+ assert.equal(getTierFromVolume(500), 'neutral');
69
+ assert.equal(getTierFromVolume(1000), 'neutral');
70
+ assert.equal(getTierFromVolume(1999), 'neutral');
71
+ });
72
+ it('should return plus for volume >= 2000', () => {
73
+ assert.equal(getTierFromVolume(2000), 'plus');
74
+ assert.equal(getTierFromVolume(5000), 'plus');
75
+ assert.equal(getTierFromVolume(9999), 'plus');
76
+ });
77
+ it('should return pro for volume >= 10000', () => {
78
+ assert.equal(getTierFromVolume(10000), 'pro');
79
+ assert.equal(getTierFromVolume(15000), 'pro');
80
+ assert.equal(getTierFromVolume(24999), 'pro');
81
+ });
82
+ it('should return platinum for volume >= 25000', () => {
83
+ assert.equal(getTierFromVolume(25000), 'platinum');
84
+ assert.equal(getTierFromVolume(50000), 'platinum');
85
+ assert.equal(getTierFromVolume(1000000), 'platinum');
86
+ });
87
+ it('should never return president (manually assigned only)', () => {
88
+ assert.notEqual(getTierFromVolume(1_000_000_000), tiers.PRESIDENT);
89
+ });
90
+ });
91
+ describe('getEffectiveCap', () => {
92
+ it('should calculate effective cap with multiplier', () => {
93
+ // Peasant ($100 base) with Revolut (5x) = $500
94
+ assert.equal(getEffectiveCap({ tier: 'peasant', providerKey: 'revolut' }), 500);
95
+ // Neutral ($250 base) with Zelle (1.5x) = $375
96
+ assert.equal(getEffectiveCap({ tier: 'neutral', providerKey: 'zelle' }), 375);
97
+ // Plus ($1000 base) with Venmo (1x) = $1000
98
+ assert.equal(getEffectiveCap({ tier: 'plus', providerKey: 'venmo' }), 1000);
99
+ });
100
+ it('should calculate effective cap for low-risk platforms (5x)', () => {
101
+ // Peasant ($100 base) with Monzo (5x) = $500
102
+ assert.equal(getEffectiveCap({ tier: 'peasant', providerKey: 'monzo' }), 500);
103
+ // Neutral ($250 base) with MercadoPago (5x) = $1250
104
+ assert.equal(getEffectiveCap({ tier: 'neutral', providerKey: 'mercadopago' }), 1250);
105
+ });
106
+ // PayPal tests removed — provider disabled until maker liquidity is established
107
+ it('should return Infinity for president on all platforms (VIP — no cap)', () => {
108
+ assert.equal(getEffectiveCap({ tier: tiers.PRESIDENT, providerKey: 'venmo' }), Infinity);
109
+ assert.equal(getEffectiveCap({ tier: tiers.PRESIDENT, providerKey: 'revolut' }), Infinity);
110
+ });
111
+ });
112
+ describe('getCooldownHours', () => {
113
+ it('should return 0 for low-risk platforms', () => {
114
+ assert.equal(getCooldownHours({ tier: 'peasant', providerKey: 'revolut' }), 0);
115
+ assert.equal(getCooldownHours({ tier: 'neutral', providerKey: 'wise' }), 0);
116
+ assert.equal(getCooldownHours({ tier: 'peasant', providerKey: 'monzo' }), 0);
117
+ assert.equal(getCooldownHours({ tier: 'neutral', providerKey: 'mercadopago' }), 0);
118
+ });
119
+ it('should return tier cooldown for high-risk platforms', () => {
120
+ // Peasant = 12h cooldown
121
+ assert.equal(getCooldownHours({ tier: 'peasant', providerKey: 'zelle' }), 12);
122
+ assert.equal(getCooldownHours({ tier: 'peasant', providerKey: 'venmo' }), 12);
123
+ // Neutral = 6h cooldown
124
+ assert.equal(getCooldownHours({ tier: 'neutral', providerKey: 'zelle' }), 6);
125
+ assert.equal(getCooldownHours({ tier: 'neutral', providerKey: 'cashapp' }), 6);
126
+ });
127
+ it('should return 0 for higher tiers', () => {
128
+ assert.equal(getCooldownHours({ tier: 'plus', providerKey: 'zelle' }), 0);
129
+ assert.equal(getCooldownHours({ tier: 'pro', providerKey: 'venmo' }), 0);
130
+ assert.equal(getCooldownHours({ tier: 'platinum', providerKey: 'monzo' }), 0);
131
+ });
132
+ it('should return 0 for president on all platforms (VIP — no cooldown)', () => {
133
+ assert.equal(getCooldownHours({ tier: tiers.PRESIDENT, providerKey: 'venmo' }), 0);
134
+ assert.equal(getCooldownHours({ tier: tiers.PRESIDENT, providerKey: 'zelle' }), 0);
135
+ assert.equal(getCooldownHours({ tier: tiers.PRESIDENT, providerKey: 'monzo' }), 0);
136
+ });
137
+ });
138
+ describe('formatCooldown', () => {
139
+ it('should format 0 hours as "No cooldown"', () => {
140
+ assert.equal(formatCooldown(0), 'No cooldown');
141
+ });
142
+ it('should format hours correctly', () => {
143
+ assert.equal(formatCooldown(6), '6h cooldown');
144
+ assert.equal(formatCooldown(12), '12h cooldown');
145
+ });
146
+ });
147
+ describe('formatCap', () => {
148
+ it('should format null as "Locked"', () => {
149
+ assert.equal(formatCap(null), 'Locked');
150
+ });
151
+ it('should format cap with dollar sign', () => {
152
+ assert.equal(formatCap(100), '$100 max');
153
+ assert.equal(formatCap(1000), '$1,000 max');
154
+ assert.equal(formatCap(5000), '$5,000 max');
155
+ });
156
+ });
@@ -1,17 +1,56 @@
1
+ /**
2
+ * @fileoverview Generic sorting utilities for consistent ordering of data.
3
+ *
4
+ * Provides type-safe comparison functions that work with strings, numbers,
5
+ * and bigints, with support for ascending/descending order.
6
+ */
7
+ /**
8
+ * Sort direction for ordering queries and displays.
9
+ * - 'asc': Ascending order (oldest first, lowest first)
10
+ * - 'desc': Descending order (newest first, highest first)
11
+ */
1
12
  export type SortDirection = 'asc' | 'desc';
13
+ /**
14
+ * Types that can be sorted using orderedSort.
15
+ */
2
16
  export type Sortable = string | number | bigint;
3
17
  /**
4
- * Ordered sort
5
- * @param aVal - The first value
6
- * @param bVal - The second value
7
- * @param orderDirection - The order direction
8
- * @returns The sorted value
18
+ * Compare two values for sorting with configurable direction.
19
+ * Handles bigint, string, and number types with appropriate comparison logic.
20
+ *
21
+ * @param aVal - First value to compare
22
+ * @param bVal - Second value to compare
23
+ * @param orderDirection - Sort direction ('asc' or 'desc')
24
+ * @returns Negative if a < b, positive if a > b, zero if equal (adjusted for direction)
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // Sort numbers ascending
29
+ * [3, 1, 2].sort((a, b) => orderedSort(a, b, 'asc'))
30
+ * // [1, 2, 3]
31
+ *
32
+ * // Sort bigints descending
33
+ * [1n, 3n, 2n].sort((a, b) => orderedSort(a, b, 'desc'))
34
+ * // [3n, 2n, 1n]
35
+ * ```
9
36
  */
10
37
  export declare const orderedSort: (aVal: Sortable, bVal: Sortable, orderDirection: SortDirection) => number;
11
38
  /**
12
- * Ordered sort then by deposit ID
13
- * @param param0 - The parameters
14
- * @returns The sorted value
39
+ * Sort by a primary value, then by timestamp and deposit ID as tiebreakers.
40
+ * Ensures consistent ordering when primary sort values are equal.
41
+ *
42
+ * @param params - Sort parameters including primary values and entity metadata
43
+ * @returns Comparison result for Array.sort()
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * deposits.sort((a, b) => orderedSortThenById({
48
+ * aVal: a.amount,
49
+ * bVal: b.amount,
50
+ * orderDirection: 'desc',
51
+ * a, b
52
+ * }))
53
+ * ```
15
54
  */
16
55
  export declare const orderedSortThenById: ({ aVal, bVal, orderDirection, a, b, }: {
17
56
  aVal: Sortable;
@@ -21,7 +60,7 @@ export declare const orderedSortThenById: ({ aVal, bVal, orderDirection, a, b, }
21
60
  depositId: bigint;
22
61
  transaction: {
23
62
  block: {
24
- timestamp: string;
63
+ timestamp: bigint;
25
64
  };
26
65
  };
27
66
  };
@@ -29,7 +68,7 @@ export declare const orderedSortThenById: ({ aVal, bVal, orderDirection, a, b, }
29
68
  depositId: bigint;
30
69
  transaction: {
31
70
  block: {
32
- timestamp: string;
71
+ timestamp: bigint;
33
72
  };
34
73
  };
35
74
  };
package/dist/src/sort.js CHANGED
@@ -1,9 +1,28 @@
1
1
  /**
2
- * Ordered sort
3
- * @param aVal - The first value
4
- * @param bVal - The second value
5
- * @param orderDirection - The order direction
6
- * @returns The sorted value
2
+ * @fileoverview Generic sorting utilities for consistent ordering of data.
3
+ *
4
+ * Provides type-safe comparison functions that work with strings, numbers,
5
+ * and bigints, with support for ascending/descending order.
6
+ */
7
+ /**
8
+ * Compare two values for sorting with configurable direction.
9
+ * Handles bigint, string, and number types with appropriate comparison logic.
10
+ *
11
+ * @param aVal - First value to compare
12
+ * @param bVal - Second value to compare
13
+ * @param orderDirection - Sort direction ('asc' or 'desc')
14
+ * @returns Negative if a < b, positive if a > b, zero if equal (adjusted for direction)
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * // Sort numbers ascending
19
+ * [3, 1, 2].sort((a, b) => orderedSort(a, b, 'asc'))
20
+ * // [1, 2, 3]
21
+ *
22
+ * // Sort bigints descending
23
+ * [1n, 3n, 2n].sort((a, b) => orderedSort(a, b, 'desc'))
24
+ * // [3n, 2n, 1n]
25
+ * ```
7
26
  */
8
27
  export const orderedSort = (aVal, bVal, orderDirection) => {
9
28
  if (typeof aVal === 'bigint' && typeof bVal === 'bigint') {
@@ -24,15 +43,27 @@ export const orderedSort = (aVal, bVal, orderDirection) => {
24
43
  }
25
44
  };
26
45
  /**
27
- * Ordered sort then by deposit ID
28
- * @param param0 - The parameters
29
- * @returns The sorted value
46
+ * Sort by a primary value, then by timestamp and deposit ID as tiebreakers.
47
+ * Ensures consistent ordering when primary sort values are equal.
48
+ *
49
+ * @param params - Sort parameters including primary values and entity metadata
50
+ * @returns Comparison result for Array.sort()
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * deposits.sort((a, b) => orderedSortThenById({
55
+ * aVal: a.amount,
56
+ * bVal: b.amount,
57
+ * orderDirection: 'desc',
58
+ * a, b
59
+ * }))
60
+ * ```
30
61
  */
31
62
  export const orderedSortThenById = ({ aVal, bVal, orderDirection, a, b, }) => {
32
63
  let result = orderedSort(aVal, bVal, orderDirection);
33
64
  if (result !== 0)
34
65
  return result;
35
- result = orderedSort(BigInt(a.transaction.block.timestamp), BigInt(b.transaction.block.timestamp), 'asc');
66
+ result = orderedSort(a.transaction.block.timestamp, b.transaction.block.timestamp, 'asc');
36
67
  if (result !== 0)
37
68
  return result;
38
69
  return orderedSort(a.depositId, b.depositId, 'asc');
@@ -0,0 +1 @@
1
+ export {};