@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,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
|
+
});
|
package/dist/src/sort.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param
|
|
8
|
-
* @
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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:
|
|
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:
|
|
71
|
+
timestamp: bigint;
|
|
33
72
|
};
|
|
34
73
|
};
|
|
35
74
|
};
|
package/dist/src/sort.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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(
|
|
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 {};
|