@provex/utils 1.0.16 → 1.2.4
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/chain.d.ts +446 -0
- package/dist/chain.js +98 -0
- package/dist/chain.test.js +112 -0
- package/dist/contracts.d.ts +206 -0
- package/dist/contracts.js +292 -0
- package/dist/contracts.test.js +243 -0
- package/dist/conversionRates.d.ts +215 -0
- package/dist/{src/conversionRates.js → conversionRates.js} +74 -4
- package/dist/conversionRates.test.js +473 -0
- package/dist/currencies.d.ts +248 -0
- package/dist/{src/currencies.js → currencies.js} +119 -38
- package/dist/currencies.test.js +343 -0
- package/dist/data/countries.json +1099 -0
- package/dist/deposit.d.ts +69 -0
- package/dist/deposit.js +52 -0
- package/dist/depositKeys.d.ts +154 -0
- package/dist/depositKeys.js +104 -0
- package/dist/depositKeys.test.d.ts +1 -0
- package/dist/depositKeys.test.js +231 -0
- package/dist/events.d.ts +41 -0
- package/dist/events.js +92 -0
- package/dist/events.test.d.ts +1 -0
- package/dist/events.test.js +111 -0
- package/dist/fees.d.ts +136 -0
- package/dist/fees.js +178 -0
- package/dist/fees.test.d.ts +1 -0
- package/dist/fees.test.js +165 -0
- package/dist/ids.d.ts +175 -0
- package/dist/ids.js +231 -0
- package/dist/ids.test.d.ts +1 -0
- package/dist/ids.test.js +607 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +45 -0
- package/dist/input.d.ts +26 -0
- package/dist/{src/input.js → input.js} +23 -4
- package/dist/intent.d.ts +50 -0
- package/dist/intent.js +28 -0
- package/dist/intent.test.d.ts +1 -0
- package/dist/intent.test.js +119 -0
- package/dist/json.d.ts +55 -0
- package/dist/json.js +80 -0
- package/dist/participants.d.ts +22 -0
- package/dist/participants.js +28 -0
- package/dist/payment.d.ts +481 -0
- package/dist/payment.js +1149 -0
- package/dist/payment.test.d.ts +1 -0
- package/dist/payment.test.js +272 -0
- package/dist/peer-types.d.ts +327 -0
- package/dist/peer-types.js +169 -0
- package/dist/profit.d.ts +49 -0
- package/dist/profit.js +57 -0
- package/dist/profit.test.d.ts +1 -0
- package/dist/profit.test.js +315 -0
- package/dist/reputation.d.ts +125 -0
- package/dist/reputation.js +188 -0
- package/dist/reputation.test.d.ts +1 -0
- package/dist/reputation.test.js +156 -0
- package/dist/sort.d.ts +75 -0
- package/dist/sort.js +70 -0
- package/dist/sort.test.d.ts +1 -0
- package/dist/sort.test.js +216 -0
- package/dist/stateKey.d.ts +35 -0
- package/dist/stateKey.js +36 -0
- package/dist/stats.d.ts +61 -0
- package/dist/stats.js +53 -0
- package/dist/stats.test.d.ts +1 -0
- package/dist/stats.test.js +104 -0
- package/dist/time.d.ts +104 -0
- package/dist/time.js +162 -0
- package/dist/time.test.d.ts +1 -0
- package/dist/time.test.js +231 -0
- package/dist/tokens.d.ts +15502 -0
- package/dist/{src/tokens.js → tokens.js} +98 -17
- package/dist/tokens.test.d.ts +1 -0
- package/dist/tokens.test.js +193 -0
- package/dist/units.d.ts +200 -0
- package/dist/units.js +279 -0
- package/dist/units.test.d.ts +1 -0
- package/dist/units.test.js +327 -0
- package/package.json +119 -29
- package/dist/scripts/convert-svg.js +0 -17
- package/dist/src/chain.d.ts +0 -14
- package/dist/src/chain.js +0 -18
- package/dist/src/contracts.d.ts +0 -30
- package/dist/src/contracts.js +0 -64
- package/dist/src/conversionRates.d.ts +0 -124
- package/dist/src/currencies.d.ts +0 -144
- package/dist/src/data/countries.json +0 -1099
- package/dist/src/deposit.d.ts +0 -1
- package/dist/src/events.d.ts +0 -3
- package/dist/src/events.js +0 -16
- package/dist/src/ids.d.ts +0 -72
- package/dist/src/ids.js +0 -81
- package/dist/src/index.d.ts +0 -16
- package/dist/src/index.js +0 -16
- package/dist/src/input.d.ts +0 -7
- package/dist/src/intent.d.ts +0 -14
- package/dist/src/json.d.ts +0 -18
- package/dist/src/json.js +0 -44
- package/dist/src/participants.d.ts +0 -7
- package/dist/src/participants.js +0 -13
- package/dist/src/payment.d.ts +0 -164
- package/dist/src/payment.js +0 -439
- package/dist/src/profit.d.ts +0 -28
- package/dist/src/profit.js +0 -34
- package/dist/src/sort.d.ts +0 -36
- package/dist/src/sort.js +0 -39
- package/dist/src/stats.d.ts +0 -7
- package/dist/src/stats.js +0 -8
- package/dist/src/time.d.ts +0 -54
- package/dist/src/time.js +0 -106
- package/dist/src/tokens.d.ts +0 -3675
- package/dist/src/units.d.ts +0 -17
- package/dist/src/units.js +0 -79
- package/dist/src/zkp2p-types.d.ts +0 -48
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/scripts/convert-svg.ts +0 -22
- package/src/chain.ts +0 -27
- package/src/contracts.ts +0 -89
- package/src/conversionRates.ts +0 -356
- package/src/currencies.ts +0 -286
- package/src/data/countries.json +0 -1099
- package/src/deposit.ts +0 -2
- package/src/events.ts +0 -21
- package/src/flags/eur.svg +0 -4
- package/src/flags/usd.svg +0 -25
- package/src/flags.ts +0 -4
- package/src/global.d.ts +0 -9
- package/src/ids.ts +0 -137
- package/src/index.ts +0 -16
- package/src/input.ts +0 -35
- package/src/intent.ts +0 -15
- package/src/json.ts +0 -42
- package/src/participants.ts +0 -14
- package/src/payment.ts +0 -573
- package/src/profit.ts +0 -55
- package/src/sort.ts +0 -49
- package/src/stats.ts +0 -10
- package/src/time.ts +0 -123
- package/src/tokens.ts +0 -159
- package/src/units.ts +0 -120
- package/src/zkp2p-types.ts +0 -55
- package/tsconfig.json +0 -18
- /package/dist/{scripts/convert-svg.d.ts → chain.test.d.ts} +0 -0
- /package/dist/{src/deposit.js → contracts.test.d.ts} +0 -0
- /package/dist/{src/intent.js → conversionRates.test.d.ts} +0 -0
- /package/dist/{src/zkp2p-types.js → currencies.test.d.ts} +0 -0
- /package/dist/{src/flags.d.ts → flags.d.ts} +0 -0
- /package/dist/{src/flags.js → flags.js} +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Peer protocol types for attestation and proof verification.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the type system for the V3 attestation flow, including:
|
|
5
|
+
* - Notarized proofs from the PeerAuth browser extension
|
|
6
|
+
* - Attestation requests/responses from the peer attestation service
|
|
7
|
+
* - EIP-712 typed data structures for on-chain verification
|
|
8
|
+
* - Payment proof encoding for the UnifiedPaymentVerifier contract
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* **V3 Attestation Flow:**
|
|
12
|
+
* 1. Buyer generates zkTLS proof via PeerAuth extension (NotarizedProofData)
|
|
13
|
+
* 2. Proof + intent data sent to attestation service (V3AttestationRequest)
|
|
14
|
+
* 3. Service validates and returns EIP-712 signed attestation (V3AttestationResponse)
|
|
15
|
+
* 4. Attestation encoded as paymentProof bytes (encodeV3PaymentProof)
|
|
16
|
+
* 5. Buyer calls fulfillIntent() with encoded proof
|
|
17
|
+
*
|
|
18
|
+
* **Type Aliases:**
|
|
19
|
+
* For cleaner imports, V3-prefixed types have unprefixed aliases:
|
|
20
|
+
* - AttestationOptions = V3AttestationOptions
|
|
21
|
+
* - AttestationResponse = V3AttestationResponse
|
|
22
|
+
* - IntentData = V3IntentData
|
|
23
|
+
*
|
|
24
|
+
* @see {@link intent} for intent data structures
|
|
25
|
+
* @see {@link payment} for provider definitions
|
|
26
|
+
*/
|
|
27
|
+
import { encodeAbiParameters, decodeAbiParameters, keccak256, encodePacked } from "viem";
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// V3 Payment Proof Encoding
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* ABI parameter definition for the V3 payment proof struct.
|
|
33
|
+
*
|
|
34
|
+
* IMPORTANT: The struct must be wrapped in an outer tuple to match the peer encoding.
|
|
35
|
+
* This causes ABI encoding to add an offset prefix (0x20) at the start.
|
|
36
|
+
*
|
|
37
|
+
* Structure:
|
|
38
|
+
* - Outer tuple wrapper (adds 0x20 offset prefix)
|
|
39
|
+
* - PaymentAttestation tuple (intentHash, releaseAmount, dataHash)
|
|
40
|
+
* - signatures[] array of EIP-712 signatures
|
|
41
|
+
* - encodedPaymentDetails bytes
|
|
42
|
+
* - metadata bytes
|
|
43
|
+
* - attestorAddresses[] array of signer addresses
|
|
44
|
+
*/
|
|
45
|
+
const V3_PAYMENT_PROOF_ABI = [
|
|
46
|
+
{
|
|
47
|
+
type: 'tuple',
|
|
48
|
+
components: [
|
|
49
|
+
{
|
|
50
|
+
type: 'tuple',
|
|
51
|
+
name: 'attestation',
|
|
52
|
+
components: [
|
|
53
|
+
{ name: 'intentHash', type: 'bytes32' },
|
|
54
|
+
{ name: 'releaseAmount', type: 'uint256' },
|
|
55
|
+
{ name: 'dataHash', type: 'bytes32' }
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{ type: 'bytes[]', name: 'signatures' },
|
|
59
|
+
{ type: 'bytes', name: 'encodedPaymentDetails' },
|
|
60
|
+
{ type: 'bytes', name: 'metadata' },
|
|
61
|
+
{ type: 'address[]', name: 'attestorAddresses' }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Encodes a V3 attestation response into the paymentProof bytes format
|
|
67
|
+
* expected by the UnifiedPaymentVerifier contract's fulfillIntent function.
|
|
68
|
+
*
|
|
69
|
+
* The encoded struct contains (wrapped in outer tuple for offset prefix):
|
|
70
|
+
* - PaymentAttestation: { intentHash, releaseAmount, dataHash }
|
|
71
|
+
* - signatures: [signature] (array with the EIP-712 signature)
|
|
72
|
+
* - encodedPaymentDetails: payment details for on-chain verification
|
|
73
|
+
* - metadata: additional metadata bytes
|
|
74
|
+
* - attestorAddresses: [signer] (array with the attestor address)
|
|
75
|
+
*
|
|
76
|
+
* @param attestation - The V3AttestationResponseObject from the attestation service
|
|
77
|
+
* @returns ABI-encoded bytes to pass as paymentProof in fulfillIntent
|
|
78
|
+
*/
|
|
79
|
+
export function encodeV3PaymentProof(attestation) {
|
|
80
|
+
return encodeAbiParameters(V3_PAYMENT_PROOF_ABI, [
|
|
81
|
+
{
|
|
82
|
+
attestation: {
|
|
83
|
+
intentHash: attestation.typedDataValue.intentHash,
|
|
84
|
+
releaseAmount: BigInt(attestation.typedDataValue.releaseAmount),
|
|
85
|
+
dataHash: attestation.typedDataValue.dataHash
|
|
86
|
+
},
|
|
87
|
+
signatures: [attestation.signature],
|
|
88
|
+
encodedPaymentDetails: attestation.encodedPaymentDetails,
|
|
89
|
+
metadata: attestation.metadata,
|
|
90
|
+
attestorAddresses: [attestation.signer]
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Extracts the Reclaim attestor address from the attestation proofInput.
|
|
96
|
+
* This is needed for the verificationData field in fulfillIntent.
|
|
97
|
+
*
|
|
98
|
+
* @param attestation - The V3AttestationResponseObject from the attestation service
|
|
99
|
+
* @returns The Reclaim attestor address as Hex, or null if not found
|
|
100
|
+
*/
|
|
101
|
+
export function extractReclaimAttestorAddress(attestation) {
|
|
102
|
+
try {
|
|
103
|
+
const proofInput = JSON.parse(attestation.proofInput);
|
|
104
|
+
const proofs = JSON.parse(proofInput.proof);
|
|
105
|
+
if (Array.isArray(proofs) && proofs.length > 0) {
|
|
106
|
+
const attestorAddress = proofs[0]?.signatures?.attestorAddress;
|
|
107
|
+
if (attestorAddress && typeof attestorAddress === 'string') {
|
|
108
|
+
return attestorAddress;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extracts the nullifier from a V3 attestation response.
|
|
119
|
+
*
|
|
120
|
+
* The nullifier is NOT directly stored in `encodedPaymentDetails`. Instead, it's derived
|
|
121
|
+
* from `paymentMethod` and `paymentId` using:
|
|
122
|
+
* `keccak256(abi.encodePacked(paymentMethod, paymentId))`
|
|
123
|
+
*
|
|
124
|
+
* The `encodedPaymentDetails` contains a tuple of `(PaymentDetails, IntentSnapshot)`.
|
|
125
|
+
* This function decodes the PaymentDetails struct to extract paymentMethod and paymentId,
|
|
126
|
+
* then derives the nullifier.
|
|
127
|
+
*
|
|
128
|
+
* @param attestation - The V3AttestationResponseObject from the attestation service
|
|
129
|
+
* @returns The derived nullifier as Hex, or null if extraction fails
|
|
130
|
+
*/
|
|
131
|
+
export function extractNullifier(attestation) {
|
|
132
|
+
try {
|
|
133
|
+
// Decode encodedPaymentDetails as (PaymentDetails, IntentSnapshot) tuple
|
|
134
|
+
// PaymentDetails struct: { paymentMethod, payeeId, amount, currency, timestamp, paymentId }
|
|
135
|
+
// IntentSnapshot struct: { paymentMethod, currency, payeeId, conversionRate, timestamp, unknown }
|
|
136
|
+
const decoded = decodeAbiParameters([
|
|
137
|
+
{
|
|
138
|
+
type: 'tuple',
|
|
139
|
+
components: [
|
|
140
|
+
{ type: 'bytes32', name: 'paymentMethod' },
|
|
141
|
+
{ type: 'bytes32', name: 'payeeId' },
|
|
142
|
+
{ type: 'uint256', name: 'amount' },
|
|
143
|
+
{ type: 'bytes32', name: 'currency' },
|
|
144
|
+
{ type: 'uint256', name: 'timestamp' },
|
|
145
|
+
{ type: 'bytes32', name: 'paymentId' },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'tuple',
|
|
150
|
+
components: [
|
|
151
|
+
{ type: 'bytes32', name: 'paymentMethod' },
|
|
152
|
+
{ type: 'bytes32', name: 'currency' },
|
|
153
|
+
{ type: 'bytes32', name: 'payeeId' },
|
|
154
|
+
{ type: 'uint256', name: 'conversionRate' },
|
|
155
|
+
{ type: 'uint256', name: 'timestamp' },
|
|
156
|
+
{ type: 'uint256', name: 'unknown' },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
], attestation.encodedPaymentDetails);
|
|
160
|
+
const paymentDetails = decoded[0];
|
|
161
|
+
// Derive nullifier: keccak256(abi.encodePacked(paymentMethod, paymentId))
|
|
162
|
+
const nullifier = keccak256(encodePacked(['bytes32', 'bytes32'], [paymentDetails.paymentMethod, paymentDetails.paymentId]));
|
|
163
|
+
return nullifier;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error('Failed to extract nullifier:', error);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
package/dist/profit.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Profit calculation utilities for market maker analytics.
|
|
3
|
+
*
|
|
4
|
+
* Calculates the profit/loss on fulfilled intents by comparing the token
|
|
5
|
+
* amount received against the expected amount at a 1:1 fiat:token rate.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* This is most meaningful for USD where $1 ≈ 1 USDC. For other currencies,
|
|
9
|
+
* the calculation shows the mathematical difference assuming 1:1 parity,
|
|
10
|
+
* which may not reflect actual profit in local currency terms.
|
|
11
|
+
*/
|
|
12
|
+
import type { ChainId } from "./chain.js";
|
|
13
|
+
/**
|
|
14
|
+
* Flexible bigint input type that accepts string, bigint, or number.
|
|
15
|
+
*/
|
|
16
|
+
export type BigIntish = string | bigint | number;
|
|
17
|
+
/**
|
|
18
|
+
* Input parameters for profit calculation on a single intent.
|
|
19
|
+
*/
|
|
20
|
+
export interface ProfitCalculationInput {
|
|
21
|
+
/** Token amount received in base units (e.g., 6 decimals for USDC) */
|
|
22
|
+
amount: BigIntish;
|
|
23
|
+
/** Chain ID for token decimal lookup */
|
|
24
|
+
chainId: ChainId;
|
|
25
|
+
/** Conversion rate at which the exchange occurred */
|
|
26
|
+
conversionRate: {
|
|
27
|
+
/** Rate value with 18 decimals (1e18 = 1.0) */
|
|
28
|
+
value: BigIntish;
|
|
29
|
+
/** Currency contract ID (keccak256 hash) */
|
|
30
|
+
currency: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Calculate the profit of an intent fulfilled event
|
|
35
|
+
* @param param0.amount - The fiat amount in currency base units (e.g., cents for USD)
|
|
36
|
+
* @param param0.conversionRate - The conversion rate (tokens per fiat unit, scaled by 10^18)
|
|
37
|
+
* @param param0.chainId - The chain ID to get the default token
|
|
38
|
+
* @returns The profit in token base units, or null if not calculable
|
|
39
|
+
*/
|
|
40
|
+
export declare const calculateProfit: ({ amount: amountInput, conversionRate, chainId, }: ProfitCalculationInput) => bigint | null;
|
|
41
|
+
/**
|
|
42
|
+
* Calculate the profits for a list of inputs
|
|
43
|
+
* @param inputs - The inputs to calculate the profits for a series of intents
|
|
44
|
+
* @returns The profits
|
|
45
|
+
*/
|
|
46
|
+
export declare const calculateProfits: (inputs: ProfitCalculationInput[]) => {
|
|
47
|
+
profit: bigint;
|
|
48
|
+
nullAmounts: number;
|
|
49
|
+
};
|
package/dist/profit.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Profit calculation utilities for market maker analytics.
|
|
3
|
+
*
|
|
4
|
+
* Calculates the profit/loss on fulfilled intents by comparing the token
|
|
5
|
+
* amount received against the expected amount at a 1:1 fiat:token rate.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* This is most meaningful for USD where $1 ≈ 1 USDC. For other currencies,
|
|
9
|
+
* the calculation shows the mathematical difference assuming 1:1 parity,
|
|
10
|
+
* which may not reflect actual profit in local currency terms.
|
|
11
|
+
*/
|
|
12
|
+
import { getCurrencyByContractId } from "./currencies.js";
|
|
13
|
+
import { getDefaultToken } from "./tokens.js";
|
|
14
|
+
/**
|
|
15
|
+
* Calculate the profit of an intent fulfilled event
|
|
16
|
+
* @param param0.amount - The fiat amount in currency base units (e.g., cents for USD)
|
|
17
|
+
* @param param0.conversionRate - The conversion rate (tokens per fiat unit, scaled by 10^18)
|
|
18
|
+
* @param param0.chainId - The chain ID to get the default token
|
|
19
|
+
* @returns The profit in token base units, or null if not calculable
|
|
20
|
+
*/
|
|
21
|
+
export const calculateProfit = ({ amount: amountInput, conversionRate, chainId, }) => {
|
|
22
|
+
const rate = BigInt(conversionRate.value);
|
|
23
|
+
const amount = BigInt(amountInput);
|
|
24
|
+
const currency = getCurrencyByContractId(conversionRate.currency);
|
|
25
|
+
const inputTokenInfo = getDefaultToken(chainId);
|
|
26
|
+
if (!inputTokenInfo || !currency) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
// Calculate expected token amount based on fiat amount and conversion rate
|
|
30
|
+
// valueReceived = (fiat_amount * rate) / 10^18, gives us token amount in token base units
|
|
31
|
+
const valueReceived = amount * rate / 10n ** 18n;
|
|
32
|
+
// Scale the fiat amount to match token decimals for comparison
|
|
33
|
+
// e.g., $100 USD (10000 in 2 decimals) -> 100000000 in 6 decimals for USDC
|
|
34
|
+
// e.g., €100 EUR (10000 in 2 decimals) -> 100000000 in 6 decimals for USDC
|
|
35
|
+
const decimalDiff = inputTokenInfo.decimals - currency.decimals;
|
|
36
|
+
const amountScaled = decimalDiff >= 0
|
|
37
|
+
? amount * 10n ** BigInt(decimalDiff)
|
|
38
|
+
: amount / 10n ** BigInt(-decimalDiff);
|
|
39
|
+
// Profit = what we received - what we expected for 1:1 fiat:token exchange
|
|
40
|
+
// Note: This is most meaningful for USD where $1 ≈ 1 USDC
|
|
41
|
+
// For other currencies, this shows the mathematical difference assuming 1:1
|
|
42
|
+
return valueReceived - amountScaled;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Calculate the profits for a list of inputs
|
|
46
|
+
* @param inputs - The inputs to calculate the profits for a series of intents
|
|
47
|
+
* @returns The profits
|
|
48
|
+
*/
|
|
49
|
+
export const calculateProfits = (inputs) => {
|
|
50
|
+
const profitAmounts = inputs.map(calculateProfit);
|
|
51
|
+
const nullAmounts = profitAmounts.filter((amount) => amount === null).length;
|
|
52
|
+
const profit = profitAmounts.reduce((acc, curr) => !!curr ? acc + curr : acc, 0n);
|
|
53
|
+
return {
|
|
54
|
+
profit,
|
|
55
|
+
nullAmounts,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { base, pulsechain } from 'viem/chains';
|
|
4
|
+
import { calculateProfit, calculateProfits } from './profit.js';
|
|
5
|
+
import { SUPPORTED_CURRENCIES } from './currencies.js';
|
|
6
|
+
describe('profit', () => {
|
|
7
|
+
// Constants for testing
|
|
8
|
+
const USD_CONTRACT_ID = SUPPORTED_CURRENCIES.USD.contractId;
|
|
9
|
+
const EUR_CONTRACT_ID = SUPPORTED_CURRENCIES.EUR.contractId;
|
|
10
|
+
const JPY_CONTRACT_ID = SUPPORTED_CURRENCIES.JPY.contractId;
|
|
11
|
+
/**
|
|
12
|
+
* Rate calculation for USD/USDC:
|
|
13
|
+
* - USD has 2 decimals (amount in cents)
|
|
14
|
+
* - USDC has 6 decimals
|
|
15
|
+
* - Rate is scaled by 10^18
|
|
16
|
+
*
|
|
17
|
+
* For a 1:1 USD/USDC rate:
|
|
18
|
+
* - $100 USD = 10000 cents
|
|
19
|
+
* - Expected: 100 USDC = 100000000 (in 6 decimals)
|
|
20
|
+
* - valueReceived = amount * rate / 10^18
|
|
21
|
+
* - 100000000 = 10000 * rate / 10^18
|
|
22
|
+
* - rate = 100000000 * 10^18 / 10000 = 10^22
|
|
23
|
+
*
|
|
24
|
+
* This accounts for decimal difference (6-2=4 extra zeros)
|
|
25
|
+
*/
|
|
26
|
+
// 1.0 rate that produces 1:1 fiat:token (accounts for 4 decimal difference USD->USDC)
|
|
27
|
+
const RATE_1_0 = 10n ** 22n;
|
|
28
|
+
// 1.05 rate (5% premium)
|
|
29
|
+
const RATE_1_05 = (10n ** 22n * 105n) / 100n;
|
|
30
|
+
// 0.95 rate (5% discount)
|
|
31
|
+
const RATE_0_95 = (10n ** 22n * 95n) / 100n;
|
|
32
|
+
describe('calculateProfit', () => {
|
|
33
|
+
it('should return 0 profit for 1:1 rate with USD', () => {
|
|
34
|
+
// $100 USD (10000 in 2 decimals) at 1.0 rate
|
|
35
|
+
const input = {
|
|
36
|
+
amount: 10000n, // $100 in cents
|
|
37
|
+
chainId: base.id,
|
|
38
|
+
conversionRate: {
|
|
39
|
+
value: RATE_1_0,
|
|
40
|
+
currency: USD_CONTRACT_ID,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const profit = calculateProfit(input);
|
|
44
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
45
|
+
// At 1:1 rate, profit should be 0
|
|
46
|
+
assert.equal(profit, 0n);
|
|
47
|
+
});
|
|
48
|
+
it('should return positive profit for rate above 1.0', () => {
|
|
49
|
+
// $100 USD at 1.05 rate means receiving 105 USDC
|
|
50
|
+
const input = {
|
|
51
|
+
amount: 10000n, // $100 in cents
|
|
52
|
+
chainId: base.id,
|
|
53
|
+
conversionRate: {
|
|
54
|
+
value: RATE_1_05,
|
|
55
|
+
currency: USD_CONTRACT_ID,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const profit = calculateProfit(input);
|
|
59
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
60
|
+
assert.ok(profit > 0n, 'Profit should be positive for rate > 1.0');
|
|
61
|
+
// Expected: received (100 * 1.05 = 105 USDC) - expected (100 USDC) = 5 USDC = 5000000 in 6 decimals
|
|
62
|
+
assert.equal(profit, 5000000n);
|
|
63
|
+
});
|
|
64
|
+
it('should return negative profit (loss) for rate below 1.0', () => {
|
|
65
|
+
// $100 USD at 0.95 rate means receiving 95 USDC
|
|
66
|
+
const input = {
|
|
67
|
+
amount: 10000n, // $100 in cents
|
|
68
|
+
chainId: base.id,
|
|
69
|
+
conversionRate: {
|
|
70
|
+
value: RATE_0_95,
|
|
71
|
+
currency: USD_CONTRACT_ID,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const profit = calculateProfit(input);
|
|
75
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
76
|
+
assert.ok(profit < 0n, 'Profit should be negative for rate < 1.0');
|
|
77
|
+
// Expected: received (100 * 0.95 = 95 USDC) - expected (100 USDC) = -5 USDC = -5000000 in 6 decimals
|
|
78
|
+
assert.equal(profit, -5000000n);
|
|
79
|
+
});
|
|
80
|
+
it('should work with Pulsechain', () => {
|
|
81
|
+
const input = {
|
|
82
|
+
amount: 10000n,
|
|
83
|
+
chainId: pulsechain.id,
|
|
84
|
+
conversionRate: {
|
|
85
|
+
value: RATE_1_0,
|
|
86
|
+
currency: USD_CONTRACT_ID,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const profit = calculateProfit(input);
|
|
90
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
91
|
+
assert.equal(profit, 0n);
|
|
92
|
+
});
|
|
93
|
+
it('should handle EUR currency', () => {
|
|
94
|
+
// EUR also has 2 decimals like USD
|
|
95
|
+
const input = {
|
|
96
|
+
amount: 10000n, // €100 in cents
|
|
97
|
+
chainId: base.id,
|
|
98
|
+
conversionRate: {
|
|
99
|
+
value: RATE_1_0, // Same rate logic for EUR
|
|
100
|
+
currency: EUR_CONTRACT_ID,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const profit = calculateProfit(input);
|
|
104
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
105
|
+
// At 1:1 rate, profit should be 0
|
|
106
|
+
assert.equal(profit, 0n);
|
|
107
|
+
});
|
|
108
|
+
it('should handle JPY (0 decimal currency)', () => {
|
|
109
|
+
// JPY has 0 decimals, USDC has 6
|
|
110
|
+
// For 1:1 rate: ¥15000 -> 15000 USDC
|
|
111
|
+
// Rate = 15000 * 10^6 * 10^18 / 15000 = 10^24 (accounts for 6 decimal difference)
|
|
112
|
+
const JPY_RATE_1_0 = 10n ** 24n;
|
|
113
|
+
const input = {
|
|
114
|
+
amount: 15000n, // ¥15000
|
|
115
|
+
chainId: base.id,
|
|
116
|
+
conversionRate: {
|
|
117
|
+
value: JPY_RATE_1_0,
|
|
118
|
+
currency: JPY_CONTRACT_ID,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
const profit = calculateProfit(input);
|
|
122
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
123
|
+
assert.equal(profit, 0n);
|
|
124
|
+
});
|
|
125
|
+
it('should accept string amount', () => {
|
|
126
|
+
const input = {
|
|
127
|
+
amount: '10000',
|
|
128
|
+
chainId: base.id,
|
|
129
|
+
conversionRate: {
|
|
130
|
+
value: RATE_1_0,
|
|
131
|
+
currency: USD_CONTRACT_ID,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
const profit = calculateProfit(input);
|
|
135
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
136
|
+
});
|
|
137
|
+
it('should accept number amount', () => {
|
|
138
|
+
const input = {
|
|
139
|
+
amount: 10000,
|
|
140
|
+
chainId: base.id,
|
|
141
|
+
conversionRate: {
|
|
142
|
+
value: RATE_1_0,
|
|
143
|
+
currency: USD_CONTRACT_ID,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const profit = calculateProfit(input);
|
|
147
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
148
|
+
});
|
|
149
|
+
it('should accept string rate', () => {
|
|
150
|
+
const input = {
|
|
151
|
+
amount: 10000n,
|
|
152
|
+
chainId: base.id,
|
|
153
|
+
conversionRate: {
|
|
154
|
+
value: RATE_1_0.toString(),
|
|
155
|
+
currency: USD_CONTRACT_ID,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const profit = calculateProfit(input);
|
|
159
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
160
|
+
});
|
|
161
|
+
it('should return null for invalid currency', () => {
|
|
162
|
+
const input = {
|
|
163
|
+
amount: 10000n,
|
|
164
|
+
chainId: base.id,
|
|
165
|
+
conversionRate: {
|
|
166
|
+
value: RATE_1_0,
|
|
167
|
+
currency: '0x' + '0'.repeat(64), // Invalid currency contract ID
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
const profit = calculateProfit(input);
|
|
171
|
+
assert.equal(profit, null);
|
|
172
|
+
});
|
|
173
|
+
it('should return null for invalid chainId', () => {
|
|
174
|
+
const input = {
|
|
175
|
+
amount: 10000n,
|
|
176
|
+
chainId: 99999, // Invalid chain
|
|
177
|
+
conversionRate: {
|
|
178
|
+
value: RATE_1_0,
|
|
179
|
+
currency: USD_CONTRACT_ID,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
const profit = calculateProfit(input);
|
|
183
|
+
assert.equal(profit, null);
|
|
184
|
+
});
|
|
185
|
+
it('should handle large amounts', () => {
|
|
186
|
+
// $1,000,000 USD
|
|
187
|
+
const input = {
|
|
188
|
+
amount: 100000000n, // $1M in cents
|
|
189
|
+
chainId: base.id,
|
|
190
|
+
conversionRate: {
|
|
191
|
+
value: RATE_1_05,
|
|
192
|
+
currency: USD_CONTRACT_ID,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const profit = calculateProfit(input);
|
|
196
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
197
|
+
// 5% profit on $1M = $50,000 = 50000000000 in 6 decimals
|
|
198
|
+
assert.equal(profit, 50000000000n);
|
|
199
|
+
});
|
|
200
|
+
it('should handle small amounts', () => {
|
|
201
|
+
// $0.01 USD (1 cent)
|
|
202
|
+
const input = {
|
|
203
|
+
amount: 1n,
|
|
204
|
+
chainId: base.id,
|
|
205
|
+
conversionRate: {
|
|
206
|
+
value: RATE_1_05,
|
|
207
|
+
currency: USD_CONTRACT_ID,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const profit = calculateProfit(input);
|
|
211
|
+
assert.ok(profit !== null, 'Profit should not be null');
|
|
212
|
+
// Small amount should still calculate correctly
|
|
213
|
+
assert.ok(typeof profit === 'bigint');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('calculateProfits', () => {
|
|
217
|
+
it('should calculate total profit for multiple inputs', () => {
|
|
218
|
+
const inputs = [
|
|
219
|
+
{
|
|
220
|
+
amount: 10000n, // $100
|
|
221
|
+
chainId: base.id,
|
|
222
|
+
conversionRate: { value: RATE_1_05, currency: USD_CONTRACT_ID },
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
amount: 20000n, // $200
|
|
226
|
+
chainId: base.id,
|
|
227
|
+
conversionRate: { value: RATE_1_05, currency: USD_CONTRACT_ID },
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
const result = calculateProfits(inputs);
|
|
231
|
+
assert.equal(result.nullAmounts, 0);
|
|
232
|
+
// $100 * 5% + $200 * 5% = $5 + $10 = $15 = 15000000 in 6 decimals
|
|
233
|
+
assert.equal(result.profit, 15000000n);
|
|
234
|
+
});
|
|
235
|
+
it('should handle mix of positive and negative profits', () => {
|
|
236
|
+
const inputs = [
|
|
237
|
+
{
|
|
238
|
+
amount: 10000n,
|
|
239
|
+
chainId: base.id,
|
|
240
|
+
conversionRate: { value: RATE_1_05, currency: USD_CONTRACT_ID }, // +$5
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
amount: 10000n,
|
|
244
|
+
chainId: base.id,
|
|
245
|
+
conversionRate: { value: RATE_0_95, currency: USD_CONTRACT_ID }, // -$5
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
const result = calculateProfits(inputs);
|
|
249
|
+
assert.equal(result.nullAmounts, 0);
|
|
250
|
+
// +5 + -5 = 0
|
|
251
|
+
assert.equal(result.profit, 0n);
|
|
252
|
+
});
|
|
253
|
+
it('should count null amounts', () => {
|
|
254
|
+
const inputs = [
|
|
255
|
+
{
|
|
256
|
+
amount: 10000n,
|
|
257
|
+
chainId: base.id,
|
|
258
|
+
conversionRate: { value: RATE_1_0, currency: USD_CONTRACT_ID },
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
amount: 10000n,
|
|
262
|
+
chainId: 99999, // Invalid - will return null
|
|
263
|
+
conversionRate: { value: RATE_1_0, currency: USD_CONTRACT_ID },
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
amount: 10000n,
|
|
267
|
+
chainId: base.id,
|
|
268
|
+
conversionRate: { value: RATE_1_0, currency: '0x' + '0'.repeat(64) }, // Invalid currency
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
const result = calculateProfits(inputs);
|
|
272
|
+
assert.equal(result.nullAmounts, 2);
|
|
273
|
+
});
|
|
274
|
+
it('should return 0 profit and 0 nullAmounts for empty array', () => {
|
|
275
|
+
const result = calculateProfits([]);
|
|
276
|
+
assert.equal(result.profit, 0n);
|
|
277
|
+
assert.equal(result.nullAmounts, 0);
|
|
278
|
+
});
|
|
279
|
+
it('should handle all null results', () => {
|
|
280
|
+
const inputs = [
|
|
281
|
+
{
|
|
282
|
+
amount: 10000n,
|
|
283
|
+
chainId: 99999,
|
|
284
|
+
conversionRate: { value: RATE_1_0, currency: USD_CONTRACT_ID },
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
amount: 10000n,
|
|
288
|
+
chainId: 99998,
|
|
289
|
+
conversionRate: { value: RATE_1_0, currency: USD_CONTRACT_ID },
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
const result = calculateProfits(inputs);
|
|
293
|
+
assert.equal(result.nullAmounts, 2);
|
|
294
|
+
assert.equal(result.profit, 0n);
|
|
295
|
+
});
|
|
296
|
+
it('should work with mixed chains', () => {
|
|
297
|
+
const inputs = [
|
|
298
|
+
{
|
|
299
|
+
amount: 10000n,
|
|
300
|
+
chainId: base.id,
|
|
301
|
+
conversionRate: { value: RATE_1_05, currency: USD_CONTRACT_ID },
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
amount: 10000n,
|
|
305
|
+
chainId: pulsechain.id,
|
|
306
|
+
conversionRate: { value: RATE_1_05, currency: USD_CONTRACT_ID },
|
|
307
|
+
},
|
|
308
|
+
];
|
|
309
|
+
const result = calculateProfits(inputs);
|
|
310
|
+
assert.equal(result.nullAmounts, 0);
|
|
311
|
+
// Both should have 5% profit = $5 each = $10 total
|
|
312
|
+
assert.equal(result.profit, 10000000n);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|