@provex/utils 1.11.0 → 2.0.0
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/buy/browsePanelDerivations.d.ts +212 -0
- package/dist/buy/browsePanelDerivations.js +269 -0
- package/dist/buy/browsePanelDerivations.test.d.ts +9 -0
- package/dist/buy/browsePanelDerivations.test.js +552 -0
- package/dist/buy/fillabilityNotice.d.ts +214 -0
- package/dist/buy/fillabilityNotice.js +162 -0
- package/dist/buy/fillabilityNotice.test.d.ts +8 -0
- package/dist/buy/fillabilityNotice.test.js +246 -0
- package/dist/buy/formatCurrencyBaseUnits.d.ts +30 -0
- package/dist/buy/formatCurrencyBaseUnits.js +34 -0
- package/dist/buy/formatCurrencyBaseUnits.test.d.ts +7 -0
- package/dist/buy/formatCurrencyBaseUnits.test.js +34 -0
- package/dist/buy/fulfillTransactionHash.d.ts +29 -0
- package/dist/buy/fulfillTransactionHash.js +27 -0
- package/dist/buy/fulfillTransactionHash.test.d.ts +7 -0
- package/dist/buy/fulfillTransactionHash.test.js +46 -0
- package/dist/buy/index.d.ts +17 -0
- package/dist/buy/index.js +17 -0
- package/dist/buy/orderCap.d.ts +29 -0
- package/dist/buy/orderCap.js +35 -0
- package/dist/buy/orderCap.test.d.ts +8 -0
- package/dist/buy/orderCap.test.js +35 -0
- package/dist/buy/postFeeAmount.d.ts +39 -0
- package/dist/buy/postFeeAmount.js +36 -0
- package/dist/buy/postFeeAmount.test.d.ts +9 -0
- package/dist/buy/postFeeAmount.test.js +82 -0
- package/dist/buy/providerSelectionMode.d.ts +5 -0
- package/dist/buy/providerSelectionMode.js +4 -0
- package/dist/buy/settlementReconciliation.d.ts +48 -0
- package/dist/buy/settlementReconciliation.js +65 -0
- package/dist/buy/settlementReconciliation.test.d.ts +9 -0
- package/dist/buy/settlementReconciliation.test.js +164 -0
- package/dist/buy/submitStepDerivations.d.ts +122 -0
- package/dist/buy/submitStepDerivations.js +140 -0
- package/dist/buy/submitStepDerivations.test.d.ts +1 -0
- package/dist/buy/submitStepDerivations.test.js +305 -0
- package/dist/buy/verifiableBanks.d.ts +29 -0
- package/dist/buy/verifiableBanks.js +28 -0
- package/dist/buy/verifiableBanks.test.d.ts +4 -0
- package/dist/buy/verifiableBanks.test.js +31 -0
- package/dist/consent.js +0 -2
- package/dist/consent.storage.test.d.ts +8 -0
- package/dist/consent.storage.test.js +111 -0
- package/dist/consent.test.js +60 -1
- package/dist/contracts.js +3 -1
- package/dist/contracts.test.js +17 -1
- package/dist/conversionRates.test.js +18 -0
- package/dist/currencies.d.ts +6 -0
- package/dist/currencies.js +1 -1
- package/dist/currencies.test.js +29 -2
- package/dist/fees.js +0 -3
- package/dist/gating.test.js +16 -1
- package/dist/ids.test.js +48 -0
- package/dist/payment.d.ts +50 -0
- package/dist/payment.js +72 -51
- package/dist/payment.lookup.test.d.ts +5 -0
- package/dist/payment.lookup.test.js +169 -0
- package/dist/payment.providerLists.test.d.ts +12 -0
- package/dist/payment.providerLists.test.js +40 -0
- package/dist/payment.sendLinks.test.d.ts +9 -0
- package/dist/payment.sendLinks.test.js +91 -0
- package/dist/peer-types.d.ts +5 -78
- package/dist/peer-types.js +5 -31
- package/dist/peer-types.test.js +1 -31
- package/dist/profit.d.ts +8 -0
- package/dist/profit.js +16 -3
- package/dist/profit.test.js +12 -1
- package/dist/reputation.js +4 -6
- package/dist/reputation.test.js +8 -0
- package/dist/time.test.js +26 -5
- package/dist/tokens.d.ts +2 -2
- package/dist/transports.d.ts +53 -2
- package/dist/transports.debug.test.d.ts +9 -0
- package/dist/transports.debug.test.js +95 -0
- package/dist/transports.js +26 -10
- package/dist/transports.test.js +5 -1
- package/dist/units.d.ts +19 -0
- package/dist/units.js +21 -0
- package/dist/units.test.js +31 -1
- package/package.json +6 -2
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the partial-settlement figures on the success panel.
|
|
3
|
+
*
|
|
4
|
+
* The expected strings were captured from the inline code in `CompletePanel`
|
|
5
|
+
* before it moved here, so each row pins what buyers already saw. A buyer reads
|
|
6
|
+
* these numbers to learn what they received and what went back to the maker,
|
|
7
|
+
* so a rounding change here changes a figure that touches money.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { SUPPORTED_CURRENCIES } from '../currencies.js';
|
|
12
|
+
import { reconcileSettlement } from './settlementReconciliation.js';
|
|
13
|
+
const PULSECHAIN = 369;
|
|
14
|
+
/** Not a chain the protocol runs on, so it has no settlement token. */
|
|
15
|
+
const UNSUPPORTED_CHAIN = 1;
|
|
16
|
+
const USD = SUPPORTED_CURRENCIES.USD.contractId;
|
|
17
|
+
const EUR = SUPPORTED_CURRENCIES.EUR.contractId;
|
|
18
|
+
/** Only `releaseAmount` is read, so the rest of the attestation is left out. */
|
|
19
|
+
const attestationReleasing = (releaseAmount) => ({ typedDataValue: { releaseAmount: releaseAmount.toString() } });
|
|
20
|
+
const intentFor = ({ amount, conversionRate, fiatCurrency, }) => ({ amount, conversionRate, fiatCurrency });
|
|
21
|
+
const hundredUsdc = intentFor({ amount: 100000000n, conversionRate: 1020000000000000000n, fiatCurrency: USD });
|
|
22
|
+
describe('reconcileSettlement: partial settlements', () => {
|
|
23
|
+
const rows = [
|
|
24
|
+
{
|
|
25
|
+
name: 'no fee known: the buyer keeps the whole release',
|
|
26
|
+
release: 60000000n,
|
|
27
|
+
intent: hundredUsdc,
|
|
28
|
+
feeBps: null,
|
|
29
|
+
expected: {
|
|
30
|
+
currencySymbol: '$',
|
|
31
|
+
contractedFiat: '102.00',
|
|
32
|
+
attestedFiat: '61.20',
|
|
33
|
+
netToBuyer: '60.00',
|
|
34
|
+
protocolFee: null,
|
|
35
|
+
returnedToMaker: '40.00',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'zero fee: same as no fee, and no fee row',
|
|
40
|
+
release: 60000000n,
|
|
41
|
+
intent: hundredUsdc,
|
|
42
|
+
feeBps: 0n,
|
|
43
|
+
expected: {
|
|
44
|
+
currencySymbol: '$',
|
|
45
|
+
contractedFiat: '102.00',
|
|
46
|
+
attestedFiat: '61.20',
|
|
47
|
+
netToBuyer: '60.00',
|
|
48
|
+
protocolFee: null,
|
|
49
|
+
returnedToMaker: '40.00',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'two percent fee comes off the released amount, not the locked amount',
|
|
54
|
+
release: 60000000n,
|
|
55
|
+
intent: hundredUsdc,
|
|
56
|
+
feeBps: 200n,
|
|
57
|
+
expected: {
|
|
58
|
+
currencySymbol: '$',
|
|
59
|
+
contractedFiat: '102.00',
|
|
60
|
+
attestedFiat: '61.20',
|
|
61
|
+
netToBuyer: '58.80',
|
|
62
|
+
protocolFee: '1.20',
|
|
63
|
+
returnedToMaker: '40.00',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'euro order with odd base units and a 0.5 percent fee',
|
|
68
|
+
release: 33333333n,
|
|
69
|
+
intent: intentFor({ amount: 77777777n, conversionRate: 920000000000000000n, fiatCurrency: EUR }),
|
|
70
|
+
feeBps: 50n,
|
|
71
|
+
expected: {
|
|
72
|
+
currencySymbol: '€',
|
|
73
|
+
contractedFiat: '71.56',
|
|
74
|
+
attestedFiat: '30.67',
|
|
75
|
+
netToBuyer: '33.16',
|
|
76
|
+
protocolFee: '0.16',
|
|
77
|
+
returnedToMaker: '44.44',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'a fee below one display cent still shows a fee row of 0.00',
|
|
82
|
+
release: 1000000n,
|
|
83
|
+
intent: intentFor({ amount: 2000000n, conversionRate: 1000000000000000000n, fiatCurrency: USD }),
|
|
84
|
+
feeBps: 1n,
|
|
85
|
+
expected: {
|
|
86
|
+
currencySymbol: '$',
|
|
87
|
+
contractedFiat: '2.00',
|
|
88
|
+
attestedFiat: '1.00',
|
|
89
|
+
netToBuyer: '0.99',
|
|
90
|
+
protocolFee: '0.00',
|
|
91
|
+
returnedToMaker: '1.00',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
it(row.name, () => {
|
|
97
|
+
const result = reconcileSettlement({
|
|
98
|
+
attestation: attestationReleasing(row.release),
|
|
99
|
+
intent: row.intent,
|
|
100
|
+
chainId: PULSECHAIN,
|
|
101
|
+
feeBps: row.feeBps,
|
|
102
|
+
});
|
|
103
|
+
assert.deepEqual(result, row.expected);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
describe('reconcileSettlement: nothing to reconcile', () => {
|
|
108
|
+
const partialRelease = attestationReleasing(60000000n);
|
|
109
|
+
it('returns null for a full settlement, so no partial story is told', () => {
|
|
110
|
+
const result = reconcileSettlement({
|
|
111
|
+
attestation: attestationReleasing(100000000n),
|
|
112
|
+
intent: hundredUsdc,
|
|
113
|
+
chainId: PULSECHAIN,
|
|
114
|
+
feeBps: 200n,
|
|
115
|
+
});
|
|
116
|
+
assert.equal(result, null);
|
|
117
|
+
});
|
|
118
|
+
it('returns null when the release exceeds the lock', () => {
|
|
119
|
+
const result = reconcileSettlement({
|
|
120
|
+
attestation: attestationReleasing(120000000n),
|
|
121
|
+
intent: hundredUsdc,
|
|
122
|
+
chainId: PULSECHAIN,
|
|
123
|
+
feeBps: 200n,
|
|
124
|
+
});
|
|
125
|
+
assert.equal(result, null);
|
|
126
|
+
});
|
|
127
|
+
const missingInputs = [
|
|
128
|
+
['no attestation', { attestation: undefined, intent: hundredUsdc, chainId: PULSECHAIN, feeBps: 200n }],
|
|
129
|
+
['no intent', { attestation: partialRelease, intent: undefined, chainId: PULSECHAIN, feeBps: 200n }],
|
|
130
|
+
[
|
|
131
|
+
'an intent of zero amount',
|
|
132
|
+
{
|
|
133
|
+
attestation: partialRelease,
|
|
134
|
+
intent: intentFor({ amount: 0n, conversionRate: 1n, fiatCurrency: USD }),
|
|
135
|
+
chainId: PULSECHAIN,
|
|
136
|
+
feeBps: 200n,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
'an intent of zero rate',
|
|
141
|
+
{
|
|
142
|
+
attestation: partialRelease,
|
|
143
|
+
intent: intentFor({ amount: 5n, conversionRate: 0n, fiatCurrency: USD }),
|
|
144
|
+
chainId: PULSECHAIN,
|
|
145
|
+
feeBps: 200n,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
'a currency this build does not know',
|
|
150
|
+
{
|
|
151
|
+
attestation: partialRelease,
|
|
152
|
+
intent: intentFor({ amount: 100000000n, conversionRate: 10n ** 18n, fiatCurrency: `0x${'12'.repeat(32)}` }),
|
|
153
|
+
chainId: PULSECHAIN,
|
|
154
|
+
feeBps: 200n,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
['a chain with no settlement token', { attestation: partialRelease, intent: hundredUsdc, chainId: UNSUPPORTED_CHAIN, feeBps: 200n }],
|
|
158
|
+
];
|
|
159
|
+
for (const [name, input] of missingInputs) {
|
|
160
|
+
it(`returns null for ${name}`, () => {
|
|
161
|
+
assert.equal(reconcileSettlement(input), null);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure derivations extracted from SubmitStep.tsx.
|
|
3
|
+
*
|
|
4
|
+
* SubmitStep is 1016 lines of state machine + UI. Most of the JSX is
|
|
5
|
+
* conditional on the lifecycle status — surfacing that branching as a
|
|
6
|
+
* named, tested predicate keeps the JSX side readable and lets us pin
|
|
7
|
+
* the status → label / disabled / display mappings without mounting
|
|
8
|
+
* the whole step (which needs wagmi + tx-flight + indexer + toast
|
|
9
|
+
* contexts wired up).
|
|
10
|
+
*
|
|
11
|
+
* Bigint throughout — see the `money-values-stay-bigint-until-display`
|
|
12
|
+
* rule. The only string conversions happen at the formatter boundary
|
|
13
|
+
* (formatUnits / formatTokenDisplay), which is the display layer.
|
|
14
|
+
*/
|
|
15
|
+
import type { V3AttestationResponseObject } from '../peer-types.js';
|
|
16
|
+
/** Where the attestation request and the on-chain submission stand. */
|
|
17
|
+
export type AttestationState = 'idle' | 'requesting' | 'verifying' | 'verified' | 'submitting' | 'confirming' | 'syncing' | 'complete' | 'error';
|
|
18
|
+
/**
|
|
19
|
+
* Discriminated union for attestation + submission lifecycle. Mirrors
|
|
20
|
+
* the local type in SubmitStep so the derivations don't depend on a
|
|
21
|
+
* re-export from the consumer file (avoids a cycle if SubmitStep ever
|
|
22
|
+
* starts importing helpers from here).
|
|
23
|
+
*/
|
|
24
|
+
export type AttestationResult = {
|
|
25
|
+
status: 'idle';
|
|
26
|
+
} | {
|
|
27
|
+
status: 'requesting';
|
|
28
|
+
} | {
|
|
29
|
+
status: 'verifying';
|
|
30
|
+
data: V3AttestationResponseObject;
|
|
31
|
+
} | {
|
|
32
|
+
status: 'verified';
|
|
33
|
+
data: V3AttestationResponseObject;
|
|
34
|
+
} | {
|
|
35
|
+
status: 'submitting';
|
|
36
|
+
data: V3AttestationResponseObject;
|
|
37
|
+
} | {
|
|
38
|
+
status: 'confirming';
|
|
39
|
+
data: V3AttestationResponseObject;
|
|
40
|
+
} | {
|
|
41
|
+
status: 'syncing';
|
|
42
|
+
data: V3AttestationResponseObject;
|
|
43
|
+
} | {
|
|
44
|
+
status: 'complete';
|
|
45
|
+
data: V3AttestationResponseObject;
|
|
46
|
+
} | {
|
|
47
|
+
status: 'error';
|
|
48
|
+
error: string;
|
|
49
|
+
isSubmissionError: boolean;
|
|
50
|
+
data?: V3AttestationResponseObject;
|
|
51
|
+
};
|
|
52
|
+
/** Map the lifecycle result to the simpler AttestationState enum the
|
|
53
|
+
* UI components consume. The mapping is 1:1 but pinning it here lets
|
|
54
|
+
* a future refactor that splits the result type from the UI state
|
|
55
|
+
* type catch the divergence in a test rather than at render time. */
|
|
56
|
+
export declare function attestationStateFromResult(result: AttestationResult): AttestationState;
|
|
57
|
+
/** Pull the attestation payload out of any lifecycle status that has
|
|
58
|
+
* it (including `error` when the failure was post-verification). */
|
|
59
|
+
export declare function attestationFromResult(result: AttestationResult): V3AttestationResponseObject | null;
|
|
60
|
+
/** Returns the error string only when the result is in the error state. */
|
|
61
|
+
export declare function attestationErrorFromResult(result: AttestationResult): string | null;
|
|
62
|
+
/** Returns true when the error originated post-verification (i.e.
|
|
63
|
+
* during the on-chain submission), as opposed to during the
|
|
64
|
+
* attestation request itself. Drives different retry-button copy. */
|
|
65
|
+
export declare function isSubmissionErrorFromResult(result: AttestationResult): boolean;
|
|
66
|
+
/** True when the attestation has been verified and the user is in
|
|
67
|
+
* one of the downstream submission states. Used to gate the
|
|
68
|
+
* receive-first card's "verified" recap row vs the in-progress
|
|
69
|
+
* spinner row. */
|
|
70
|
+
export declare function isVerifiedOrLater(state: AttestationState): boolean;
|
|
71
|
+
/** Inputs that decide whether the Complete Order button is waiting. */
|
|
72
|
+
export interface CompleteButtonProgressInput {
|
|
73
|
+
state: AttestationState;
|
|
74
|
+
/** True once the protocol fee has loaded, so "You'll receive" is final. */
|
|
75
|
+
isFeeReady: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** Loading label for the Complete Order button, or undefined when the
|
|
78
|
+
* button is not waiting on anything and shows its default label.
|
|
79
|
+
*
|
|
80
|
+
* Covers the wait BEFORE the order is actionable (proof requested,
|
|
81
|
+
* payment being verified, fee still loading) as well as the
|
|
82
|
+
* submission phases. Without the first group the button sat muted
|
|
83
|
+
* with no spinner while data loaded, and the only cue that it was
|
|
84
|
+
* ready was its outline changing color. */
|
|
85
|
+
export declare function buttonLoadingTextFromState({ state, isFeeReady, }: CompleteButtonProgressInput): string | undefined;
|
|
86
|
+
/** True while the button is waiting: wagmi reports `isPending`, or
|
|
87
|
+
* the state has a loading label. Deriving it from the label keeps
|
|
88
|
+
* the spinner and its text from ever disagreeing. */
|
|
89
|
+
export declare function isCompleteButtonLoading({ isPending, ...progress }: CompleteButtonProgressInput & {
|
|
90
|
+
isPending: boolean;
|
|
91
|
+
}): boolean;
|
|
92
|
+
/** True when the Complete Onramp button should be disabled.
|
|
93
|
+
* Enabled only in the `verified` state (when there's an attestation
|
|
94
|
+
* ready to submit) and never when already loading. */
|
|
95
|
+
export declare function isCompleteButtonDisabled({ state, isLoading, }: {
|
|
96
|
+
state: AttestationState;
|
|
97
|
+
isLoading: boolean;
|
|
98
|
+
}): boolean;
|
|
99
|
+
/** Build the protocol-fee display string for the receive-first card.
|
|
100
|
+
* Returns null when there's no fee for this provider (normalizedBps
|
|
101
|
+
* is 0n) or when any required input is missing.
|
|
102
|
+
*
|
|
103
|
+
* The two-step trim/pad keeps a fee like `0.0050` from rendering
|
|
104
|
+
* with trailing zeros while still preserving the minimum 2-decimal
|
|
105
|
+
* padding (`0.005` stays `0.005`, `0.50` stays `0.50`).
|
|
106
|
+
*/
|
|
107
|
+
export declare function formatFeeTokenAmount({ intentAmount, tokenDecimals, feeBps, }: {
|
|
108
|
+
intentAmount: bigint | null | undefined;
|
|
109
|
+
tokenDecimals: number | null | undefined;
|
|
110
|
+
feeBps: bigint | null | undefined;
|
|
111
|
+
}): string | null;
|
|
112
|
+
/** Net token amount after fee deduction, formatted to 2 decimals.
|
|
113
|
+
* Falls back to the un-deducted `defaultDisplay` when there's no fee
|
|
114
|
+
* or when any required input is missing — the caller passes the
|
|
115
|
+
* display value already computed by useOrderDisplayValues so the
|
|
116
|
+
* fallback has no extra formatting cost. */
|
|
117
|
+
export declare function formatNetTokenAmount({ intentAmount, tokenDecimals, feeBps, defaultDisplay, }: {
|
|
118
|
+
intentAmount: bigint | null | undefined;
|
|
119
|
+
tokenDecimals: number | null | undefined;
|
|
120
|
+
feeBps: bigint | null | undefined;
|
|
121
|
+
defaultDisplay: string;
|
|
122
|
+
}): string;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure derivations extracted from SubmitStep.tsx.
|
|
3
|
+
*
|
|
4
|
+
* SubmitStep is 1016 lines of state machine + UI. Most of the JSX is
|
|
5
|
+
* conditional on the lifecycle status — surfacing that branching as a
|
|
6
|
+
* named, tested predicate keeps the JSX side readable and lets us pin
|
|
7
|
+
* the status → label / disabled / display mappings without mounting
|
|
8
|
+
* the whole step (which needs wagmi + tx-flight + indexer + toast
|
|
9
|
+
* contexts wired up).
|
|
10
|
+
*
|
|
11
|
+
* Bigint throughout — see the `money-values-stay-bigint-until-display`
|
|
12
|
+
* rule. The only string conversions happen at the formatter boundary
|
|
13
|
+
* (formatUnits / formatTokenDisplay), which is the display layer.
|
|
14
|
+
*/
|
|
15
|
+
import { formatUnits } from '../units.js';
|
|
16
|
+
import { calculateFeeAmount, calculateNetAmount } from '../fees.js';
|
|
17
|
+
/** Map the lifecycle result to the simpler AttestationState enum the
|
|
18
|
+
* UI components consume. The mapping is 1:1 but pinning it here lets
|
|
19
|
+
* a future refactor that splits the result type from the UI state
|
|
20
|
+
* type catch the divergence in a test rather than at render time. */
|
|
21
|
+
export function attestationStateFromResult(result) {
|
|
22
|
+
return result.status;
|
|
23
|
+
}
|
|
24
|
+
/** Pull the attestation payload out of any lifecycle status that has
|
|
25
|
+
* it (including `error` when the failure was post-verification). */
|
|
26
|
+
export function attestationFromResult(result) {
|
|
27
|
+
switch (result.status) {
|
|
28
|
+
case 'verifying':
|
|
29
|
+
case 'verified':
|
|
30
|
+
case 'submitting':
|
|
31
|
+
case 'confirming':
|
|
32
|
+
case 'syncing':
|
|
33
|
+
case 'complete':
|
|
34
|
+
return result.data;
|
|
35
|
+
case 'error':
|
|
36
|
+
return result.data ?? null;
|
|
37
|
+
default:
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Returns the error string only when the result is in the error state. */
|
|
42
|
+
export function attestationErrorFromResult(result) {
|
|
43
|
+
return result.status === 'error' ? result.error : null;
|
|
44
|
+
}
|
|
45
|
+
/** Returns true when the error originated post-verification (i.e.
|
|
46
|
+
* during the on-chain submission), as opposed to during the
|
|
47
|
+
* attestation request itself. Drives different retry-button copy. */
|
|
48
|
+
export function isSubmissionErrorFromResult(result) {
|
|
49
|
+
return result.status === 'error' && result.isSubmissionError;
|
|
50
|
+
}
|
|
51
|
+
/** True when the attestation has been verified and the user is in
|
|
52
|
+
* one of the downstream submission states. Used to gate the
|
|
53
|
+
* receive-first card's "verified" recap row vs the in-progress
|
|
54
|
+
* spinner row. */
|
|
55
|
+
export function isVerifiedOrLater(state) {
|
|
56
|
+
return (state === 'verified' ||
|
|
57
|
+
state === 'submitting' ||
|
|
58
|
+
state === 'confirming' ||
|
|
59
|
+
state === 'syncing' ||
|
|
60
|
+
state === 'complete');
|
|
61
|
+
}
|
|
62
|
+
/** Loading label for the Complete Order button, or undefined when the
|
|
63
|
+
* button is not waiting on anything and shows its default label.
|
|
64
|
+
*
|
|
65
|
+
* Covers the wait BEFORE the order is actionable (proof requested,
|
|
66
|
+
* payment being verified, fee still loading) as well as the
|
|
67
|
+
* submission phases. Without the first group the button sat muted
|
|
68
|
+
* with no spinner while data loaded, and the only cue that it was
|
|
69
|
+
* ready was its outline changing color. */
|
|
70
|
+
export function buttonLoadingTextFromState({ state, isFeeReady, }) {
|
|
71
|
+
switch (state) {
|
|
72
|
+
case 'idle':
|
|
73
|
+
case 'requesting':
|
|
74
|
+
return 'Loading proof...';
|
|
75
|
+
case 'verifying':
|
|
76
|
+
return 'Verifying payment...';
|
|
77
|
+
case 'verified':
|
|
78
|
+
return isFeeReady ? undefined : 'Loading fees...';
|
|
79
|
+
case 'submitting':
|
|
80
|
+
return 'Submitting...';
|
|
81
|
+
case 'confirming':
|
|
82
|
+
return 'Confirming...';
|
|
83
|
+
case 'syncing':
|
|
84
|
+
return 'Syncing...';
|
|
85
|
+
default:
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** True while the button is waiting: wagmi reports `isPending`, or
|
|
90
|
+
* the state has a loading label. Deriving it from the label keeps
|
|
91
|
+
* the spinner and its text from ever disagreeing. */
|
|
92
|
+
export function isCompleteButtonLoading({ isPending, ...progress }) {
|
|
93
|
+
return isPending || buttonLoadingTextFromState(progress) !== undefined;
|
|
94
|
+
}
|
|
95
|
+
/** True when the Complete Onramp button should be disabled.
|
|
96
|
+
* Enabled only in the `verified` state (when there's an attestation
|
|
97
|
+
* ready to submit) and never when already loading. */
|
|
98
|
+
export function isCompleteButtonDisabled({ state, isLoading, }) {
|
|
99
|
+
return state !== 'verified' && !isLoading;
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Fee + net display
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
/** Build the protocol-fee display string for the receive-first card.
|
|
105
|
+
* Returns null when there's no fee for this provider (normalizedBps
|
|
106
|
+
* is 0n) or when any required input is missing.
|
|
107
|
+
*
|
|
108
|
+
* The two-step trim/pad keeps a fee like `0.0050` from rendering
|
|
109
|
+
* with trailing zeros while still preserving the minimum 2-decimal
|
|
110
|
+
* padding (`0.005` stays `0.005`, `0.50` stays `0.50`).
|
|
111
|
+
*/
|
|
112
|
+
export function formatFeeTokenAmount({ intentAmount, tokenDecimals, feeBps, }) {
|
|
113
|
+
if (!intentAmount || tokenDecimals === null || tokenDecimals === undefined)
|
|
114
|
+
return null;
|
|
115
|
+
if (!feeBps || feeBps === 0n)
|
|
116
|
+
return null;
|
|
117
|
+
const fee = calculateFeeAmount({ amount: intentAmount, feeBps });
|
|
118
|
+
const raw = formatUnits(fee, { decimals: tokenDecimals, truncateDecimals: tokenDecimals });
|
|
119
|
+
const [int, frac = ''] = raw.split('.');
|
|
120
|
+
const trimmed = frac.length > 2 ? frac.replace(/0+$/, '') : frac;
|
|
121
|
+
return `${int}.${trimmed.padEnd(2, '0')}`;
|
|
122
|
+
}
|
|
123
|
+
/** Net token amount after fee deduction, formatted to 2 decimals.
|
|
124
|
+
* Falls back to the un-deducted `defaultDisplay` when there's no fee
|
|
125
|
+
* or when any required input is missing — the caller passes the
|
|
126
|
+
* display value already computed by useOrderDisplayValues so the
|
|
127
|
+
* fallback has no extra formatting cost. */
|
|
128
|
+
export function formatNetTokenAmount({ intentAmount, tokenDecimals, feeBps, defaultDisplay, }) {
|
|
129
|
+
if (!intentAmount || tokenDecimals === null || tokenDecimals === undefined) {
|
|
130
|
+
return defaultDisplay;
|
|
131
|
+
}
|
|
132
|
+
if (!feeBps || feeBps === 0n)
|
|
133
|
+
return defaultDisplay;
|
|
134
|
+
const net = calculateNetAmount({ grossAmount: intentAmount, feeBps });
|
|
135
|
+
return formatUnits(net, {
|
|
136
|
+
decimals: tokenDecimals,
|
|
137
|
+
truncateDecimals: 2,
|
|
138
|
+
padDecimals: 2,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|