@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.
Files changed (80) hide show
  1. package/dist/buy/browsePanelDerivations.d.ts +212 -0
  2. package/dist/buy/browsePanelDerivations.js +269 -0
  3. package/dist/buy/browsePanelDerivations.test.d.ts +9 -0
  4. package/dist/buy/browsePanelDerivations.test.js +552 -0
  5. package/dist/buy/fillabilityNotice.d.ts +214 -0
  6. package/dist/buy/fillabilityNotice.js +162 -0
  7. package/dist/buy/fillabilityNotice.test.d.ts +8 -0
  8. package/dist/buy/fillabilityNotice.test.js +246 -0
  9. package/dist/buy/formatCurrencyBaseUnits.d.ts +30 -0
  10. package/dist/buy/formatCurrencyBaseUnits.js +34 -0
  11. package/dist/buy/formatCurrencyBaseUnits.test.d.ts +7 -0
  12. package/dist/buy/formatCurrencyBaseUnits.test.js +34 -0
  13. package/dist/buy/fulfillTransactionHash.d.ts +29 -0
  14. package/dist/buy/fulfillTransactionHash.js +27 -0
  15. package/dist/buy/fulfillTransactionHash.test.d.ts +7 -0
  16. package/dist/buy/fulfillTransactionHash.test.js +46 -0
  17. package/dist/buy/index.d.ts +17 -0
  18. package/dist/buy/index.js +17 -0
  19. package/dist/buy/orderCap.d.ts +29 -0
  20. package/dist/buy/orderCap.js +35 -0
  21. package/dist/buy/orderCap.test.d.ts +8 -0
  22. package/dist/buy/orderCap.test.js +35 -0
  23. package/dist/buy/postFeeAmount.d.ts +39 -0
  24. package/dist/buy/postFeeAmount.js +36 -0
  25. package/dist/buy/postFeeAmount.test.d.ts +9 -0
  26. package/dist/buy/postFeeAmount.test.js +82 -0
  27. package/dist/buy/providerSelectionMode.d.ts +5 -0
  28. package/dist/buy/providerSelectionMode.js +4 -0
  29. package/dist/buy/settlementReconciliation.d.ts +48 -0
  30. package/dist/buy/settlementReconciliation.js +65 -0
  31. package/dist/buy/settlementReconciliation.test.d.ts +9 -0
  32. package/dist/buy/settlementReconciliation.test.js +164 -0
  33. package/dist/buy/submitStepDerivations.d.ts +122 -0
  34. package/dist/buy/submitStepDerivations.js +140 -0
  35. package/dist/buy/submitStepDerivations.test.d.ts +1 -0
  36. package/dist/buy/submitStepDerivations.test.js +305 -0
  37. package/dist/buy/verifiableBanks.d.ts +29 -0
  38. package/dist/buy/verifiableBanks.js +28 -0
  39. package/dist/buy/verifiableBanks.test.d.ts +4 -0
  40. package/dist/buy/verifiableBanks.test.js +31 -0
  41. package/dist/consent.js +0 -2
  42. package/dist/consent.storage.test.d.ts +8 -0
  43. package/dist/consent.storage.test.js +111 -0
  44. package/dist/consent.test.js +60 -1
  45. package/dist/contracts.js +3 -1
  46. package/dist/contracts.test.js +17 -1
  47. package/dist/conversionRates.test.js +18 -0
  48. package/dist/currencies.d.ts +6 -0
  49. package/dist/currencies.js +1 -1
  50. package/dist/currencies.test.js +29 -2
  51. package/dist/fees.js +0 -3
  52. package/dist/gating.test.js +16 -1
  53. package/dist/ids.test.js +48 -0
  54. package/dist/payment.d.ts +50 -0
  55. package/dist/payment.js +72 -51
  56. package/dist/payment.lookup.test.d.ts +5 -0
  57. package/dist/payment.lookup.test.js +169 -0
  58. package/dist/payment.providerLists.test.d.ts +12 -0
  59. package/dist/payment.providerLists.test.js +40 -0
  60. package/dist/payment.sendLinks.test.d.ts +9 -0
  61. package/dist/payment.sendLinks.test.js +91 -0
  62. package/dist/peer-types.d.ts +5 -78
  63. package/dist/peer-types.js +5 -31
  64. package/dist/peer-types.test.js +1 -31
  65. package/dist/profit.d.ts +8 -0
  66. package/dist/profit.js +16 -3
  67. package/dist/profit.test.js +12 -1
  68. package/dist/reputation.js +4 -6
  69. package/dist/reputation.test.js +8 -0
  70. package/dist/time.test.js +26 -5
  71. package/dist/tokens.d.ts +2 -2
  72. package/dist/transports.d.ts +53 -2
  73. package/dist/transports.debug.test.d.ts +9 -0
  74. package/dist/transports.debug.test.js +95 -0
  75. package/dist/transports.js +26 -10
  76. package/dist/transports.test.js +5 -1
  77. package/dist/units.d.ts +19 -0
  78. package/dist/units.js +21 -0
  79. package/dist/units.test.js +31 -1
  80. 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 {};