@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
|
@@ -7,32 +7,53 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Live-network spec. It talks to a real subgraph and a real RPC, so it is
|
|
12
|
+
* OPT-IN: it runs only under `LIVE_TESTS=1` (`npm run test:live`) and is
|
|
13
|
+
* skipped by `npm test` and `npm run test:unit`. A network problem must never
|
|
14
|
+
* be able to fail a pull request that did not touch the network.
|
|
15
|
+
*
|
|
16
|
+
* Known state: the stack it points at (`kasu-wip-sepolia` on The Graph Studio,
|
|
17
|
+
* with Base Sepolia addresses) is decommissioned, so `npm run test:live` fails
|
|
18
|
+
* on a missing deployment. That is pre-existing and unrelated to the gate; the
|
|
19
|
+
* spec needs retargeting at a live deployment before it means anything.
|
|
20
|
+
*/
|
|
10
21
|
import * as ethers from 'ethers';
|
|
11
22
|
import { KasuSdk } from '..';
|
|
12
23
|
import { SdkConfig } from '../sdk-config';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
const live = process.env.LIVE_TESTS === '1';
|
|
25
|
+
(live ? describe : describe.skip)('live network — subgraph read', () => {
|
|
26
|
+
it('reads a user APY bonus through the subgraph', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
+
const provider = new ethers.providers.JsonRpcProvider('https://sepolia.base.org');
|
|
28
|
+
// Generated per run. This spec only reads, so the wallet never needs
|
|
29
|
+
// funds or a key that outlives the process — and a public repository
|
|
30
|
+
// must never carry one.
|
|
31
|
+
const wallet = ethers.Wallet.createRandom().connect(provider);
|
|
32
|
+
const config = new SdkConfig({
|
|
33
|
+
subgraphUrl: 'https://api.studio.thegraph.com/query/63245/kasu-wip-sepolia/version/latest',
|
|
34
|
+
contracts: {
|
|
35
|
+
IKSULocking: '0x529A81c11ab6176c5E88670d293BB771800066a2',
|
|
36
|
+
IKSULockBonus: '0xBBfFd5F744156FFc526df12F5e09dC7b208Be740',
|
|
37
|
+
// The allow below is for a public Base Sepolia contract
|
|
38
|
+
// address, not a secret: the generic-api-key rule fires on a
|
|
39
|
+
// "Token" key name sitting next to a long hex literal.
|
|
40
|
+
KSUToken: '0xa0f698Feb9Bc2BeA6E85eb071D5E3F59dc5bC56b', // gitleaks:allow
|
|
41
|
+
UserManager: '0xA788e9223fDd7c9Db91Ef133BbeB73515c174773',
|
|
42
|
+
LendingPoolManager: '0xc074Aaf2565aae18db2c7498ee1387610a809F40',
|
|
43
|
+
KasuAllowList: '0xaE94F9D187c9eA649ADd4966340831F7cc62B69c',
|
|
44
|
+
SystemVariables: '0xB174a3240B23e595e90bC2736A5b8ec674Cba73A',
|
|
45
|
+
UserLoyaltyRewards: '0x259631CE76FD9F65296549Cd81232aa59D53Dd0c',
|
|
46
|
+
KsuPrice: '0x2027192aCFB4810Ad734A212F19d7030F698aCE3',
|
|
47
|
+
ClearingCoordinator: '0x2027192aCFB4810Ad734A212F19d7030F698aCE3',
|
|
48
|
+
KasuNFTs: '0x0000000000000000000000000000000000000000',
|
|
49
|
+
ExternalTVL: '0x0000000000000000000000000000000000000000',
|
|
50
|
+
},
|
|
51
|
+
directusUrl: 'https://kasu-finance.directus.app',
|
|
52
|
+
UNUSED_LENDING_POOL_IDS: [''],
|
|
53
|
+
});
|
|
54
|
+
const sdk = new KasuSdk(config, wallet);
|
|
55
|
+
const bonus = yield sdk.UserLending.getUserApyBonus('0xef38c432682ab49095e00442aaa354e955ac03a5');
|
|
56
|
+
expect(bonus).toBeDefined();
|
|
57
|
+
}));
|
|
58
|
+
});
|
|
38
59
|
//# sourceMappingURL=sample.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sample.test.js","sourceRoot":"","sources":["../../src/tests/sample.test.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"sample.test.js","sourceRoot":"","sources":["../../src/tests/sample.test.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC;AAE5C,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,8BAA8B,EAAE,GAAG,EAAE;IACnE,EAAE,CAAC,6CAA6C,EAAE,GAAS,EAAE;QACzD,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,SAAS,CAAC,eAAe,CACjD,0BAA0B,CAC7B,CAAC;QAEF,qEAAqE;QACrE,qEAAqE;QACrE,wBAAwB;QACxB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE9D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;YACzB,WAAW,EACP,6EAA6E;YACjF,SAAS,EAAE;gBACP,WAAW,EAAE,4CAA4C;gBACzD,aAAa,EAAE,4CAA4C;gBAC3D,wDAAwD;gBACxD,6DAA6D;gBAC7D,uDAAuD;gBACvD,QAAQ,EAAE,4CAA4C,EAAE,iBAAiB;gBACzE,WAAW,EAAE,4CAA4C;gBACzD,kBAAkB,EAAE,4CAA4C;gBAChE,aAAa,EAAE,4CAA4C;gBAC3D,eAAe,EAAE,4CAA4C;gBAC7D,kBAAkB,EACd,4CAA4C;gBAChD,QAAQ,EAAE,4CAA4C;gBACtD,mBAAmB,EACf,4CAA4C;gBAChD,QAAQ,EAAE,4CAA4C;gBACtD,WAAW,EAAE,4CAA4C;aAC5D;YACD,WAAW,EAAE,mCAAmC;YAChD,uBAAuB,EAAE,CAAC,EAAE,CAAC;SAChC,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAExC,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,eAAe,CAC/C,4CAA4C,CAC/C,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAChC,CAAC,CAAA,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kasufinance/kasu-sdk",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"description": "TypeScript SDK for the Kasu Finance protocol — pool data, portfolio analytics, and KYC-gated deposits and withdrawals on Base and XDC",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kasu",
|
|
7
|
+
"defi",
|
|
8
|
+
"rwa",
|
|
9
|
+
"lending",
|
|
10
|
+
"ethers",
|
|
11
|
+
"base",
|
|
12
|
+
"xdc",
|
|
13
|
+
"sdk"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/Kasu-Finance/kasu-sdk#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/Kasu-Finance/kasu-sdk/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Kasu-Finance/kasu-sdk.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Kasu Finance",
|
|
5
25
|
"main": "dist/bundle.cjs.js",
|
|
6
26
|
"module": "dist/bundle.esm.js",
|
|
7
27
|
"types": "dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src",
|
|
31
|
+
"abis",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
8
38
|
"scripts": {
|
|
9
39
|
"test": "jest --config=jest-config.ts",
|
|
40
|
+
"test:unit": "jest --config=jest-config.ts --testPathIgnorePatterns /src/tests/",
|
|
41
|
+
"test:live": "LIVE_TESTS=1 jest --config=jest-config.ts src/tests",
|
|
10
42
|
"build-tc": "typechain --target ethers-v5 --out-dir src/contracts './abis/*.json'",
|
|
11
43
|
"build": "eslint . && tsc --build --force tsconfig.json",
|
|
12
44
|
"prepublishOnly": "npm run build && npm run rollup-build",
|
|
@@ -18,34 +50,33 @@
|
|
|
18
50
|
"registry": "https://registry.npmjs.org/",
|
|
19
51
|
"access": "public"
|
|
20
52
|
},
|
|
21
|
-
"author": "",
|
|
22
|
-
"license": "ISC",
|
|
23
53
|
"devDependencies": {
|
|
54
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
24
55
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
25
56
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
57
|
+
"@typechain/ethers-v5": "^11.1.2",
|
|
26
58
|
"@types/jest": "^29.5.11",
|
|
27
59
|
"@types/node": "^22.15.29",
|
|
28
60
|
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
61
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
62
|
+
"eslint": "^8.56.0",
|
|
29
63
|
"eslint-config-prettier": "^9.1.0",
|
|
30
64
|
"eslint-plugin-import": "^2.29.1",
|
|
65
|
+
"fast-check": "^4.9.0",
|
|
31
66
|
"graphql": "^16.8.1",
|
|
67
|
+
"jest": "^29.7.0",
|
|
32
68
|
"prettier": "^3.2.2",
|
|
33
69
|
"rollup": "^4.14.3",
|
|
34
70
|
"ts-jest": "^29.1.2",
|
|
71
|
+
"ts-node": "^10.9.2",
|
|
72
|
+
"tslib": "^2.6.2",
|
|
35
73
|
"typechain": "^8.3.2",
|
|
36
74
|
"typescript": "^5.8.3"
|
|
37
75
|
},
|
|
38
76
|
"dependencies": {
|
|
39
77
|
"@directus/sdk": "^16.1.0",
|
|
40
|
-
"@rollup/plugin-commonjs": "^25.0.7",
|
|
41
|
-
"@typechain/ethers-v5": "^11.1.2",
|
|
42
|
-
"@typescript-eslint/parser": "^6.0.0",
|
|
43
78
|
"axios": "^1.7.0",
|
|
44
|
-
"eslint": "^8.56.0",
|
|
45
79
|
"ethers": "^5.7.2",
|
|
46
|
-
"graphql-request": "^6.1.0"
|
|
47
|
-
"jest": "^29.7.0",
|
|
48
|
-
"ts-node": "^10.9.2",
|
|
49
|
-
"tslib": "^2.6.2"
|
|
80
|
+
"graphql-request": "^6.1.0"
|
|
50
81
|
}
|
|
51
82
|
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AU_ALPHA3,
|
|
3
|
+
AU_MIN_CUMULATIVE_BY_STABLE,
|
|
4
|
+
auMinimumRemaining,
|
|
5
|
+
auThresholdFor,
|
|
6
|
+
isAuMinimumExempt,
|
|
7
|
+
isAustralianKyc,
|
|
8
|
+
parseMinorUnits,
|
|
9
|
+
} from './au-minimum';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ported from kasu-ui
|
|
13
|
+
* `src/features/lending/lib/au-lending-restriction.test.ts`, minus the two
|
|
14
|
+
* cases that pin toast copy — those strings stay in the applications.
|
|
15
|
+
*
|
|
16
|
+
* What these pin is the arithmetic: integer bigint minor units and a
|
|
17
|
+
* truncating parse, so no consumer can disagree with another at the
|
|
18
|
+
* 360,000 / 500,000 boundary.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// 6dp on every current deployment (USDC on Base/XDC, AUDD on XDC).
|
|
22
|
+
const DP = 6;
|
|
23
|
+
const n = (v: string): bigint => BigInt(v);
|
|
24
|
+
|
|
25
|
+
describe('isAustralianKyc', () => {
|
|
26
|
+
it('accepts the alpha-3 code in any casing, with surrounding whitespace', () => {
|
|
27
|
+
expect(isAustralianKyc('AUS')).toBe(true);
|
|
28
|
+
expect(isAustralianKyc('aus')).toBe(true);
|
|
29
|
+
expect(isAustralianKyc(' AUS ')).toBe(true);
|
|
30
|
+
expect(isAustralianKyc('Aus')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('rejects the alpha-2 code — the collapsed KYC field is alpha-3', () => {
|
|
34
|
+
// Deliberate: a bare 'AU' must NOT restrict. The KYC claim is
|
|
35
|
+
// normalised to alpha-3 before it ever reaches this rule.
|
|
36
|
+
expect(isAustralianKyc('AU')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rejects near-misses and absent countries (fail-open)', () => {
|
|
40
|
+
expect(isAustralianKyc('AUT')).toBe(false);
|
|
41
|
+
expect(isAustralianKyc('')).toBe(false);
|
|
42
|
+
expect(isAustralianKyc(undefined)).toBe(false);
|
|
43
|
+
expect(isAustralianKyc(null)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('exports the alpha-3 constant it compares against', () => {
|
|
47
|
+
expect(AU_ALPHA3).toBe('AUS');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('auThresholdFor', () => {
|
|
52
|
+
it('returns the per-stable whole-unit minimum, case-insensitively', () => {
|
|
53
|
+
expect(auThresholdFor('USDC')).toBe(360_000);
|
|
54
|
+
expect(auThresholdFor('usdc')).toBe(360_000);
|
|
55
|
+
expect(auThresholdFor(' USDC ')).toBe(360_000);
|
|
56
|
+
expect(auThresholdFor('AUDD')).toBe(500_000);
|
|
57
|
+
expect(auThresholdFor('audd')).toBe(500_000);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('fails open for an unlisted stable or a missing symbol', () => {
|
|
61
|
+
expect(auThresholdFor('DAI')).toBeUndefined();
|
|
62
|
+
expect(auThresholdFor(undefined)).toBeUndefined();
|
|
63
|
+
expect(auThresholdFor(null)).toBeUndefined();
|
|
64
|
+
expect(auThresholdFor('')).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('exposes the table kasu-backend’s minimums must match', () => {
|
|
68
|
+
expect(AU_MIN_CUMULATIVE_BY_STABLE).toEqual({
|
|
69
|
+
USDC: 360_000,
|
|
70
|
+
AUDD: 500_000,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('parseMinorUnits (6dp)', () => {
|
|
76
|
+
it('parses whole and partial decimal input', () => {
|
|
77
|
+
expect(parseMinorUnits('0', DP)).toBe(n('0'));
|
|
78
|
+
expect(parseMinorUnits('1', DP)).toBe(n('1000000'));
|
|
79
|
+
// Mid-typing states an amount field really produces.
|
|
80
|
+
expect(parseMinorUnits('10.', DP)).toBe(n('10000000'));
|
|
81
|
+
expect(parseMinorUnits('.5', DP)).toBe(n('500000'));
|
|
82
|
+
expect(parseMinorUnits('1.5', DP)).toBe(n('1500000'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('TRUNCATES excess precision — a position can never be inflated into passing', () => {
|
|
86
|
+
expect(parseMinorUnits('1.23456789', DP)).toBe(n('1234567'));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns null for empty, non-numeric, negative and absent input', () => {
|
|
90
|
+
expect(parseMinorUnits('', DP)).toBeNull();
|
|
91
|
+
expect(parseMinorUnits(' ', DP)).toBeNull();
|
|
92
|
+
expect(parseMinorUnits('abc', DP)).toBeNull();
|
|
93
|
+
expect(parseMinorUnits('-5', DP)).toBeNull();
|
|
94
|
+
// A lone separator matches the shape but carries no digits.
|
|
95
|
+
expect(parseMinorUnits('.', DP)).toBeNull();
|
|
96
|
+
expect(parseMinorUnits(null, DP)).toBeNull();
|
|
97
|
+
expect(parseMinorUnits(undefined, DP)).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('accepts numbers as well as strings', () => {
|
|
101
|
+
expect(parseMinorUnits(360_000, DP)).toBe(n('360000000000'));
|
|
102
|
+
expect(parseMinorUnits(0, DP)).toBe(n('0'));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// `address` and `exemptAddresses` are required keys on AuMinimumInput (a
|
|
107
|
+
// refactor must not be able to drop either silently), so the shared fixture
|
|
108
|
+
// supplies the un-exempted defaults and the exemption cases override them.
|
|
109
|
+
const BASE = {
|
|
110
|
+
stableSymbol: 'USDC',
|
|
111
|
+
decimals: DP,
|
|
112
|
+
address: undefined,
|
|
113
|
+
exemptAddresses: undefined,
|
|
114
|
+
} as const;
|
|
115
|
+
|
|
116
|
+
describe('auMinimumRemaining — fail-open cases return 0 (no raised minimum)', () => {
|
|
117
|
+
it('is 0 when the KYC country is absent', () => {
|
|
118
|
+
expect(
|
|
119
|
+
auMinimumRemaining({
|
|
120
|
+
...BASE,
|
|
121
|
+
country: undefined,
|
|
122
|
+
existingDeposited: '0',
|
|
123
|
+
}),
|
|
124
|
+
).toBe(0);
|
|
125
|
+
expect(
|
|
126
|
+
auMinimumRemaining({
|
|
127
|
+
...BASE,
|
|
128
|
+
country: null,
|
|
129
|
+
existingDeposited: '0',
|
|
130
|
+
}),
|
|
131
|
+
).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('is 0 for a non-Australian lender', () => {
|
|
135
|
+
expect(
|
|
136
|
+
auMinimumRemaining({
|
|
137
|
+
...BASE,
|
|
138
|
+
country: 'USA',
|
|
139
|
+
existingDeposited: '0',
|
|
140
|
+
}),
|
|
141
|
+
).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('is 0 on a deployment whose stable has no configured threshold', () => {
|
|
145
|
+
expect(
|
|
146
|
+
auMinimumRemaining({
|
|
147
|
+
...BASE,
|
|
148
|
+
stableSymbol: 'DAI',
|
|
149
|
+
country: 'AUS',
|
|
150
|
+
existingDeposited: '0',
|
|
151
|
+
}),
|
|
152
|
+
).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('auMinimumRemaining — Australian lender', () => {
|
|
157
|
+
it('demands the FULL threshold while the position is unknown (strict-until-loaded)', () => {
|
|
158
|
+
// `undefined` is not a zero position — it is an unread one. Treating
|
|
159
|
+
// it as 0 keeps the floor at its strictest until the read lands, so
|
|
160
|
+
// the field never advertises a minimum lower than the backend accepts.
|
|
161
|
+
expect(
|
|
162
|
+
auMinimumRemaining({
|
|
163
|
+
...BASE,
|
|
164
|
+
country: 'AUS',
|
|
165
|
+
existingDeposited: undefined,
|
|
166
|
+
}),
|
|
167
|
+
).toBe(360_000);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('is 0 once the existing position exactly meets the threshold (inclusive)', () => {
|
|
171
|
+
expect(
|
|
172
|
+
auMinimumRemaining({
|
|
173
|
+
...BASE,
|
|
174
|
+
country: 'AUS',
|
|
175
|
+
existingDeposited: '360000',
|
|
176
|
+
}),
|
|
177
|
+
).toBe(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('is 0 once the existing position exceeds the threshold', () => {
|
|
181
|
+
expect(
|
|
182
|
+
auMinimumRemaining({
|
|
183
|
+
...BASE,
|
|
184
|
+
country: 'AUS',
|
|
185
|
+
existingDeposited: '400000',
|
|
186
|
+
}),
|
|
187
|
+
).toBe(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('returns the remainder for a partial position', () => {
|
|
191
|
+
expect(
|
|
192
|
+
auMinimumRemaining({
|
|
193
|
+
...BASE,
|
|
194
|
+
country: 'AUS',
|
|
195
|
+
existingDeposited: '120000',
|
|
196
|
+
}),
|
|
197
|
+
).toBe(240_000);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('keeps sub-unit precision — one minor unit short still demands one minor unit', () => {
|
|
201
|
+
// 359,999.999999 lent → 0.000001 USDC left to reach 360,000. The
|
|
202
|
+
// bigint arithmetic must not round that away into a 0 (unrestricted).
|
|
203
|
+
expect(
|
|
204
|
+
auMinimumRemaining({
|
|
205
|
+
...BASE,
|
|
206
|
+
country: 'AUS',
|
|
207
|
+
existingDeposited: '359999.999999',
|
|
208
|
+
}),
|
|
209
|
+
).toBe(0.000001);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('accepts a numeric existing position, not just a string', () => {
|
|
213
|
+
expect(
|
|
214
|
+
auMinimumRemaining({
|
|
215
|
+
...BASE,
|
|
216
|
+
country: 'AUS',
|
|
217
|
+
existingDeposited: 120_000,
|
|
218
|
+
}),
|
|
219
|
+
).toBe(240_000);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('uses the 500,000 threshold on AUDD deployments', () => {
|
|
223
|
+
expect(
|
|
224
|
+
auMinimumRemaining({
|
|
225
|
+
...BASE,
|
|
226
|
+
stableSymbol: 'AUDD',
|
|
227
|
+
country: 'AUS',
|
|
228
|
+
existingDeposited: 0,
|
|
229
|
+
}),
|
|
230
|
+
).toBe(500_000);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Deployment configuration the caller supplies — see `isAuMinimumExempt`. The
|
|
235
|
+
// real list is never shipped in this public package; these are stand-ins.
|
|
236
|
+
const EXEMPT_CHECKSUMMED = '0x1111111111111111111111111111111111111111';
|
|
237
|
+
const EXEMPT_LOWER = EXEMPT_CHECKSUMMED.toLowerCase();
|
|
238
|
+
const OTHER_EXEMPT = '0x2222222222222222222222222222222222222222';
|
|
239
|
+
const NOT_EXEMPT = '0x3333333333333333333333333333333333333333';
|
|
240
|
+
const LIST: readonly string[] = [EXEMPT_LOWER, OTHER_EXEMPT.toLowerCase()];
|
|
241
|
+
|
|
242
|
+
describe('isAuMinimumExempt', () => {
|
|
243
|
+
it('matches a listed wallet in any casing, with surrounding whitespace', () => {
|
|
244
|
+
expect(isAuMinimumExempt(EXEMPT_CHECKSUMMED, LIST)).toBe(true);
|
|
245
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, LIST)).toBe(true);
|
|
246
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER.toUpperCase(), LIST)).toBe(true);
|
|
247
|
+
expect(isAuMinimumExempt(` ${OTHER_EXEMPT} `, LIST)).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('matches a list entry stored in mixed case too', () => {
|
|
251
|
+
// The rule normalises both sides, so a caller that reads its list
|
|
252
|
+
// straight out of configuration cannot be caught out by casing.
|
|
253
|
+
expect(
|
|
254
|
+
isAuMinimumExempt(EXEMPT_LOWER, [EXEMPT_CHECKSUMMED.toUpperCase()]),
|
|
255
|
+
).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('fails CLOSED on anything unlisted or not a string', () => {
|
|
259
|
+
expect(isAuMinimumExempt(NOT_EXEMPT, LIST)).toBe(false);
|
|
260
|
+
expect(isAuMinimumExempt('', LIST)).toBe(false);
|
|
261
|
+
expect(isAuMinimumExempt(' ', LIST)).toBe(false);
|
|
262
|
+
expect(isAuMinimumExempt(undefined, LIST)).toBe(false);
|
|
263
|
+
expect(isAuMinimumExempt(null, LIST)).toBe(false);
|
|
264
|
+
// A prefix of, or an extension to, a listed address is another wallet.
|
|
265
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER.slice(0, -1), LIST)).toBe(false);
|
|
266
|
+
expect(isAuMinimumExempt(`${EXEMPT_LOWER}00`, LIST)).toBe(false);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('fails CLOSED when no list was supplied', () => {
|
|
270
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, undefined)).toBe(false);
|
|
271
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, null)).toBe(false);
|
|
272
|
+
expect(isAuMinimumExempt(EXEMPT_LOWER, [])).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('auMinimumRemaining — named-address exemption', () => {
|
|
277
|
+
it('is 0 for an exempt wallet that would otherwise owe the full threshold', () => {
|
|
278
|
+
for (const address of [
|
|
279
|
+
EXEMPT_CHECKSUMMED,
|
|
280
|
+
EXEMPT_LOWER,
|
|
281
|
+
OTHER_EXEMPT,
|
|
282
|
+
]) {
|
|
283
|
+
expect(
|
|
284
|
+
auMinimumRemaining({
|
|
285
|
+
...BASE,
|
|
286
|
+
country: 'AUS',
|
|
287
|
+
address,
|
|
288
|
+
exemptAddresses: LIST,
|
|
289
|
+
existingDeposited: 0,
|
|
290
|
+
}),
|
|
291
|
+
).toBe(0);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('is 0 on AUDD too, where the threshold is the larger 500,000', () => {
|
|
296
|
+
expect(
|
|
297
|
+
auMinimumRemaining({
|
|
298
|
+
...BASE,
|
|
299
|
+
stableSymbol: 'AUDD',
|
|
300
|
+
country: 'AUS',
|
|
301
|
+
address: EXEMPT_LOWER,
|
|
302
|
+
exemptAddresses: LIST,
|
|
303
|
+
existingDeposited: 0,
|
|
304
|
+
}),
|
|
305
|
+
).toBe(0);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('overrides an UNKNOWN position — the strict full-threshold case', () => {
|
|
309
|
+
// `undefined` is what a caller passes while the on-chain read is in
|
|
310
|
+
// flight, and it normally yields the whole threshold. An exemption
|
|
311
|
+
// outranks it, so an exempt lender never sees a floor that later drops.
|
|
312
|
+
expect(
|
|
313
|
+
auMinimumRemaining({
|
|
314
|
+
...BASE,
|
|
315
|
+
country: 'AUS',
|
|
316
|
+
address: EXEMPT_LOWER,
|
|
317
|
+
exemptAddresses: LIST,
|
|
318
|
+
existingDeposited: undefined,
|
|
319
|
+
}),
|
|
320
|
+
).toBe(0);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('leaves a non-exempt Australian on the full threshold', () => {
|
|
324
|
+
expect(
|
|
325
|
+
auMinimumRemaining({
|
|
326
|
+
...BASE,
|
|
327
|
+
country: 'AUS',
|
|
328
|
+
address: NOT_EXEMPT,
|
|
329
|
+
exemptAddresses: LIST,
|
|
330
|
+
existingDeposited: 0,
|
|
331
|
+
}),
|
|
332
|
+
).toBe(360_000);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('treats an absent address, or an absent list, as NOT exempt', () => {
|
|
336
|
+
// Both keys are required; either VALUE may be absent, and when it is
|
|
337
|
+
// the rule applies exactly as it did before exemptions existed.
|
|
338
|
+
for (const address of [undefined, null]) {
|
|
339
|
+
expect(
|
|
340
|
+
auMinimumRemaining({
|
|
341
|
+
...BASE,
|
|
342
|
+
country: 'AUS',
|
|
343
|
+
address,
|
|
344
|
+
exemptAddresses: LIST,
|
|
345
|
+
existingDeposited: 0,
|
|
346
|
+
}),
|
|
347
|
+
).toBe(360_000);
|
|
348
|
+
}
|
|
349
|
+
expect(
|
|
350
|
+
auMinimumRemaining({
|
|
351
|
+
...BASE,
|
|
352
|
+
country: 'AUS',
|
|
353
|
+
address: EXEMPT_LOWER,
|
|
354
|
+
exemptAddresses: undefined,
|
|
355
|
+
existingDeposited: 0,
|
|
356
|
+
}),
|
|
357
|
+
).toBe(360_000);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('is 0 for a non-Australian at an exempt address, as it always was', () => {
|
|
361
|
+
expect(
|
|
362
|
+
auMinimumRemaining({
|
|
363
|
+
...BASE,
|
|
364
|
+
country: 'FRA',
|
|
365
|
+
address: EXEMPT_LOWER,
|
|
366
|
+
exemptAddresses: LIST,
|
|
367
|
+
existingDeposited: 0,
|
|
368
|
+
}),
|
|
369
|
+
).toBe(0);
|
|
370
|
+
});
|
|
371
|
+
});
|