@kasufinance/kasu-sdk 2.4.2 → 2.6.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/LICENSE +21 -0
- package/README.md +201 -253
- package/dist/bundle.cjs.js +5472 -1710
- package/dist/bundle.esm.js +5415 -1713
- package/dist/domain/au-minimum.d.ts +135 -0
- package/dist/domain/au-minimum.js +154 -0
- package/dist/domain/au-minimum.js.map +1 -0
- package/dist/domain/au-minimum.test.js +202 -0
- package/dist/domain/au-minimum.test.js.map +1 -0
- package/dist/domain/deposit-bounds.d.ts +62 -0
- package/dist/domain/deposit-bounds.js +84 -0
- package/dist/domain/deposit-bounds.js.map +1 -0
- package/dist/domain/deposit-bounds.test.d.ts +1 -0
- package/dist/domain/deposit-bounds.test.js +135 -0
- package/dist/domain/deposit-bounds.test.js.map +1 -0
- package/dist/domain/index.d.ts +40 -0
- package/dist/domain/index.js +33 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/loan-contract.d.ts +174 -0
- package/dist/domain/loan-contract.js +160 -0
- package/dist/domain/loan-contract.js.map +1 -0
- package/dist/domain/loan-contract.test.d.ts +1 -0
- package/dist/domain/loan-contract.test.js +255 -0
- package/dist/domain/loan-contract.test.js.map +1 -0
- package/dist/domain/partners.d.ts +34 -0
- package/dist/domain/partners.js +43 -0
- package/dist/domain/partners.js.map +1 -0
- package/dist/domain/partners.test.d.ts +1 -0
- package/dist/domain/partners.test.js +53 -0
- package/dist/domain/partners.test.js.map +1 -0
- package/dist/domain/pools.d.ts +70 -0
- package/dist/domain/pools.js +134 -0
- package/dist/domain/pools.js.map +1 -0
- package/dist/domain/pools.test.d.ts +1 -0
- package/dist/domain/pools.test.js +184 -0
- package/dist/domain/pools.test.js.map +1 -0
- package/dist/domain/rates.d.ts +83 -0
- package/dist/domain/rates.js +120 -0
- package/dist/domain/rates.js.map +1 -0
- package/dist/domain/rates.test.d.ts +1 -0
- package/dist/domain/rates.test.js +181 -0
- package/dist/domain/rates.test.js.map +1 -0
- package/dist/domain/requests.d.ts +181 -0
- package/dist/domain/requests.js +202 -0
- package/dist/domain/requests.js.map +1 -0
- package/dist/domain/requests.test.d.ts +1 -0
- package/dist/domain/requests.test.js +470 -0
- package/dist/domain/requests.test.js.map +1 -0
- package/dist/domain/settlement.d.ts +97 -0
- package/dist/domain/settlement.js +117 -0
- package/dist/domain/settlement.js.map +1 -0
- package/dist/domain/settlement.test.d.ts +1 -0
- package/dist/domain/settlement.test.js +152 -0
- package/dist/domain/settlement.test.js.map +1 -0
- package/dist/domain/tranche-display-name.d.ts +17 -0
- package/dist/domain/tranche-display-name.js +30 -0
- package/dist/domain/tranche-display-name.js.map +1 -0
- package/dist/domain/tranche-display-name.test.d.ts +1 -0
- package/dist/domain/tranche-display-name.test.js +58 -0
- package/dist/domain/tranche-display-name.test.js.map +1 -0
- package/dist/domain/tranches.d.ts +89 -0
- package/dist/domain/tranches.js +150 -0
- package/dist/domain/tranches.js.map +1 -0
- package/dist/domain/tranches.test.d.ts +1 -0
- package/dist/domain/tranches.test.js +206 -0
- package/dist/domain/tranches.test.js.map +1 -0
- package/dist/domain/wallet-errors.d.ts +37 -0
- package/dist/domain/wallet-errors.js +56 -0
- package/dist/domain/wallet-errors.js.map +1 -0
- package/dist/domain/wallet-errors.test.d.ts +1 -0
- package/dist/domain/wallet-errors.test.js +71 -0
- package/dist/domain/wallet-errors.test.js.map +1 -0
- package/dist/facade/chain-configs.js +61 -1
- package/dist/facade/chain-configs.js.map +1 -1
- package/dist/facade/config.test.d.ts +1 -0
- package/dist/facade/config.test.js +216 -0
- package/dist/facade/config.test.js.map +1 -0
- package/dist/facade/deposits.d.ts +22 -1
- package/dist/facade/deposits.js +24 -1
- package/dist/facade/deposits.js.map +1 -1
- package/dist/facade/facade.test.d.ts +1 -0
- package/dist/{tests → facade}/facade.test.js +87 -5
- package/dist/facade/facade.test.js.map +1 -0
- package/dist/facade/index.d.ts +2 -1
- package/dist/facade/index.js +2 -0
- package/dist/facade/index.js.map +1 -1
- package/dist/facade/kasu.d.ts +49 -8
- package/dist/facade/kasu.js +117 -13
- package/dist/facade/kasu.js.map +1 -1
- package/dist/facade/strategies.d.ts +25 -0
- package/dist/facade/strategies.js +36 -0
- package/dist/facade/strategies.js.map +1 -1
- package/dist/facade/types.d.ts +57 -2
- package/dist/facade/unused-pool-ids.d.ts +31 -0
- package/dist/facade/unused-pool-ids.js +57 -0
- package/dist/facade/unused-pool-ids.js.map +1 -0
- package/dist/facade/user-portfolio.d.ts +18 -0
- package/dist/facade/user-portfolio.js +23 -0
- package/dist/facade/user-portfolio.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/sdk-config.d.ts +11 -0
- package/dist/sdk-config.js +8 -1
- package/dist/sdk-config.js.map +1 -1
- package/dist/services/DataService/data-service.d.ts +5 -0
- package/dist/services/DataService/data-service.js +10 -12
- package/dist/services/DataService/data-service.js.map +1 -1
- package/dist/services/DataService/directus-client.d.ts +26 -0
- package/dist/services/DataService/directus-client.js +38 -0
- package/dist/services/DataService/directus-client.js.map +1 -0
- package/dist/services/Locking/calculate-apy.test.d.ts +1 -0
- package/dist/services/Locking/calculate-apy.test.js +41 -0
- package/dist/services/Locking/calculate-apy.test.js.map +1 -0
- package/dist/services/Locking/locking.d.ts +14 -0
- package/dist/services/Locking/locking.js +16 -2
- package/dist/services/Locking/locking.js.map +1 -1
- package/dist/services/UserLending/user-lending.js +11 -7
- package/dist/services/UserLending/user-lending.js.map +1 -1
- package/dist/tests/sample.test.js +46 -25
- package/dist/tests/sample.test.js.map +1 -1
- package/package.json +43 -12
- package/src/domain/au-minimum.test.ts +371 -0
- package/src/domain/au-minimum.ts +192 -0
- package/src/domain/deposit-bounds.test.ts +200 -0
- package/src/domain/deposit-bounds.ts +118 -0
- package/src/domain/index.ts +134 -0
- package/src/domain/loan-contract.test.ts +343 -0
- package/src/domain/loan-contract.ts +275 -0
- package/src/domain/partners.test.ts +83 -0
- package/src/domain/partners.ts +66 -0
- package/src/domain/pools.test.ts +260 -0
- package/src/domain/pools.ts +150 -0
- package/src/domain/rates.test.ts +254 -0
- package/src/domain/rates.ts +120 -0
- package/src/domain/requests.test.ts +653 -0
- package/src/domain/requests.ts +414 -0
- package/src/domain/settlement.test.ts +198 -0
- package/src/domain/settlement.ts +161 -0
- package/src/domain/tranche-display-name.test.ts +96 -0
- package/src/domain/tranche-display-name.ts +34 -0
- package/src/domain/tranches.test.ts +300 -0
- package/src/domain/tranches.ts +177 -0
- package/src/domain/wallet-errors.test.ts +100 -0
- package/src/domain/wallet-errors.ts +56 -0
- package/src/facade/chain-configs.ts +62 -1
- package/src/facade/config.test.ts +265 -0
- package/src/facade/deposits.ts +25 -0
- package/src/{tests → facade}/facade.test.ts +134 -5
- package/src/facade/index.ts +4 -0
- package/src/facade/kasu.ts +155 -12
- package/src/facade/strategies.ts +36 -0
- package/src/facade/types.ts +60 -2
- package/src/facade/unused-pool-ids.ts +59 -0
- package/src/facade/user-portfolio.ts +24 -0
- package/src/index.ts +8 -0
- package/src/sdk-config.ts +19 -1
- package/src/services/DataService/data-service.ts +14 -26
- package/src/services/DataService/directus-client.ts +54 -0
- package/src/services/Locking/calculate-apy.test.ts +52 -0
- package/src/services/Locking/locking.ts +16 -2
- package/src/services/UserLending/user-lending.ts +17 -21
- package/src/tests/sample.test.ts +54 -33
- package/.eslintignore +0 -3
- package/.eslintrc.json +0 -90
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/gitleaks.yml +0 -22
- package/.github/workflows/main.yml +0 -41
- package/.gitleaksignore +0 -19
- package/.prettierignore +0 -3
- package/.prettierrc +0 -28
- package/CLAUDE.md +0 -410
- package/copy-abi.ps1 +0 -15
- package/dist/tests/facade.test.js.map +0 -1
- package/index.ts +0 -2
- package/jest-config.ts +0 -9
- package/jest.config.js +0 -8
- package/rollup.config.js +0 -45
- package/tsconfig.json +0 -27
- /package/dist/{tests/facade.test.d.ts → domain/au-minimum.test.d.ts} +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AU cumulative-lending minimum — the numeric half.
|
|
3
|
+
*
|
|
4
|
+
* RULE: a lender whose verified KYC country is Australia may only lend when
|
|
5
|
+
* their existing deposited position on THIS deployment, plus the amount they
|
|
6
|
+
* are asking for, reaches the deployment's threshold.
|
|
7
|
+
*
|
|
8
|
+
* This is UX PRE-VALIDATION ONLY. kasu-backend enforces the rule
|
|
9
|
+
* authoritatively at contract generation and refuses with HTTP 403
|
|
10
|
+
* `AU_WHOLESALE_MINIMUM_NOT_MET`; a lender who gets past this check is still
|
|
11
|
+
* stopped there. What these helpers exist for is to raise the amount field's
|
|
12
|
+
* minimum so the lender learns the rule while typing rather than at the end.
|
|
13
|
+
*
|
|
14
|
+
* FAIL-OPEN by design: an absent or unknown country is NOT restricted, and an
|
|
15
|
+
* unlisted stable symbol has no threshold. An UNKNOWN existing position is
|
|
16
|
+
* treated as 0 — strict, so the floor is never advertised lower than the
|
|
17
|
+
* backend will accept, and it relaxes once the position loads.
|
|
18
|
+
*
|
|
19
|
+
* Arithmetic is integer bigint in minor units (10^decimals) so no two
|
|
20
|
+
* consumers can disagree at the boundary. Parsing TRUNCATES, never rounds up —
|
|
21
|
+
* a position can never be inflated into passing. `BigInt(...)` calls only, no
|
|
22
|
+
* bigint literals, so consumers on an older target still compile.
|
|
23
|
+
*
|
|
24
|
+
* Lifted from kasu-ui's `features/lending/lib/au-lending-restriction.ts`. The
|
|
25
|
+
* two toast strings that live there are copy and stay in the applications.
|
|
26
|
+
*/
|
|
27
|
+
type Maybe<T> = T | null | undefined;
|
|
28
|
+
type NumLike = string | number | null | undefined;
|
|
29
|
+
/** ISO 3166-1 alpha-3 for Australia — the collapsed KYC country field. */
|
|
30
|
+
export declare const AU_ALPHA3 = "AUS";
|
|
31
|
+
/**
|
|
32
|
+
* Is this the Australian KYC country? Case-insensitive and
|
|
33
|
+
* whitespace-tolerant. Anything that is not a string — including the
|
|
34
|
+
* `undefined` of an unloaded KYC record — is not restricted (fail-open).
|
|
35
|
+
*
|
|
36
|
+
* The alpha-2 `'AU'` deliberately does NOT match: the KYC country reaching
|
|
37
|
+
* this rule is normalised to alpha-3 upstream, and a bare two-letter code
|
|
38
|
+
* here means something else went wrong.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isAustralianKyc(country: Maybe<string>): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Minimum CUMULATIVE position, in WHOLE stable units, keyed on the
|
|
43
|
+
* deployment's stable-asset symbol (a property of the currency, not the
|
|
44
|
+
* chain). Must match kasu-backend's table exactly.
|
|
45
|
+
*/
|
|
46
|
+
export declare const AU_MIN_CUMULATIVE_BY_STABLE: Readonly<Record<string, number>>;
|
|
47
|
+
/**
|
|
48
|
+
* Threshold in whole units, or `undefined` for an unlisted stable — a
|
|
49
|
+
* deployment whose currency has no configured minimum is unrestricted.
|
|
50
|
+
*/
|
|
51
|
+
export declare function auThresholdFor(symbol: Maybe<string>): number | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Is this wallet released from the minimum entirely, whatever its country,
|
|
54
|
+
* stable or position says?
|
|
55
|
+
*
|
|
56
|
+
* `exemptAddresses` is DEPLOYMENT CONFIGURATION the caller supplies, not a
|
|
57
|
+
* constant of this package. kasu-sdk is published publicly; the exempt list is
|
|
58
|
+
* a compliance carve-out naming particular lender wallets, so it does not
|
|
59
|
+
* belong in a public tarball. Read it from wherever the application keeps its
|
|
60
|
+
* deployment configuration, and keep it identical to kasu-backend's
|
|
61
|
+
* `AU_WHOLESALE_EXEMPT_ADDRESSES` — that is where the rule is actually
|
|
62
|
+
* enforced. An address exempt here but not there sees no raised minimum in the
|
|
63
|
+
* form and is refused at the end, which is worse than not exempting it at all.
|
|
64
|
+
*
|
|
65
|
+
* The address matched must be the CONNECTED wallet — the one that owns the KYC
|
|
66
|
+
* record and signs the agreement request. kasu-backend verifies that signature
|
|
67
|
+
* before the gate, so an exemption cannot be claimed by asserting someone
|
|
68
|
+
* else's address; it takes their private key. Never match a "view as" address.
|
|
69
|
+
*
|
|
70
|
+
* Comparison is case-insensitive and whitespace-tolerant. Anything that is not
|
|
71
|
+
* a string is NOT exempt (fail closed), and so is anything when the list is
|
|
72
|
+
* absent.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isAuMinimumExempt(address: Maybe<string>, exemptAddresses: Maybe<readonly string[]>): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Truncating, partial-input-tolerant decimal → minor-unit parse.
|
|
77
|
+
* `'10.'` → `10000000n` · `'1.23456789'` → `1234567n` (truncated at 6dp).
|
|
78
|
+
* `''` / `'abc'` / negative / null → `null`.
|
|
79
|
+
*
|
|
80
|
+
* Tolerant of mid-typing states because it runs on an amount field as the
|
|
81
|
+
* lender types, and truncating because rounding up would let a position that
|
|
82
|
+
* is a fraction short read as passing.
|
|
83
|
+
*/
|
|
84
|
+
export declare function parseMinorUnits(value: NumLike, decimals: number): bigint | null;
|
|
85
|
+
export interface AuMinimumInput {
|
|
86
|
+
/** Collapsed KYC country (alpha-3). Missing/undefined ⇒ not restricted. */
|
|
87
|
+
country: Maybe<string>;
|
|
88
|
+
/**
|
|
89
|
+
* The CONNECTED wallet (never a "view as" override).
|
|
90
|
+
*
|
|
91
|
+
* REQUIRED KEY, nullable value. A caller with no wallet passes `undefined`
|
|
92
|
+
* and gets the un-exempted behaviour; what it may not do is silently omit
|
|
93
|
+
* the key. Optional would let a refactor of the call site drop it with no
|
|
94
|
+
* compile error — and because a form's own minimum validation refuses a
|
|
95
|
+
* submit below the floor, an exempt lender would then be stopped HERE and
|
|
96
|
+
* never reach the backend that would have let them through.
|
|
97
|
+
*/
|
|
98
|
+
address: Maybe<string>;
|
|
99
|
+
/**
|
|
100
|
+
* The deployment's exempt wallets — see `isAuMinimumExempt`. REQUIRED KEY
|
|
101
|
+
* for the same reason `address` is: a caller that has no list passes
|
|
102
|
+
* `undefined` deliberately, and no refactor can drop it by accident.
|
|
103
|
+
*/
|
|
104
|
+
exemptAddresses: Maybe<readonly string[]>;
|
|
105
|
+
/** Deployment stable-asset symbol, e.g. `'USDC'` / `'AUDD'`. */
|
|
106
|
+
stableSymbol: Maybe<string>;
|
|
107
|
+
/** Stable-asset decimals (6 on every current deployment). */
|
|
108
|
+
decimals: number;
|
|
109
|
+
/**
|
|
110
|
+
* Existing position (active + pending) on THIS deployment, whole units.
|
|
111
|
+
* Unparseable/undefined ⇒ treated as 0 (strict: the full threshold applies
|
|
112
|
+
* until the position is known).
|
|
113
|
+
*/
|
|
114
|
+
existingDeposited: NumLike;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The amount, in WHOLE stable units, an Australian lender still needs in order
|
|
118
|
+
* to reach the deployment's cumulative minimum. Apply it as the amount field's
|
|
119
|
+
* floor via `max(trancheMin, auMinimumRemaining(...))`.
|
|
120
|
+
*
|
|
121
|
+
* Returns 0 for everyone the rule does not restrict (fail-open), 0 for an
|
|
122
|
+
* exempt address, and 0 once the lender's existing position already satisfies
|
|
123
|
+
* the threshold.
|
|
124
|
+
*
|
|
125
|
+
* The exemption is tested FIRST and unconditionally: an exempt wallet has no
|
|
126
|
+
* minimum whatever its country, stable or position says. kasu-backend
|
|
127
|
+
* deliberately tests country first and the exemption second — the two orders
|
|
128
|
+
* are not in conflict, both return "no minimum" for the same inputs. There,
|
|
129
|
+
* the exemption sets a flag that drives a compliance log, so it must not fire
|
|
130
|
+
* for a lender the rule never engaged for. Here nothing is logged, so
|
|
131
|
+
* exemption-first is preferred: it makes this function right on its own,
|
|
132
|
+
* whatever country a caller happens to pass.
|
|
133
|
+
*/
|
|
134
|
+
export declare function auMinimumRemaining(input: AuMinimumInput): number;
|
|
135
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AU cumulative-lending minimum — the numeric half.
|
|
3
|
+
*
|
|
4
|
+
* RULE: a lender whose verified KYC country is Australia may only lend when
|
|
5
|
+
* their existing deposited position on THIS deployment, plus the amount they
|
|
6
|
+
* are asking for, reaches the deployment's threshold.
|
|
7
|
+
*
|
|
8
|
+
* This is UX PRE-VALIDATION ONLY. kasu-backend enforces the rule
|
|
9
|
+
* authoritatively at contract generation and refuses with HTTP 403
|
|
10
|
+
* `AU_WHOLESALE_MINIMUM_NOT_MET`; a lender who gets past this check is still
|
|
11
|
+
* stopped there. What these helpers exist for is to raise the amount field's
|
|
12
|
+
* minimum so the lender learns the rule while typing rather than at the end.
|
|
13
|
+
*
|
|
14
|
+
* FAIL-OPEN by design: an absent or unknown country is NOT restricted, and an
|
|
15
|
+
* unlisted stable symbol has no threshold. An UNKNOWN existing position is
|
|
16
|
+
* treated as 0 — strict, so the floor is never advertised lower than the
|
|
17
|
+
* backend will accept, and it relaxes once the position loads.
|
|
18
|
+
*
|
|
19
|
+
* Arithmetic is integer bigint in minor units (10^decimals) so no two
|
|
20
|
+
* consumers can disagree at the boundary. Parsing TRUNCATES, never rounds up —
|
|
21
|
+
* a position can never be inflated into passing. `BigInt(...)` calls only, no
|
|
22
|
+
* bigint literals, so consumers on an older target still compile.
|
|
23
|
+
*
|
|
24
|
+
* Lifted from kasu-ui's `features/lending/lib/au-lending-restriction.ts`. The
|
|
25
|
+
* two toast strings that live there are copy and stay in the applications.
|
|
26
|
+
*/
|
|
27
|
+
const ZERO = BigInt(0);
|
|
28
|
+
/** ISO 3166-1 alpha-3 for Australia — the collapsed KYC country field. */
|
|
29
|
+
export const AU_ALPHA3 = 'AUS';
|
|
30
|
+
/**
|
|
31
|
+
* Is this the Australian KYC country? Case-insensitive and
|
|
32
|
+
* whitespace-tolerant. Anything that is not a string — including the
|
|
33
|
+
* `undefined` of an unloaded KYC record — is not restricted (fail-open).
|
|
34
|
+
*
|
|
35
|
+
* The alpha-2 `'AU'` deliberately does NOT match: the KYC country reaching
|
|
36
|
+
* this rule is normalised to alpha-3 upstream, and a bare two-letter code
|
|
37
|
+
* here means something else went wrong.
|
|
38
|
+
*/
|
|
39
|
+
export function isAustralianKyc(country) {
|
|
40
|
+
if (typeof country !== 'string')
|
|
41
|
+
return false;
|
|
42
|
+
return country.trim().toUpperCase() === AU_ALPHA3;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Minimum CUMULATIVE position, in WHOLE stable units, keyed on the
|
|
46
|
+
* deployment's stable-asset symbol (a property of the currency, not the
|
|
47
|
+
* chain). Must match kasu-backend's table exactly.
|
|
48
|
+
*/
|
|
49
|
+
export const AU_MIN_CUMULATIVE_BY_STABLE = {
|
|
50
|
+
USDC: 360000,
|
|
51
|
+
AUDD: 500000,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Threshold in whole units, or `undefined` for an unlisted stable — a
|
|
55
|
+
* deployment whose currency has no configured minimum is unrestricted.
|
|
56
|
+
*/
|
|
57
|
+
export function auThresholdFor(symbol) {
|
|
58
|
+
if (typeof symbol !== 'string')
|
|
59
|
+
return undefined;
|
|
60
|
+
return AU_MIN_CUMULATIVE_BY_STABLE[symbol.trim().toUpperCase()];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Is this wallet released from the minimum entirely, whatever its country,
|
|
64
|
+
* stable or position says?
|
|
65
|
+
*
|
|
66
|
+
* `exemptAddresses` is DEPLOYMENT CONFIGURATION the caller supplies, not a
|
|
67
|
+
* constant of this package. kasu-sdk is published publicly; the exempt list is
|
|
68
|
+
* a compliance carve-out naming particular lender wallets, so it does not
|
|
69
|
+
* belong in a public tarball. Read it from wherever the application keeps its
|
|
70
|
+
* deployment configuration, and keep it identical to kasu-backend's
|
|
71
|
+
* `AU_WHOLESALE_EXEMPT_ADDRESSES` — that is where the rule is actually
|
|
72
|
+
* enforced. An address exempt here but not there sees no raised minimum in the
|
|
73
|
+
* form and is refused at the end, which is worse than not exempting it at all.
|
|
74
|
+
*
|
|
75
|
+
* The address matched must be the CONNECTED wallet — the one that owns the KYC
|
|
76
|
+
* record and signs the agreement request. kasu-backend verifies that signature
|
|
77
|
+
* before the gate, so an exemption cannot be claimed by asserting someone
|
|
78
|
+
* else's address; it takes their private key. Never match a "view as" address.
|
|
79
|
+
*
|
|
80
|
+
* Comparison is case-insensitive and whitespace-tolerant. Anything that is not
|
|
81
|
+
* a string is NOT exempt (fail closed), and so is anything when the list is
|
|
82
|
+
* absent.
|
|
83
|
+
*/
|
|
84
|
+
export function isAuMinimumExempt(address, exemptAddresses) {
|
|
85
|
+
if (typeof address !== 'string' || !exemptAddresses)
|
|
86
|
+
return false;
|
|
87
|
+
const needle = address.trim().toLowerCase();
|
|
88
|
+
return exemptAddresses.some((a) => a.trim().toLowerCase() === needle);
|
|
89
|
+
}
|
|
90
|
+
const DECIMAL_RE = /^(\d+)?(?:\.(\d*))?$/;
|
|
91
|
+
/**
|
|
92
|
+
* Truncating, partial-input-tolerant decimal → minor-unit parse.
|
|
93
|
+
* `'10.'` → `10000000n` · `'1.23456789'` → `1234567n` (truncated at 6dp).
|
|
94
|
+
* `''` / `'abc'` / negative / null → `null`.
|
|
95
|
+
*
|
|
96
|
+
* Tolerant of mid-typing states because it runs on an amount field as the
|
|
97
|
+
* lender types, and truncating because rounding up would let a position that
|
|
98
|
+
* is a fraction short read as passing.
|
|
99
|
+
*/
|
|
100
|
+
export function parseMinorUnits(value, decimals) {
|
|
101
|
+
if (value === null || value === undefined)
|
|
102
|
+
return null;
|
|
103
|
+
const raw = typeof value === 'number' ? String(value) : value.trim();
|
|
104
|
+
if (!raw)
|
|
105
|
+
return null;
|
|
106
|
+
const match = DECIMAL_RE.exec(raw);
|
|
107
|
+
if (!match || (!match[1] && !match[2]))
|
|
108
|
+
return null;
|
|
109
|
+
// Both capture groups are optional, so either can be absent at runtime.
|
|
110
|
+
// A fractional group that matched EMPTY ('10.') is not absent, and the
|
|
111
|
+
// destructuring defaults leave it alone — only `undefined` takes them.
|
|
112
|
+
const [, whole = '0', fracDigits = ''] = match;
|
|
113
|
+
const frac = fracDigits.slice(0, decimals).padEnd(decimals, '0');
|
|
114
|
+
return BigInt(whole + frac);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The amount, in WHOLE stable units, an Australian lender still needs in order
|
|
118
|
+
* to reach the deployment's cumulative minimum. Apply it as the amount field's
|
|
119
|
+
* floor via `max(trancheMin, auMinimumRemaining(...))`.
|
|
120
|
+
*
|
|
121
|
+
* Returns 0 for everyone the rule does not restrict (fail-open), 0 for an
|
|
122
|
+
* exempt address, and 0 once the lender's existing position already satisfies
|
|
123
|
+
* the threshold.
|
|
124
|
+
*
|
|
125
|
+
* The exemption is tested FIRST and unconditionally: an exempt wallet has no
|
|
126
|
+
* minimum whatever its country, stable or position says. kasu-backend
|
|
127
|
+
* deliberately tests country first and the exemption second — the two orders
|
|
128
|
+
* are not in conflict, both return "no minimum" for the same inputs. There,
|
|
129
|
+
* the exemption sets a flag that drives a compliance log, so it must not fire
|
|
130
|
+
* for a lender the rule never engaged for. Here nothing is logged, so
|
|
131
|
+
* exemption-first is preferred: it makes this function right on its own,
|
|
132
|
+
* whatever country a caller happens to pass.
|
|
133
|
+
*/
|
|
134
|
+
export function auMinimumRemaining(input) {
|
|
135
|
+
var _a, _b;
|
|
136
|
+
if (isAuMinimumExempt(input.address, input.exemptAddresses))
|
|
137
|
+
return 0;
|
|
138
|
+
if (!isAustralianKyc(input.country))
|
|
139
|
+
return 0;
|
|
140
|
+
const threshold = auThresholdFor(input.stableSymbol);
|
|
141
|
+
if (threshold === undefined)
|
|
142
|
+
return 0;
|
|
143
|
+
const { existingDeposited, decimals } = input;
|
|
144
|
+
// A table threshold always parses; `?? ZERO` keeps the rule fail-open
|
|
145
|
+
// rather than asserting, so an unparseable one yields no minimum at all.
|
|
146
|
+
const thresholdMinor = (_a = parseMinorUnits(threshold, decimals)) !== null && _a !== void 0 ? _a : ZERO;
|
|
147
|
+
const existingMinor = (_b = parseMinorUnits(existingDeposited, decimals)) !== null && _b !== void 0 ? _b : ZERO;
|
|
148
|
+
const remaining = thresholdMinor - existingMinor;
|
|
149
|
+
if (remaining <= ZERO)
|
|
150
|
+
return 0;
|
|
151
|
+
const base = BigInt('1' + '0'.repeat(decimals));
|
|
152
|
+
return Number(remaining / base) + Number(remaining % base) / Number(base);
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=au-minimum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"au-minimum.js","sourceRoot":"","sources":["../../src/domain/au-minimum.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { AU_ALPHA3, AU_MIN_CUMULATIVE_BY_STABLE, auMinimumRemaining, auThresholdFor, isAuMinimumExempt, isAustralianKyc, parseMinorUnits, } from './au-minimum';
|
|
2
|
+
/**
|
|
3
|
+
* Ported from kasu-ui
|
|
4
|
+
* `src/features/lending/lib/au-lending-restriction.test.ts`, minus the two
|
|
5
|
+
* cases that pin toast copy — those strings stay in the applications.
|
|
6
|
+
*
|
|
7
|
+
* What these pin is the arithmetic: integer bigint minor units and a
|
|
8
|
+
* truncating parse, so no consumer can disagree with another at the
|
|
9
|
+
* 360,000 / 500,000 boundary.
|
|
10
|
+
*/
|
|
11
|
+
// 6dp on every current deployment (USDC on Base/XDC, AUDD on XDC).
|
|
12
|
+
const DP = 6;
|
|
13
|
+
const n = (v) => BigInt(v);
|
|
14
|
+
describe('isAustralianKyc', () => {
|
|
15
|
+
it('accepts the alpha-3 code in any casing, with surrounding whitespace', () => {
|
|
16
|
+
expect(isAustralianKyc('AUS')).toBe(true);
|
|
17
|
+
expect(isAustralianKyc('aus')).toBe(true);
|
|
18
|
+
expect(isAustralianKyc(' AUS ')).toBe(true);
|
|
19
|
+
expect(isAustralianKyc('Aus')).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it('rejects the alpha-2 code — the collapsed KYC field is alpha-3', () => {
|
|
22
|
+
// Deliberate: a bare 'AU' must NOT restrict. The KYC claim is
|
|
23
|
+
// normalised to alpha-3 before it ever reaches this rule.
|
|
24
|
+
expect(isAustralianKyc('AU')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('rejects near-misses and absent countries (fail-open)', () => {
|
|
27
|
+
expect(isAustralianKyc('AUT')).toBe(false);
|
|
28
|
+
expect(isAustralianKyc('')).toBe(false);
|
|
29
|
+
expect(isAustralianKyc(undefined)).toBe(false);
|
|
30
|
+
expect(isAustralianKyc(null)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
it('exports the alpha-3 constant it compares against', () => {
|
|
33
|
+
expect(AU_ALPHA3).toBe('AUS');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('auThresholdFor', () => {
|
|
37
|
+
it('returns the per-stable whole-unit minimum, case-insensitively', () => {
|
|
38
|
+
expect(auThresholdFor('USDC')).toBe(360000);
|
|
39
|
+
expect(auThresholdFor('usdc')).toBe(360000);
|
|
40
|
+
expect(auThresholdFor(' USDC ')).toBe(360000);
|
|
41
|
+
expect(auThresholdFor('AUDD')).toBe(500000);
|
|
42
|
+
expect(auThresholdFor('audd')).toBe(500000);
|
|
43
|
+
});
|
|
44
|
+
it('fails open for an unlisted stable or a missing symbol', () => {
|
|
45
|
+
expect(auThresholdFor('DAI')).toBeUndefined();
|
|
46
|
+
expect(auThresholdFor(undefined)).toBeUndefined();
|
|
47
|
+
expect(auThresholdFor(null)).toBeUndefined();
|
|
48
|
+
expect(auThresholdFor('')).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
it('exposes the table kasu-backend’s minimums must match', () => {
|
|
51
|
+
expect(AU_MIN_CUMULATIVE_BY_STABLE).toEqual({
|
|
52
|
+
USDC: 360000,
|
|
53
|
+
AUDD: 500000,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('parseMinorUnits (6dp)', () => {
|
|
58
|
+
it('parses whole and partial decimal input', () => {
|
|
59
|
+
expect(parseMinorUnits('0', DP)).toBe(n('0'));
|
|
60
|
+
expect(parseMinorUnits('1', DP)).toBe(n('1000000'));
|
|
61
|
+
// Mid-typing states an amount field really produces.
|
|
62
|
+
expect(parseMinorUnits('10.', DP)).toBe(n('10000000'));
|
|
63
|
+
expect(parseMinorUnits('.5', DP)).toBe(n('500000'));
|
|
64
|
+
expect(parseMinorUnits('1.5', DP)).toBe(n('1500000'));
|
|
65
|
+
});
|
|
66
|
+
it('TRUNCATES excess precision — a position can never be inflated into passing', () => {
|
|
67
|
+
expect(parseMinorUnits('1.23456789', DP)).toBe(n('1234567'));
|
|
68
|
+
});
|
|
69
|
+
it('returns null for empty, non-numeric, negative and absent input', () => {
|
|
70
|
+
expect(parseMinorUnits('', DP)).toBeNull();
|
|
71
|
+
expect(parseMinorUnits(' ', DP)).toBeNull();
|
|
72
|
+
expect(parseMinorUnits('abc', DP)).toBeNull();
|
|
73
|
+
expect(parseMinorUnits('-5', DP)).toBeNull();
|
|
74
|
+
// A lone separator matches the shape but carries no digits.
|
|
75
|
+
expect(parseMinorUnits('.', DP)).toBeNull();
|
|
76
|
+
expect(parseMinorUnits(null, DP)).toBeNull();
|
|
77
|
+
expect(parseMinorUnits(undefined, DP)).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
it('accepts numbers as well as strings', () => {
|
|
80
|
+
expect(parseMinorUnits(360000, DP)).toBe(n('360000000000'));
|
|
81
|
+
expect(parseMinorUnits(0, DP)).toBe(n('0'));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// `address` and `exemptAddresses` are required keys on AuMinimumInput (a
|
|
85
|
+
// refactor must not be able to drop either silently), so the shared fixture
|
|
86
|
+
// supplies the un-exempted defaults and the exemption cases override them.
|
|
87
|
+
const BASE = {
|
|
88
|
+
stableSymbol: 'USDC',
|
|
89
|
+
decimals: DP,
|
|
90
|
+
address: undefined,
|
|
91
|
+
exemptAddresses: undefined,
|
|
92
|
+
};
|
|
93
|
+
describe('auMinimumRemaining — fail-open cases return 0 (no raised minimum)', () => {
|
|
94
|
+
it('is 0 when the KYC country is absent', () => {
|
|
95
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: undefined, existingDeposited: '0' }))).toBe(0);
|
|
96
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: null, existingDeposited: '0' }))).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
it('is 0 for a non-Australian lender', () => {
|
|
99
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'USA', existingDeposited: '0' }))).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
it('is 0 on a deployment whose stable has no configured threshold', () => {
|
|
102
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { stableSymbol: 'DAI', country: 'AUS', existingDeposited: '0' }))).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('auMinimumRemaining — Australian lender', () => {
|
|
106
|
+
it('demands the FULL threshold while the position is unknown (strict-until-loaded)', () => {
|
|
107
|
+
// `undefined` is not a zero position — it is an unread one. Treating
|
|
108
|
+
// it as 0 keeps the floor at its strictest until the read lands, so
|
|
109
|
+
// the field never advertises a minimum lower than the backend accepts.
|
|
110
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: undefined }))).toBe(360000);
|
|
111
|
+
});
|
|
112
|
+
it('is 0 once the existing position exactly meets the threshold (inclusive)', () => {
|
|
113
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '360000' }))).toBe(0);
|
|
114
|
+
});
|
|
115
|
+
it('is 0 once the existing position exceeds the threshold', () => {
|
|
116
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '400000' }))).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
it('returns the remainder for a partial position', () => {
|
|
119
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '120000' }))).toBe(240000);
|
|
120
|
+
});
|
|
121
|
+
it('keeps sub-unit precision — one minor unit short still demands one minor unit', () => {
|
|
122
|
+
// 359,999.999999 lent → 0.000001 USDC left to reach 360,000. The
|
|
123
|
+
// bigint arithmetic must not round that away into a 0 (unrestricted).
|
|
124
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: '359999.999999' }))).toBe(0.000001);
|
|
125
|
+
});
|
|
126
|
+
it('accepts a numeric existing position, not just a string', () => {
|
|
127
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', existingDeposited: 120000 }))).toBe(240000);
|
|
128
|
+
});
|
|
129
|
+
it('uses the 500,000 threshold on AUDD deployments', () => {
|
|
130
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { stableSymbol: 'AUDD', country: 'AUS', existingDeposited: 0 }))).toBe(500000);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// Deployment configuration the caller supplies — see `isAuMinimumExempt`. The
|
|
134
|
+
// real list is never shipped in this public package; these are stand-ins.
|
|
135
|
+
const EXEMPT_CHECKSUMMED = '0x1111111111111111111111111111111111111111';
|
|
136
|
+
const EXEMPT_LOWER = EXEMPT_CHECKSUMMED.toLowerCase();
|
|
137
|
+
const OTHER_EXEMPT = '0x2222222222222222222222222222222222222222';
|
|
138
|
+
const NOT_EXEMPT = '0x3333333333333333333333333333333333333333';
|
|
139
|
+
const LIST = [EXEMPT_LOWER, OTHER_EXEMPT.toLowerCase()];
|
|
140
|
+
describe('isAuMinimumExempt', () => {
|
|
141
|
+
it('matches a listed wallet in any casing, with surrounding whitespace', () => {
|
|
142
|
+
expect(isAuMinimumExempt(EXEMPT_CHECKSUMMED, LIST)).toBe(true);
|
|
143
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, LIST)).toBe(true);
|
|
144
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER.toUpperCase(), LIST)).toBe(true);
|
|
145
|
+
expect(isAuMinimumExempt(` ${OTHER_EXEMPT} `, LIST)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
it('matches a list entry stored in mixed case too', () => {
|
|
148
|
+
// The rule normalises both sides, so a caller that reads its list
|
|
149
|
+
// straight out of configuration cannot be caught out by casing.
|
|
150
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, [EXEMPT_CHECKSUMMED.toUpperCase()])).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
it('fails CLOSED on anything unlisted or not a string', () => {
|
|
153
|
+
expect(isAuMinimumExempt(NOT_EXEMPT, LIST)).toBe(false);
|
|
154
|
+
expect(isAuMinimumExempt('', LIST)).toBe(false);
|
|
155
|
+
expect(isAuMinimumExempt(' ', LIST)).toBe(false);
|
|
156
|
+
expect(isAuMinimumExempt(undefined, LIST)).toBe(false);
|
|
157
|
+
expect(isAuMinimumExempt(null, LIST)).toBe(false);
|
|
158
|
+
// A prefix of, or an extension to, a listed address is another wallet.
|
|
159
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER.slice(0, -1), LIST)).toBe(false);
|
|
160
|
+
expect(isAuMinimumExempt(`${EXEMPT_LOWER}00`, LIST)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
it('fails CLOSED when no list was supplied', () => {
|
|
163
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, undefined)).toBe(false);
|
|
164
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, null)).toBe(false);
|
|
165
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, [])).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('auMinimumRemaining — named-address exemption', () => {
|
|
169
|
+
it('is 0 for an exempt wallet that would otherwise owe the full threshold', () => {
|
|
170
|
+
for (const address of [
|
|
171
|
+
EXEMPT_CHECKSUMMED,
|
|
172
|
+
EXEMPT_LOWER,
|
|
173
|
+
OTHER_EXEMPT,
|
|
174
|
+
]) {
|
|
175
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(0);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
it('is 0 on AUDD too, where the threshold is the larger 500,000', () => {
|
|
179
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { stableSymbol: 'AUDD', country: 'AUS', address: EXEMPT_LOWER, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
it('overrides an UNKNOWN position — the strict full-threshold case', () => {
|
|
182
|
+
// `undefined` is what a caller passes while the on-chain read is in
|
|
183
|
+
// flight, and it normally yields the whole threshold. An exemption
|
|
184
|
+
// outranks it, so an exempt lender never sees a floor that later drops.
|
|
185
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address: EXEMPT_LOWER, exemptAddresses: LIST, existingDeposited: undefined }))).toBe(0);
|
|
186
|
+
});
|
|
187
|
+
it('leaves a non-exempt Australian on the full threshold', () => {
|
|
188
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address: NOT_EXEMPT, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(360000);
|
|
189
|
+
});
|
|
190
|
+
it('treats an absent address, or an absent list, as NOT exempt', () => {
|
|
191
|
+
// Both keys are required; either VALUE may be absent, and when it is
|
|
192
|
+
// the rule applies exactly as it did before exemptions existed.
|
|
193
|
+
for (const address of [undefined, null]) {
|
|
194
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(360000);
|
|
195
|
+
}
|
|
196
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'AUS', address: EXEMPT_LOWER, exemptAddresses: undefined, existingDeposited: 0 }))).toBe(360000);
|
|
197
|
+
});
|
|
198
|
+
it('is 0 for a non-Australian at an exempt address, as it always was', () => {
|
|
199
|
+
expect(auMinimumRemaining(Object.assign(Object.assign({}, BASE), { country: 'FRA', address: EXEMPT_LOWER, exemptAddresses: LIST, existingDeposited: 0 }))).toBe(0);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
//# sourceMappingURL=au-minimum.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"au-minimum.test.js","sourceRoot":"","sources":["../../src/domain/au-minimum.test.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,2BAA2B,EAC3B,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,eAAe,GAClB,MAAM,cAAc,CAAC;AAEtB;;;;;;;;GAQG;AAEH,mEAAmE;AACnE,MAAM,EAAE,GAAG,CAAC,CAAC;AACb,MAAM,CAAC,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAE3C,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,8DAA8D;QAC9D,0DAA0D;QAC1D,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;QAC7C,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;QAC7C,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;QAC/C,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;QAC7C,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC9C,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAClD,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,2BAA2B,CAAC,CAAC,OAAO,CAAC;YACxC,IAAI,EAAE,MAAO;YACb,IAAI,EAAE,MAAO;SAChB,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACpD,qDAAqD;QACrD,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QAClF,MAAM,CAAC,eAAe,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC3C,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC9C,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC9C,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7C,4DAA4D;QAC5D,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7C,MAAM,CAAC,eAAe,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,eAAe,CAAC,MAAO,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,yEAAyE;AACzE,4EAA4E;AAC5E,2EAA2E;AAC3E,MAAM,IAAI,GAAG;IACT,YAAY,EAAE,MAAM;IACpB,QAAQ,EAAE,EAAE;IACZ,OAAO,EAAE,SAAS;IAClB,eAAe,EAAE,SAAS;CACpB,CAAC;AAEX,QAAQ,CAAC,mEAAmE,EAAE,GAAG,EAAE;IAC/E,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC3C,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,SAAS,EAClB,iBAAiB,EAAE,GAAG,IACxB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACV,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,IAAI,EACb,iBAAiB,EAAE,GAAG,IACxB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QACxC,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,GAAG,IACxB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,YAAY,EAAE,KAAK,EACnB,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,GAAG,IACxB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACtF,qEAAqE;QACrE,oEAAoE;QACpE,uEAAuE;QACvE,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,SAAS,IAC9B,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QAC/E,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,QAAQ,IAC7B,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC7D,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,QAAQ,IAC7B,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACpD,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,QAAQ,IAC7B,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACpF,iEAAiE;QACjE,sEAAsE;QACtE,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,eAAe,IACpC,CACL,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAC9D,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,MAAO,IAC5B,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACtD,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,KAAK,EACd,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,0EAA0E;AAC1E,MAAM,kBAAkB,GAAG,4CAA4C,CAAC;AACxE,MAAM,YAAY,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAC;AACtD,MAAM,YAAY,GAAG,4CAA4C,CAAC;AAClE,MAAM,UAAU,GAAG,4CAA4C,CAAC;AAChE,MAAM,IAAI,GAAsB,CAAC,YAAY,EAAE,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC;AAE3E,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,iBAAiB,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,MAAM,CAAC,iBAAiB,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE,MAAM,CAAC,iBAAiB,CAAC,KAAK,YAAY,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACrD,kEAAkE;QAClE,gEAAgE;QAChE,MAAM,CACF,iBAAiB,CAAC,YAAY,EAAE,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAC,CAAC,CACtE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,CAAC,iBAAiB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,CAAC,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,CAAC,iBAAiB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClD,uEAAuE;QACvE,MAAM,CAAC,iBAAiB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,iBAAiB,CAAC,GAAG,YAAY,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/D,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,iBAAiB,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC7E,KAAK,MAAM,OAAO,IAAI;YAClB,kBAAkB;YAClB,YAAY;YACZ,YAAY;SACf,EAAE,CAAC;YACA,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,OAAO,EACP,eAAe,EAAE,IAAI,EACrB,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACd,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACnE,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,IAAI,EACrB,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACtE,oEAAoE;QACpE,mEAAmE;QACnE,wEAAwE;QACxE,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,IAAI,EACrB,iBAAiB,EAAE,SAAS,IAC9B,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC5D,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,UAAU,EACnB,eAAe,EAAE,IAAI,EACrB,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QAClE,qEAAqE;QACrE,gEAAgE;QAChE,KAAK,MAAM,OAAO,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,OAAO,EACP,eAAe,EAAE,IAAI,EACrB,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,SAAS,EAC1B,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QACxE,MAAM,CACF,kBAAkB,iCACX,IAAI,KACP,OAAO,EAAE,KAAK,EACd,OAAO,EAAE,YAAY,EACrB,eAAe,EAAE,IAAI,EACrB,iBAAiB,EAAE,CAAC,IACtB,CACL,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TrancheData } from '../services/DataService/types';
|
|
2
|
+
export declare const MIN_LENDING_AMOUNT_FALLBACK = 500;
|
|
3
|
+
export declare const MAX_LENDING_AMOUNT_FALLBACK = 500000;
|
|
4
|
+
export declare function parseTrancheBound(raw: string | number | null | undefined): number | null;
|
|
5
|
+
export interface DepositBounds {
|
|
6
|
+
minDeposit: number;
|
|
7
|
+
maxDeposit: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Min + max lending amount (in whole stable units) for a tranche. The max is
|
|
11
|
+
* clamped by the tranche's *remaining* capacity: a tranche can fill mid-epoch
|
|
12
|
+
* while `maximumDeposit` stays at its configured value, so without the clamp
|
|
13
|
+
* a form would accept an amount the contract will reject. Mirrors
|
|
14
|
+
* kasu-fe-next's `calculateDepositMinMax` (`trancheMax = min(max, capacity)`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveDepositBounds(tranche: TrancheData | undefined): DepositBounds;
|
|
17
|
+
export interface BoundShortcuts {
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The values a lend form's clickable Min/Max labels write into the amount
|
|
24
|
+
* field. Derived from the validated bounds but snapped to whole cents
|
|
25
|
+
* CONSERVATIVELY — the min rounds UP, the max rounds DOWN — so a shortcut can
|
|
26
|
+
* never fill an amount the form's own `min ≤ amount ≤ max` check rejects, and
|
|
27
|
+
* never carries the SDK's float noise into the field (`targetDrawAmount -
|
|
28
|
+
* pendingDeposits` routinely yields `249999.99999999997`, which would make the
|
|
29
|
+
* label, the field's 2dp display and the signed request label all disagree).
|
|
30
|
+
*
|
|
31
|
+
* `enabled` is false when the snapped range is empty: a tranche at capacity
|
|
32
|
+
* (max clamps to 0 or to sub-cent dust), a remaining capacity below the
|
|
33
|
+
* tranche minimum, or a jurisdiction floor above the capacity. Nothing either
|
|
34
|
+
* shortcut could write would validate, so both render inert.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveBoundShortcuts(minDeposit: number, maxDeposit: number): BoundShortcuts;
|
|
37
|
+
/**
|
|
38
|
+
* True when no amount exists that satisfies both bounds — the minimum sits
|
|
39
|
+
* ABOVE the maximum, so the range a form would otherwise print ("Min 360,000 ·
|
|
40
|
+
* Max 25,285") is a contradiction, not an instruction. Happens when a
|
|
41
|
+
* tranche's remaining capacity falls under its own configured minimum, when it
|
|
42
|
+
* is at capacity (max 0 / sub-cent dust), or when the AU wholesale cumulative
|
|
43
|
+
* floor lands above the capacity that is left.
|
|
44
|
+
*
|
|
45
|
+
* Defined as the negation of `resolveBoundShortcuts().enabled` on purpose: the
|
|
46
|
+
* "there is nothing to click" and "there is nothing to type" states must never
|
|
47
|
+
* be able to disagree.
|
|
48
|
+
*
|
|
49
|
+
* This is UX only. The binding minimum is enforced by kasu-backend (403
|
|
50
|
+
* `AU_WHOLESALE_MINIMUM_NOT_MET`) and by the contract's own bounds; nothing
|
|
51
|
+
* here may be used to relax either.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isBelowMinimumCapacity(minDeposit: number, maxDeposit: number): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Largest whole-cent value ≤ `n`. Float multiplication can round `n * 100` UP
|
|
56
|
+
* onto an integer (`1.15 * 100 === 114.99999999999999` is the usual direction,
|
|
57
|
+
* but the other happens too), so the result is re-checked against `n` and
|
|
58
|
+
* stepped down a cent if it overshot — the contract is "never above `n`".
|
|
59
|
+
*/
|
|
60
|
+
export declare function floorToCents(n: number): number;
|
|
61
|
+
/** Smallest whole-cent value ≥ `n`. Mirror of `floorToCents`: never below `n`. */
|
|
62
|
+
export declare function ceilToCents(n: number): number;
|