@ledgerhq/coin-hypercore 0.2.1 → 0.4.1

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 (50) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/lib/api/getBalance.d.ts +3 -0
  3. package/lib/api/getBalance.d.ts.map +1 -0
  4. package/lib/api/getBalance.js +21 -0
  5. package/lib/api/getBalance.js.map +1 -0
  6. package/lib/api/index.d.ts.map +1 -1
  7. package/lib/api/index.js +4 -2
  8. package/lib/api/index.js.map +1 -1
  9. package/lib/api/listOperations.d.ts +3 -0
  10. package/lib/api/listOperations.d.ts.map +1 -0
  11. package/lib/api/listOperations.js +57 -0
  12. package/lib/api/listOperations.js.map +1 -0
  13. package/lib/network/yield.d.ts +38 -0
  14. package/lib/network/yield.d.ts.map +1 -0
  15. package/lib/network/yield.js +36 -0
  16. package/lib/network/yield.js.map +1 -0
  17. package/lib/utils/index.d.ts +2 -0
  18. package/lib/utils/index.d.ts.map +1 -0
  19. package/lib/utils/index.js +11 -0
  20. package/lib/utils/index.js.map +1 -0
  21. package/lib-es/api/getBalance.d.ts +3 -0
  22. package/lib-es/api/getBalance.d.ts.map +1 -0
  23. package/lib-es/api/getBalance.js +18 -0
  24. package/lib-es/api/getBalance.js.map +1 -0
  25. package/lib-es/api/index.d.ts.map +1 -1
  26. package/lib-es/api/index.js +4 -2
  27. package/lib-es/api/index.js.map +1 -1
  28. package/lib-es/api/listOperations.d.ts +3 -0
  29. package/lib-es/api/listOperations.d.ts.map +1 -0
  30. package/lib-es/api/listOperations.js +54 -0
  31. package/lib-es/api/listOperations.js.map +1 -0
  32. package/lib-es/network/yield.d.ts +38 -0
  33. package/lib-es/network/yield.d.ts.map +1 -0
  34. package/lib-es/network/yield.js +28 -0
  35. package/lib-es/network/yield.js.map +1 -0
  36. package/lib-es/utils/index.d.ts +2 -0
  37. package/lib-es/utils/index.d.ts.map +1 -0
  38. package/lib-es/utils/index.js +8 -0
  39. package/lib-es/utils/index.js.map +1 -0
  40. package/package.json +2 -2
  41. package/src/api/getBalance.test.ts +66 -0
  42. package/src/api/getBalance.ts +21 -0
  43. package/src/api/index.integ.test.ts +53 -2
  44. package/src/api/index.ts +4 -2
  45. package/src/api/listOperations.test.ts +161 -0
  46. package/src/api/listOperations.ts +76 -0
  47. package/src/network/yield.test.ts +72 -0
  48. package/src/network/yield.ts +72 -0
  49. package/src/utils/index.test.ts +20 -0
  50. package/src/utils/index.ts +9 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @ledgerhq/coin-hypercore
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#740](https://github.com/LedgerHQ/coin-modules/pull/740) [`0f6c4b1`](https://github.com/LedgerHQ/coin-modules/commit/0f6c4b1398b83ec4d291f69b7b112ff1a019ef14) Thanks [@YazhuEth](https://github.com/YazhuEth)! - Make `listOperations` lenient: ignore `minHeight` and `order` instead of throwing, consistent with the other coin-modules
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#737](https://github.com/LedgerHQ/coin-modules/pull/737) [`8e8c819`](https://github.com/LedgerHQ/coin-modules/commit/8e8c819a2af6d9f47a60ff2f56a6248d55dc271f) Thanks [@YazhuEth](https://github.com/YazhuEth)! - Implement `getBalance` (USDC account balance) and `listOperations` (deposit/withdraw only) for coin-hypercore, backed by the Yield.xyz proxy client
14
+
15
+ ## 0.3.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#725](https://github.com/LedgerHQ/coin-modules/pull/725) [`39dd8ef`](https://github.com/LedgerHQ/coin-modules/commit/39dd8ef0eee5974437d6b13925d27cd022d8a940) Thanks [@henri-ly](https://github.com/henri-ly)! - update package to use catalog + hypercore yield client
20
+
3
21
  ## 0.2.1
4
22
 
5
23
  ### Patch Changes
@@ -0,0 +1,3 @@
1
+ import type { Balance } from '@ledgerhq/coin-module-framework/api/types';
2
+ export declare function getBalance(address: string): Promise<Balance[]>;
3
+ //# sourceMappingURL=getBalance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBalance.d.ts","sourceRoot":"","sources":["../../src/api/getBalance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAMxE,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAWpE"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.getBalance = getBalance;
6
+ const yield_1 = require("../network/yield");
7
+ const utils_1 = require("../utils");
8
+ // The proxy only exposes the USDC collateral, surfaced as the account's native balance:
9
+ // `value` is the total account value, `locked` the margin used by open positions.
10
+ async function getBalance(address) {
11
+ const { collateral, accountValue, usedMargin } = await (0, yield_1.fetchBalance)(address);
12
+ const magnitude = collateral.decimals ?? yield_1.USDC_DECIMALS;
13
+ return [
14
+ {
15
+ value: (0, utils_1.scaleToBigInt)(accountValue, magnitude),
16
+ locked: (0, utils_1.scaleToBigInt)(usedMargin, magnitude),
17
+ asset: { type: 'native' },
18
+ },
19
+ ];
20
+ }
21
+ //# sourceMappingURL=getBalance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBalance.js","sourceRoot":"","sources":["../../src/api/getBalance.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;AAQtC,gCAWC;AAhBD,4CAA8D;AAC9D,oCAAwC;AAExC,wFAAwF;AACxF,kFAAkF;AAC3E,KAAK,UAAU,UAAU,CAAC,OAAe;IAC9C,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,oBAAY,EAAC,OAAO,CAAC,CAAA;IAC5E,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,IAAI,qBAAa,CAAA;IAEtD,OAAO;QACL;YACE,KAAK,EAAE,IAAA,qBAAa,EAAC,YAAY,EAAE,SAAS,CAAC;YAC7C,MAAM,EAAE,IAAA,qBAAa,EAAC,UAAU,EAAE,SAAS,CAAC;YAC5C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B;KACF,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAgB,MAAM,2CAA2C,CAAA;AAC5F,OAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAuBhE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAgB,MAAM,2CAA2C,CAAA;AAC5F,OAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAI5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAuBhE"}
package/lib/api/index.js CHANGED
@@ -8,6 +8,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.createApi = createApi;
9
9
  const index_1 = require("@ledgerhq/coin-module-framework/api/index");
10
10
  const config_1 = __importDefault(require("../config"));
11
+ const getBalance_1 = require("./getBalance");
12
+ const listOperations_1 = require("./listOperations");
11
13
  function createApi(config) {
12
14
  config_1.default.setCoinConfig(() => ({ ...config, status: { type: 'active' } }));
13
15
  return {
@@ -16,8 +18,8 @@ function createApi(config) {
16
18
  getBlock: (0, index_1.notSupported)('getBlock'),
17
19
  call: (0, index_1.notSupported)('call'),
18
20
  getValidators: (0, index_1.notSupported)('getValidators'),
19
- getBalance: (0, index_1.notSupported)('getBalance'),
20
- listOperations: (0, index_1.notSupported)('listOperations'),
21
+ getBalance: getBalance_1.getBalance,
22
+ listOperations: listOperations_1.listOperations,
21
23
  getStakes: (0, index_1.notSupported)('getStakes'),
22
24
  getRewards: (0, index_1.notSupported)('getRewards'),
23
25
  craftTransaction: (0, index_1.notSupported)('craftTransaction'),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;;;;AAKtC,8BAuBC;AA1BD,qEAA4F;AAC5F,uDAA4D;AAE5D,SAAgB,SAAS,CAAC,MAAuB;IAC/C,gBAAU,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAA;IAE3E,OAAO;QACL,SAAS,EAAE,IAAA,oBAAY,EAAC,WAAW,CAAC;QACpC,YAAY,EAAE,IAAA,oBAAY,EAAC,cAAc,CAAC;QAC1C,QAAQ,EAAE,IAAA,oBAAY,EAAC,UAAU,CAAC;QAClC,IAAI,EAAE,IAAA,oBAAY,EAAC,MAAM,CAAC;QAC1B,aAAa,EAAE,IAAA,oBAAY,EAAC,eAAe,CAAC;QAC5C,UAAU,EAAE,IAAA,oBAAY,EAAC,YAAY,CAAC;QACtC,cAAc,EAAE,IAAA,oBAAY,EAAC,gBAAgB,CAAC;QAC9C,SAAS,EAAE,IAAA,oBAAY,EAAC,WAAW,CAAC;QACpC,UAAU,EAAE,IAAA,oBAAY,EAAC,YAAY,CAAC;QACtC,gBAAgB,EAAE,IAAA,oBAAY,EAAC,kBAAkB,CAAC;QAClD,mBAAmB,EAAE,IAAA,oBAAY,EAAC,qBAAqB,CAAC;QACxD,YAAY,EAAE,IAAA,oBAAY,EAAC,cAAc,CAAC;QAC1C,OAAO,EAAE,IAAA,oBAAY,EAAC,SAAS,CAAC;QAChC,SAAS,EAAE,IAAA,oBAAY,EAAC,WAAW,CAAC;QACpC,cAAc,EAAE,IAAA,oBAAY,EAAC,gBAAgB,CAAC;QAC9C,eAAe,EAAE,IAAA,oBAAY,EAAC,iBAAiB,CAAC;QAChD,eAAe,EAAE,IAAA,oBAAY,EAAC,iBAAiB,CAAC;QAChD,oBAAoB,EAAE,IAAA,oBAAY,EAAC,sBAAsB,CAAC;KAC3D,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;;;;AAOtC,8BAuBC;AA5BD,qEAA4F;AAC5F,uDAA4D;AAC5D,6CAAyC;AACzC,qDAAiD;AAEjD,SAAgB,SAAS,CAAC,MAAuB;IAC/C,gBAAU,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAA;IAE3E,OAAO;QACL,SAAS,EAAE,IAAA,oBAAY,EAAC,WAAW,CAAC;QACpC,YAAY,EAAE,IAAA,oBAAY,EAAC,cAAc,CAAC;QAC1C,QAAQ,EAAE,IAAA,oBAAY,EAAC,UAAU,CAAC;QAClC,IAAI,EAAE,IAAA,oBAAY,EAAC,MAAM,CAAC;QAC1B,aAAa,EAAE,IAAA,oBAAY,EAAC,eAAe,CAAC;QAC5C,UAAU,EAAV,uBAAU;QACV,cAAc,EAAd,+BAAc;QACd,SAAS,EAAE,IAAA,oBAAY,EAAC,WAAW,CAAC;QACpC,UAAU,EAAE,IAAA,oBAAY,EAAC,YAAY,CAAC;QACtC,gBAAgB,EAAE,IAAA,oBAAY,EAAC,kBAAkB,CAAC;QAClD,mBAAmB,EAAE,IAAA,oBAAY,EAAC,qBAAqB,CAAC;QACxD,YAAY,EAAE,IAAA,oBAAY,EAAC,cAAc,CAAC;QAC1C,OAAO,EAAE,IAAA,oBAAY,EAAC,SAAS,CAAC;QAChC,SAAS,EAAE,IAAA,oBAAY,EAAC,WAAW,CAAC;QACpC,cAAc,EAAE,IAAA,oBAAY,EAAC,gBAAgB,CAAC;QAC9C,eAAe,EAAE,IAAA,oBAAY,EAAC,iBAAiB,CAAC;QAChD,eAAe,EAAE,IAAA,oBAAY,EAAC,iBAAiB,CAAC;QAChD,oBAAoB,EAAE,IAAA,oBAAY,EAAC,sBAAsB,CAAC;KAC3D,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ListOperationsOptions, Operation, Page } from '@ledgerhq/coin-module-framework/api/types';
2
+ export declare function listOperations(address: string, { cursor, limit }: ListOperationsOptions): Promise<Page<Operation>>;
3
+ //# sourceMappingURL=listOperations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listOperations.d.ts","sourceRoot":"","sources":["../../src/api/listOperations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACT,IAAI,EACL,MAAM,2CAA2C,CAAA;AAmDlD,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,qBAAqB,GACvC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAc1B"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.listOperations = listOperations;
6
+ const yield_1 = require("../network/yield");
7
+ const utils_1 = require("../utils");
8
+ const DEFAULT_LIMIT = 100;
9
+ // Deposits/withdrawals that actually settled — drafts (status CREATED) have no completedAt.
10
+ function isSettledTransfer(action) {
11
+ const isTransfer = action.action === yield_1.FUND_ACTION || action.action === yield_1.WITHDRAW_ACTION;
12
+ return isTransfer && action.completedAt !== null;
13
+ }
14
+ function toOperation(address, dto) {
15
+ const isDeposit = dto.action === yield_1.FUND_ACTION;
16
+ const amount = dto.summary?.amount ?? '0';
17
+ const date = new Date(dto.completedAt ?? dto.createdAt);
18
+ return {
19
+ id: `${address}-${dto.id}`,
20
+ type: isDeposit ? 'IN' : 'OUT',
21
+ senders: [address],
22
+ recipients: [address],
23
+ value: (0, utils_1.scaleToBigInt)(amount, yield_1.USDC_DECIMALS),
24
+ asset: { type: 'native' },
25
+ tx: {
26
+ hash: dto.id,
27
+ // HyperCore actions have no block height on this API.
28
+ block: { height: 0, hash: '', time: date },
29
+ fees: 0n,
30
+ date,
31
+ failed: dto.status.toUpperCase() === 'FAILED',
32
+ },
33
+ };
34
+ }
35
+ // The proxy only supports offset pagination, so the cursor is the numeric offset.
36
+ function parseCursor(cursor) {
37
+ if (cursor === undefined)
38
+ return 0;
39
+ const offset = Number(cursor);
40
+ if (!Number.isInteger(offset) || offset < 0) {
41
+ throw new Error(`listOperations received an invalid cursor: ${cursor}`);
42
+ }
43
+ return offset;
44
+ }
45
+ async function listOperations(address, { cursor, limit }) {
46
+ const offset = parseCursor(cursor);
47
+ const pageLimit = limit ?? DEFAULT_LIMIT;
48
+ const page = await (0, yield_1.fetchActions)(address, offset, pageLimit);
49
+ const items = page.items.filter(isSettledTransfer).map((action) => toOperation(address, action));
50
+ // Advance by raw items consumed, not the filtered count, so no action is skipped.
51
+ // The cursor is offset-based (volatile if new actions appear between calls); the proxy
52
+ // exposes no stable id/date-based pagination.
53
+ const nextOffset = offset + page.items.length;
54
+ const next = nextOffset < page.total ? String(nextOffset) : undefined;
55
+ return { items, next };
56
+ }
57
+ //# sourceMappingURL=listOperations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listOperations.js","sourceRoot":"","sources":["../../src/api/listOperations.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;AAyDtC,wCAiBC;AAnED,4CAMyB;AACzB,oCAAwC;AAExC,MAAM,aAAa,GAAG,GAAG,CAAA;AAEzB,4FAA4F;AAC5F,SAAS,iBAAiB,CAAC,MAAiB;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,KAAK,mBAAW,IAAI,MAAM,CAAC,MAAM,KAAK,uBAAe,CAAA;IACrF,OAAO,UAAU,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,CAAA;AAClD,CAAC;AAED,SAAS,WAAW,CAAC,OAAe,EAAE,GAAc;IAClD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,mBAAW,CAAA;IAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,CAAA;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;IAEvD,OAAO;QACL,EAAE,EAAE,GAAG,OAAO,IAAI,GAAG,CAAC,EAAE,EAAE;QAC1B,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK;QAC9B,OAAO,EAAE,CAAC,OAAO,CAAC;QAClB,UAAU,EAAE,CAAC,OAAO,CAAC;QACrB,KAAK,EAAE,IAAA,qBAAa,EAAC,MAAM,EAAE,qBAAa,CAAC;QAC3C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,EAAE,EAAE;YACF,IAAI,EAAE,GAAG,CAAC,EAAE;YACZ,sDAAsD;YACtD,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;YAC1C,IAAI,EAAE,EAAE;YACR,IAAI;YACJ,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ;SAC9C;KACF,CAAA;AACH,CAAC;AAED,kFAAkF;AAClF,SAAS,WAAW,CAAC,MAA0B;IAC7C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IAClC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,8CAA8C,MAAM,EAAE,CAAC,CAAA;IACzE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAEM,KAAK,UAAU,cAAc,CAClC,OAAe,EACf,EAAE,MAAM,EAAE,KAAK,EAAyB;IAExC,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,MAAM,SAAS,GAAG,KAAK,IAAI,aAAa,CAAA;IACxC,MAAM,IAAI,GAAG,MAAM,IAAA,oBAAY,EAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;IAE3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhG,kFAAkF;IAClF,uFAAuF;IACvF,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;IAC7C,MAAM,IAAI,GAAG,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAErE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACxB,CAAC"}
@@ -0,0 +1,38 @@
1
+ export declare const USDC_DECIMALS = 6;
2
+ export declare const FUND_ACTION = "fund";
3
+ export declare const WITHDRAW_ACTION = "withdraw";
4
+ export type TokenDto = {
5
+ symbol: string;
6
+ network: string;
7
+ name?: string;
8
+ decimals?: number;
9
+ address?: string | null;
10
+ logoURI?: string | null;
11
+ };
12
+ export type YieldBalanceDto = {
13
+ providerId: string;
14
+ collateral: TokenDto;
15
+ accountValue: number;
16
+ usedMargin: number;
17
+ availableBalance: number;
18
+ unrealizedPnl: number;
19
+ };
20
+ export declare function fetchBalance(address: string): Promise<YieldBalanceDto>;
21
+ export type ActionDto = {
22
+ id: string;
23
+ action: string;
24
+ status: string;
25
+ summary: {
26
+ amount: string;
27
+ } | null;
28
+ createdAt: string;
29
+ completedAt: string | null;
30
+ };
31
+ export type ActionsPage = {
32
+ total: number;
33
+ offset: number;
34
+ limit: number;
35
+ items: ActionDto[];
36
+ };
37
+ export declare function fetchActions(address: string, offset: number, limit: number): Promise<ActionsPage>;
38
+ //# sourceMappingURL=yield.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yield.d.ts","sourceRoot":"","sources":["../../src/network/yield.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B,eAAO,MAAM,WAAW,SAAS,CAAA;AACjC,eAAO,MAAM,eAAe,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IAEf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,QAAQ,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAO5E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IAEd,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB,CAAA;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC,CAOtB"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.WITHDRAW_ACTION = exports.FUND_ACTION = exports.USDC_DECIMALS = void 0;
9
+ exports.fetchBalance = fetchBalance;
10
+ exports.fetchActions = fetchActions;
11
+ const live_network_1 = __importDefault(require("@ledgerhq/live-network"));
12
+ const config_1 = __importDefault(require("../config"));
13
+ const PROVIDER_ID = 'hyperliquid';
14
+ const getBaseUrl = () => config_1.default.getCoinConfig().node;
15
+ // The collateral is USDC and the proxy omits its decimals.
16
+ exports.USDC_DECIMALS = 6;
17
+ // A deposit is the `fund` action, a withdrawal the `withdraw` action.
18
+ exports.FUND_ACTION = 'fund';
19
+ exports.WITHDRAW_ACTION = 'withdraw';
20
+ async function fetchBalance(address) {
21
+ const { data } = await (0, live_network_1.default)({
22
+ method: 'POST',
23
+ url: `${getBaseUrl()}/v1/balances`,
24
+ data: { providerId: PROVIDER_ID, address },
25
+ });
26
+ return data;
27
+ }
28
+ async function fetchActions(address, offset, limit) {
29
+ const { data } = await (0, live_network_1.default)({
30
+ method: 'GET',
31
+ url: `${getBaseUrl()}/v1/actions`,
32
+ params: { address, offset, limit, providerId: PROVIDER_ID },
33
+ });
34
+ return data;
35
+ }
36
+ //# sourceMappingURL=yield.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yield.js","sourceRoot":"","sources":["../../src/network/yield.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;;;;;AAiCtC,oCAOC;AAmBD,oCAWC;AApED,0EAA4C;AAC5C,uDAAkC;AAElC,MAAM,WAAW,GAAG,aAAa,CAAA;AACjC,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,gBAAU,CAAC,aAAa,EAAE,CAAC,IAAI,CAAA;AAExD,2DAA2D;AAC9C,QAAA,aAAa,GAAG,CAAC,CAAA;AAC9B,sEAAsE;AACzD,QAAA,WAAW,GAAG,MAAM,CAAA;AACpB,QAAA,eAAe,GAAG,UAAU,CAAA;AAqBlC,KAAK,UAAU,YAAY,CAAC,OAAe;IAChD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAA,sBAAO,EAAkB;QAC9C,MAAM,EAAE,MAAM;QACd,GAAG,EAAE,GAAG,UAAU,EAAE,cAAc;QAClC,IAAI,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE;KAC3C,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC;AAmBM,KAAK,UAAU,YAAY,CAChC,OAAe,EACf,MAAc,EACd,KAAa;IAEb,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAA,sBAAO,EAAc;QAC1C,MAAM,EAAE,KAAK;QACb,GAAG,EAAE,GAAG,UAAU,EAAE,aAAa;QACjC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE;KAC5D,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function scaleToBigInt(amount: string | number, magnitude: number): bigint;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEhF"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.scaleToBigInt = scaleToBigInt;
6
+ const currencies_1 = require("@ledgerhq/coin-module-framework/currencies");
7
+ // Convert a decimal amount into its integer base-unit representation.
8
+ function scaleToBigInt(amount, magnitude) {
9
+ return BigInt((0, currencies_1.parseCurrencyUnit)({ name: '', code: '', magnitude }, String(amount)).toFixed());
10
+ }
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;AAKtC,sCAEC;AALD,2EAA8E;AAE9E,sEAAsE;AACtE,SAAgB,aAAa,CAAC,MAAuB,EAAE,SAAiB;IACtE,OAAO,MAAM,CAAC,IAAA,8BAAiB,EAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;AAC/F,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Balance } from '@ledgerhq/coin-module-framework/api/types';
2
+ export declare function getBalance(address: string): Promise<Balance[]>;
3
+ //# sourceMappingURL=getBalance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBalance.d.ts","sourceRoot":"","sources":["../../src/api/getBalance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAMxE,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAWpE"}
@@ -0,0 +1,18 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { USDC_DECIMALS, fetchBalance } from '../network/yield';
4
+ import { scaleToBigInt } from '../utils';
5
+ // The proxy only exposes the USDC collateral, surfaced as the account's native balance:
6
+ // `value` is the total account value, `locked` the margin used by open positions.
7
+ export async function getBalance(address) {
8
+ const { collateral, accountValue, usedMargin } = await fetchBalance(address);
9
+ const magnitude = collateral.decimals ?? USDC_DECIMALS;
10
+ return [
11
+ {
12
+ value: scaleToBigInt(accountValue, magnitude),
13
+ locked: scaleToBigInt(usedMargin, magnitude),
14
+ asset: { type: 'native' },
15
+ },
16
+ ];
17
+ }
18
+ //# sourceMappingURL=getBalance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBalance.js","sourceRoot":"","sources":["../../src/api/getBalance.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAGtC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,wFAAwF;AACxF,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe;IAC9C,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAA;IAC5E,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,IAAI,aAAa,CAAA;IAEtD,OAAO;QACL;YACE,KAAK,EAAE,aAAa,CAAC,YAAY,EAAE,SAAS,CAAC;YAC7C,MAAM,EAAE,aAAa,CAAC,UAAU,EAAE,SAAS,CAAC;YAC5C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B;KACF,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAgB,MAAM,2CAA2C,CAAA;AAC5F,OAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAuBhE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAgB,MAAM,2CAA2C,CAAA;AAC5F,OAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAI5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAuBhE"}
@@ -2,6 +2,8 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { notSupported } from '@ledgerhq/coin-module-framework/api/index';
4
4
  import coinConfig from '../config';
5
+ import { getBalance } from './getBalance';
6
+ import { listOperations } from './listOperations';
5
7
  export function createApi(config) {
6
8
  coinConfig.setCoinConfig(() => ({ ...config, status: { type: 'active' } }));
7
9
  return {
@@ -10,8 +12,8 @@ export function createApi(config) {
10
12
  getBlock: notSupported('getBlock'),
11
13
  call: notSupported('call'),
12
14
  getValidators: notSupported('getValidators'),
13
- getBalance: notSupported('getBalance'),
14
- listOperations: notSupported('listOperations'),
15
+ getBalance,
16
+ listOperations,
15
17
  getStakes: notSupported('getStakes'),
16
18
  getRewards: notSupported('getRewards'),
17
19
  craftTransaction: notSupported('craftTransaction'),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAEtC,OAAO,EAAsB,YAAY,EAAE,MAAM,2CAA2C,CAAA;AAC5F,OAAO,UAAoC,MAAM,WAAW,CAAA;AAE5D,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,UAAU,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAA;IAE3E,OAAO;QACL,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC;QACpC,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC;QAC1C,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC;QAClC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;QAC1B,aAAa,EAAE,YAAY,CAAC,eAAe,CAAC;QAC5C,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC;QACtC,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC;QAC9C,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC;QACpC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC;QACtC,gBAAgB,EAAE,YAAY,CAAC,kBAAkB,CAAC;QAClD,mBAAmB,EAAE,YAAY,CAAC,qBAAqB,CAAC;QACxD,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC;QAC1C,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC;QAChC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC;QACpC,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC;QAC9C,eAAe,EAAE,YAAY,CAAC,iBAAiB,CAAC;QAChD,eAAe,EAAE,YAAY,CAAC,iBAAiB,CAAC;QAChD,oBAAoB,EAAE,YAAY,CAAC,sBAAsB,CAAC;KAC3D,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAEtC,OAAO,EAAsB,YAAY,EAAE,MAAM,2CAA2C,CAAA;AAC5F,OAAO,UAAoC,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,UAAU,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAA;IAE3E,OAAO;QACL,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC;QACpC,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC;QAC1C,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC;QAClC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;QAC1B,aAAa,EAAE,YAAY,CAAC,eAAe,CAAC;QAC5C,UAAU;QACV,cAAc;QACd,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC;QACpC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC;QACtC,gBAAgB,EAAE,YAAY,CAAC,kBAAkB,CAAC;QAClD,mBAAmB,EAAE,YAAY,CAAC,qBAAqB,CAAC;QACxD,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC;QAC1C,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC;QAChC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC;QACpC,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC;QAC9C,eAAe,EAAE,YAAY,CAAC,iBAAiB,CAAC;QAChD,eAAe,EAAE,YAAY,CAAC,iBAAiB,CAAC;QAChD,oBAAoB,EAAE,YAAY,CAAC,sBAAsB,CAAC;KAC3D,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ListOperationsOptions, Operation, Page } from '@ledgerhq/coin-module-framework/api/types';
2
+ export declare function listOperations(address: string, { cursor, limit }: ListOperationsOptions): Promise<Page<Operation>>;
3
+ //# sourceMappingURL=listOperations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listOperations.d.ts","sourceRoot":"","sources":["../../src/api/listOperations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACT,IAAI,EACL,MAAM,2CAA2C,CAAA;AAmDlD,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,qBAAqB,GACvC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAc1B"}
@@ -0,0 +1,54 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { FUND_ACTION, USDC_DECIMALS, WITHDRAW_ACTION, fetchActions, } from '../network/yield';
4
+ import { scaleToBigInt } from '../utils';
5
+ const DEFAULT_LIMIT = 100;
6
+ // Deposits/withdrawals that actually settled — drafts (status CREATED) have no completedAt.
7
+ function isSettledTransfer(action) {
8
+ const isTransfer = action.action === FUND_ACTION || action.action === WITHDRAW_ACTION;
9
+ return isTransfer && action.completedAt !== null;
10
+ }
11
+ function toOperation(address, dto) {
12
+ const isDeposit = dto.action === FUND_ACTION;
13
+ const amount = dto.summary?.amount ?? '0';
14
+ const date = new Date(dto.completedAt ?? dto.createdAt);
15
+ return {
16
+ id: `${address}-${dto.id}`,
17
+ type: isDeposit ? 'IN' : 'OUT',
18
+ senders: [address],
19
+ recipients: [address],
20
+ value: scaleToBigInt(amount, USDC_DECIMALS),
21
+ asset: { type: 'native' },
22
+ tx: {
23
+ hash: dto.id,
24
+ // HyperCore actions have no block height on this API.
25
+ block: { height: 0, hash: '', time: date },
26
+ fees: 0n,
27
+ date,
28
+ failed: dto.status.toUpperCase() === 'FAILED',
29
+ },
30
+ };
31
+ }
32
+ // The proxy only supports offset pagination, so the cursor is the numeric offset.
33
+ function parseCursor(cursor) {
34
+ if (cursor === undefined)
35
+ return 0;
36
+ const offset = Number(cursor);
37
+ if (!Number.isInteger(offset) || offset < 0) {
38
+ throw new Error(`listOperations received an invalid cursor: ${cursor}`);
39
+ }
40
+ return offset;
41
+ }
42
+ export async function listOperations(address, { cursor, limit }) {
43
+ const offset = parseCursor(cursor);
44
+ const pageLimit = limit ?? DEFAULT_LIMIT;
45
+ const page = await fetchActions(address, offset, pageLimit);
46
+ const items = page.items.filter(isSettledTransfer).map((action) => toOperation(address, action));
47
+ // Advance by raw items consumed, not the filtered count, so no action is skipped.
48
+ // The cursor is offset-based (volatile if new actions appear between calls); the proxy
49
+ // exposes no stable id/date-based pagination.
50
+ const nextOffset = offset + page.items.length;
51
+ const next = nextOffset < page.total ? String(nextOffset) : undefined;
52
+ return { items, next };
53
+ }
54
+ //# sourceMappingURL=listOperations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listOperations.js","sourceRoot":"","sources":["../../src/api/listOperations.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAOtC,OAAO,EAEL,WAAW,EACX,aAAa,EACb,eAAe,EACf,YAAY,GACb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,aAAa,GAAG,GAAG,CAAA;AAEzB,4FAA4F;AAC5F,SAAS,iBAAiB,CAAC,MAAiB;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,eAAe,CAAA;IACrF,OAAO,UAAU,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,CAAA;AAClD,CAAC;AAED,SAAS,WAAW,CAAC,OAAe,EAAE,GAAc;IAClD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,WAAW,CAAA;IAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,CAAA;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;IAEvD,OAAO;QACL,EAAE,EAAE,GAAG,OAAO,IAAI,GAAG,CAAC,EAAE,EAAE;QAC1B,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK;QAC9B,OAAO,EAAE,CAAC,OAAO,CAAC;QAClB,UAAU,EAAE,CAAC,OAAO,CAAC;QACrB,KAAK,EAAE,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC;QAC3C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACzB,EAAE,EAAE;YACF,IAAI,EAAE,GAAG,CAAC,EAAE;YACZ,sDAAsD;YACtD,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;YAC1C,IAAI,EAAE,EAAE;YACR,IAAI;YACJ,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ;SAC9C;KACF,CAAA;AACH,CAAC;AAED,kFAAkF;AAClF,SAAS,WAAW,CAAC,MAA0B;IAC7C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IAClC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,8CAA8C,MAAM,EAAE,CAAC,CAAA;IACzE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAe,EACf,EAAE,MAAM,EAAE,KAAK,EAAyB;IAExC,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,MAAM,SAAS,GAAG,KAAK,IAAI,aAAa,CAAA;IACxC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;IAE3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhG,kFAAkF;IAClF,uFAAuF;IACvF,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;IAC7C,MAAM,IAAI,GAAG,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAErE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACxB,CAAC"}
@@ -0,0 +1,38 @@
1
+ export declare const USDC_DECIMALS = 6;
2
+ export declare const FUND_ACTION = "fund";
3
+ export declare const WITHDRAW_ACTION = "withdraw";
4
+ export type TokenDto = {
5
+ symbol: string;
6
+ network: string;
7
+ name?: string;
8
+ decimals?: number;
9
+ address?: string | null;
10
+ logoURI?: string | null;
11
+ };
12
+ export type YieldBalanceDto = {
13
+ providerId: string;
14
+ collateral: TokenDto;
15
+ accountValue: number;
16
+ usedMargin: number;
17
+ availableBalance: number;
18
+ unrealizedPnl: number;
19
+ };
20
+ export declare function fetchBalance(address: string): Promise<YieldBalanceDto>;
21
+ export type ActionDto = {
22
+ id: string;
23
+ action: string;
24
+ status: string;
25
+ summary: {
26
+ amount: string;
27
+ } | null;
28
+ createdAt: string;
29
+ completedAt: string | null;
30
+ };
31
+ export type ActionsPage = {
32
+ total: number;
33
+ offset: number;
34
+ limit: number;
35
+ items: ActionDto[];
36
+ };
37
+ export declare function fetchActions(address: string, offset: number, limit: number): Promise<ActionsPage>;
38
+ //# sourceMappingURL=yield.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yield.d.ts","sourceRoot":"","sources":["../../src/network/yield.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B,eAAO,MAAM,WAAW,SAAS,CAAA;AACjC,eAAO,MAAM,eAAe,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IAEf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,QAAQ,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAO5E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IAEd,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB,CAAA;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC,CAOtB"}
@@ -0,0 +1,28 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import network from '@ledgerhq/live-network';
4
+ import coinConfig from '../config';
5
+ const PROVIDER_ID = 'hyperliquid';
6
+ const getBaseUrl = () => coinConfig.getCoinConfig().node;
7
+ // The collateral is USDC and the proxy omits its decimals.
8
+ export const USDC_DECIMALS = 6;
9
+ // A deposit is the `fund` action, a withdrawal the `withdraw` action.
10
+ export const FUND_ACTION = 'fund';
11
+ export const WITHDRAW_ACTION = 'withdraw';
12
+ export async function fetchBalance(address) {
13
+ const { data } = await network({
14
+ method: 'POST',
15
+ url: `${getBaseUrl()}/v1/balances`,
16
+ data: { providerId: PROVIDER_ID, address },
17
+ });
18
+ return data;
19
+ }
20
+ export async function fetchActions(address, offset, limit) {
21
+ const { data } = await network({
22
+ method: 'GET',
23
+ url: `${getBaseUrl()}/v1/actions`,
24
+ params: { address, offset, limit, providerId: PROVIDER_ID },
25
+ });
26
+ return data;
27
+ }
28
+ //# sourceMappingURL=yield.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yield.js","sourceRoot":"","sources":["../../src/network/yield.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAEtC,OAAO,OAAO,MAAM,wBAAwB,CAAA;AAC5C,OAAO,UAAU,MAAM,WAAW,CAAA;AAElC,MAAM,WAAW,GAAG,aAAa,CAAA;AACjC,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC,IAAI,CAAA;AAExD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAA;AAC9B,sEAAsE;AACtE,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAA;AACjC,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAA;AAqBzC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAe;IAChD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAkB;QAC9C,MAAM,EAAE,MAAM;QACd,GAAG,EAAE,GAAG,UAAU,EAAE,cAAc;QAClC,IAAI,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE;KAC3C,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC;AAmBD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAe,EACf,MAAc,EACd,KAAa;IAEb,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAc;QAC1C,MAAM,EAAE,KAAK;QACb,GAAG,EAAE,GAAG,UAAU,EAAE,aAAa;QACjC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE;KAC5D,CAAC,CAAA;IACF,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function scaleToBigInt(amount: string | number, magnitude: number): bigint;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEhF"}
@@ -0,0 +1,8 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { parseCurrencyUnit } from '@ledgerhq/coin-module-framework/currencies';
4
+ // Convert a decimal amount into its integer base-unit representation.
5
+ export function scaleToBigInt(amount, magnitude) {
6
+ return BigInt(parseCurrencyUnit({ name: '', code: '', magnitude }, String(amount)).toFixed());
7
+ }
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAE9E,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,MAAuB,EAAE,SAAiB;IACtE,OAAO,MAAM,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;AAC/F,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/coin-hypercore",
3
- "version": "0.2.1",
3
+ "version": "0.4.1",
4
4
  "description": "Ledger Hypercore Coin integration",
5
5
  "keywords": [
6
6
  "Ledger",
@@ -65,7 +65,7 @@
65
65
  "author": "LEDGER SAS",
66
66
  "license": "Apache-2.0",
67
67
  "dependencies": {
68
- "@ledgerhq/coin-module-framework": "^4.0.0"
68
+ "@ledgerhq/coin-module-framework": "^4.0.1"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@swc/core": "1.15.11",
@@ -0,0 +1,66 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import network from '@ledgerhq/live-network'
5
+ import coinConfig from '../config'
6
+ import { getBalance } from './getBalance'
7
+
8
+ jest.mock('@ledgerhq/live-network')
9
+
10
+ const mockNetwork = jest.mocked(network)
11
+
12
+ beforeAll(() => {
13
+ coinConfig.setCoinConfig(() => ({
14
+ node: 'https://perps.live.ledger.com/proxy/perps',
15
+ status: { type: 'active' },
16
+ }))
17
+ })
18
+
19
+ afterEach(() => {
20
+ mockNetwork.mockClear()
21
+ })
22
+
23
+ describe('getBalance', () => {
24
+ it('maps the USDC collateral to a single native balance', async () => {
25
+ mockNetwork.mockResolvedValueOnce({
26
+ status: 200,
27
+ data: {
28
+ providerId: 'hyperliquid',
29
+ collateral: { symbol: 'USDC', network: 'hyperliquid' },
30
+ accountValue: 557.13644,
31
+ usedMargin: 20,
32
+ availableBalance: 537.13644,
33
+ unrealizedPnl: 0,
34
+ },
35
+ })
36
+
37
+ const balances = await getBalance('0xabc')
38
+
39
+ expect(balances).toEqual([
40
+ {
41
+ value: 557_136_440n,
42
+ locked: 20_000_000n,
43
+ asset: { type: 'native' },
44
+ },
45
+ ])
46
+ })
47
+
48
+ it('handles a zero balance without throwing', async () => {
49
+ mockNetwork.mockResolvedValueOnce({
50
+ status: 200,
51
+ data: {
52
+ providerId: 'hyperliquid',
53
+ collateral: { symbol: 'USDC', network: 'hyperliquid' },
54
+ accountValue: 0,
55
+ usedMargin: 0,
56
+ availableBalance: 0,
57
+ unrealizedPnl: 0,
58
+ },
59
+ })
60
+
61
+ const balances = await getBalance('0xabc')
62
+
63
+ expect(balances[0].value).toBe(0n)
64
+ expect(balances[0].locked).toBe(0n)
65
+ })
66
+ })
@@ -0,0 +1,21 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { Balance } from '@ledgerhq/coin-module-framework/api/types'
5
+ import { USDC_DECIMALS, fetchBalance } from '../network/yield'
6
+ import { scaleToBigInt } from '../utils'
7
+
8
+ // The proxy only exposes the USDC collateral, surfaced as the account's native balance:
9
+ // `value` is the total account value, `locked` the margin used by open positions.
10
+ export async function getBalance(address: string): Promise<Balance[]> {
11
+ const { collateral, accountValue, usedMargin } = await fetchBalance(address)
12
+ const magnitude = collateral.decimals ?? USDC_DECIMALS
13
+
14
+ return [
15
+ {
16
+ value: scaleToBigInt(accountValue, magnitude),
17
+ locked: scaleToBigInt(usedMargin, magnitude),
18
+ asset: { type: 'native' },
19
+ },
20
+ ]
21
+ }
@@ -3,6 +3,10 @@
3
3
 
4
4
  import { createApi } from '.'
5
5
 
6
+ const NODE = 'https://perps.live.ledger.com/proxy/perps'
7
+ // A real account with USDC collateral and settled fund/withdraw history, for read-only assertions.
8
+ const ADDRESS = '0x90D5b3f3FaA3cd61fBd78bF1CE3DdB2100F4BFb2'
9
+
6
10
  describe('Hypercore Api (Alpaca stub)', () => {
7
11
  const api = createApi({ node: 'https://api.hyperliquid.xyz' })
8
12
 
@@ -11,8 +15,6 @@ describe('Hypercore Api (Alpaca stub)', () => {
11
15
  'getBlockInfo',
12
16
  'getBlock',
13
17
  'getValidators',
14
- 'getBalance',
15
- 'listOperations',
16
18
  'getStakes',
17
19
  'getRewards',
18
20
  'craftTransaction',
@@ -28,3 +30,52 @@ describe('Hypercore Api (Alpaca stub)', () => {
28
30
  expect(api[name]).toThrow('not supported')
29
31
  })
30
32
  })
33
+
34
+ describe('Hypercore Api (Alpaca)', () => {
35
+ const api = createApi({ node: NODE })
36
+
37
+ describe('getBalance', () => {
38
+ it('returns the USDC-denominated account balance', async () => {
39
+ const balances = await api.getBalance(ADDRESS)
40
+
41
+ expect(balances).toHaveLength(1)
42
+ const [balance] = balances
43
+ expect(balance.asset.type).toBe('native')
44
+ expect(typeof balance.value).toBe('bigint')
45
+ expect(balance.value).toBeGreaterThanOrEqual(0n)
46
+ expect(balance.locked ?? 0n).toBeGreaterThanOrEqual(0n)
47
+ })
48
+ })
49
+
50
+ describe('listOperations', () => {
51
+ it('returns settled deposit/withdraw operations across all pages', async () => {
52
+ // Small limit to force several pages and exercise cursor chaining end-to-end.
53
+ const first = await api.listOperations(ADDRESS, { minHeight: 0, limit: 20 })
54
+ const all = [...first.items]
55
+ let cursor = first.next
56
+ while (cursor !== undefined && all.length < 10_000) {
57
+ const page = await api.listOperations(ADDRESS, { minHeight: 0, cursor, limit: 20 })
58
+ all.push(...page.items)
59
+ cursor = page.next
60
+ }
61
+
62
+ expect(all.length).toBeGreaterThan(0)
63
+ for (const op of all) {
64
+ expect(['IN', 'OUT']).toContain(op.type)
65
+ expect(op.senders).toContain(ADDRESS)
66
+ expect(op.recipients).toContain(ADDRESS)
67
+ expect(op.value).toBeGreaterThan(0n)
68
+ expect(op.tx.date).toBeInstanceOf(Date)
69
+ expect(op.tx.fees).toBe(0n)
70
+ }
71
+ // This account has both deposits (IN) and withdrawals (OUT).
72
+ expect(all.some((op) => op.type === 'IN')).toBe(true)
73
+ expect(all.some((op) => op.type === 'OUT')).toBe(true)
74
+ })
75
+
76
+ it('ignores minHeight and order (returns results, no throw)', async () => {
77
+ const { items } = await api.listOperations(ADDRESS, { minHeight: 1, order: 'asc' })
78
+ expect(items.length).toBeGreaterThan(0)
79
+ })
80
+ })
81
+ })
package/src/api/index.ts CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  import { type CoinModuleApi, notSupported } from '@ledgerhq/coin-module-framework/api/index'
5
5
  import coinConfig, { type HypercoreConfig } from '../config'
6
+ import { getBalance } from './getBalance'
7
+ import { listOperations } from './listOperations'
6
8
 
7
9
  export function createApi(config: HypercoreConfig): CoinModuleApi {
8
10
  coinConfig.setCoinConfig(() => ({ ...config, status: { type: 'active' } }))
@@ -13,8 +15,8 @@ export function createApi(config: HypercoreConfig): CoinModuleApi {
13
15
  getBlock: notSupported('getBlock'),
14
16
  call: notSupported('call'),
15
17
  getValidators: notSupported('getValidators'),
16
- getBalance: notSupported('getBalance'),
17
- listOperations: notSupported('listOperations'),
18
+ getBalance,
19
+ listOperations,
18
20
  getStakes: notSupported('getStakes'),
19
21
  getRewards: notSupported('getRewards'),
20
22
  craftTransaction: notSupported('craftTransaction'),
@@ -0,0 +1,161 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import network from '@ledgerhq/live-network'
5
+ import coinConfig from '../config'
6
+ import { listOperations } from './listOperations'
7
+
8
+ jest.mock('@ledgerhq/live-network')
9
+
10
+ const mockNetwork = jest.mocked(network)
11
+
12
+ const ADDRESS = '0xabc'
13
+
14
+ beforeAll(() => {
15
+ coinConfig.setCoinConfig(() => ({
16
+ node: 'https://perps.live.ledger.com/proxy/perps',
17
+ status: { type: 'active' },
18
+ }))
19
+ })
20
+
21
+ afterEach(() => {
22
+ mockNetwork.mockClear()
23
+ })
24
+
25
+ function action(overrides: Record<string, unknown>) {
26
+ return {
27
+ id: 'id-1',
28
+ action: 'fund',
29
+ status: 'SUCCESS',
30
+ summary: { amount: '10.5' },
31
+ createdAt: '2026-01-01T00:00:00.000Z',
32
+ completedAt: '2026-01-01T00:01:00.000Z',
33
+ ...overrides,
34
+ }
35
+ }
36
+
37
+ describe('listOperations', () => {
38
+ it('keeps only fund/withdraw actions and maps them to IN/OUT', async () => {
39
+ mockNetwork.mockResolvedValueOnce({
40
+ status: 200,
41
+ data: {
42
+ total: 3,
43
+ offset: 0,
44
+ limit: 100,
45
+ items: [
46
+ action({ id: 'dep', action: 'fund', summary: { amount: '10.5' } }),
47
+ action({ id: 'wit', action: 'withdraw', summary: { amount: '4' } }),
48
+ action({ id: 'opn', action: 'open', summary: null }),
49
+ ],
50
+ },
51
+ })
52
+
53
+ const { items, next } = await listOperations(ADDRESS, { minHeight: 0 })
54
+
55
+ expect(items).toHaveLength(2)
56
+ expect(items[0]).toMatchObject({
57
+ id: `${ADDRESS}-dep`,
58
+ type: 'IN',
59
+ value: 10_500_000n,
60
+ senders: [ADDRESS],
61
+ recipients: [ADDRESS],
62
+ asset: { type: 'native' },
63
+ })
64
+ expect(items[0].tx).toMatchObject({ hash: 'dep', fees: 0n, failed: false })
65
+ expect(items[1]).toMatchObject({ id: `${ADDRESS}-wit`, type: 'OUT', value: 4_000_000n })
66
+ expect(next).toBeUndefined()
67
+ })
68
+
69
+ it('excludes drafts that never settled (no completedAt)', async () => {
70
+ mockNetwork.mockResolvedValueOnce({
71
+ status: 200,
72
+ data: {
73
+ total: 2,
74
+ offset: 0,
75
+ limit: 100,
76
+ items: [
77
+ action({ id: 'settled', completedAt: '2026-01-01T00:01:00.000Z' }),
78
+ action({ id: 'draft', status: 'CREATED', completedAt: null }),
79
+ ],
80
+ },
81
+ })
82
+
83
+ const { items } = await listOperations(ADDRESS, { minHeight: 0 })
84
+
85
+ expect(items).toHaveLength(1)
86
+ expect(items[0].id).toBe(`${ADDRESS}-settled`)
87
+ })
88
+
89
+ it('marks failed actions', async () => {
90
+ mockNetwork.mockResolvedValueOnce({
91
+ status: 200,
92
+ data: {
93
+ total: 1,
94
+ offset: 0,
95
+ limit: 100,
96
+ items: [action({ id: 'dep', status: 'FAILED' })],
97
+ },
98
+ })
99
+
100
+ const { items } = await listOperations(ADDRESS, { minHeight: 0 })
101
+
102
+ expect(items[0].tx.failed).toBe(true)
103
+ })
104
+
105
+ it('computes the next cursor from the raw page size and total', async () => {
106
+ mockNetwork.mockResolvedValueOnce({
107
+ status: 200,
108
+ data: {
109
+ total: 10,
110
+ offset: 0,
111
+ limit: 2,
112
+ items: [action({ id: 'a' }), action({ id: 'b', action: 'open', summary: null })],
113
+ },
114
+ })
115
+
116
+ const { items, next } = await listOperations(ADDRESS, { minHeight: 0, limit: 2 })
117
+
118
+ // Only one fund/withdraw kept, but the cursor advances by the two raw items consumed.
119
+ expect(items).toHaveLength(1)
120
+ expect(next).toBe('2')
121
+ expect(mockNetwork).toHaveBeenCalledWith(
122
+ expect.objectContaining({
123
+ params: { address: ADDRESS, offset: 0, limit: 2, providerId: 'hyperliquid' },
124
+ })
125
+ )
126
+ })
127
+
128
+ it('passes the cursor as offset on subsequent pages', async () => {
129
+ mockNetwork.mockResolvedValueOnce({
130
+ status: 200,
131
+ data: { total: 10, offset: 2, limit: 2, items: [] },
132
+ })
133
+
134
+ await listOperations(ADDRESS, { minHeight: 0, cursor: '2', limit: 2 })
135
+
136
+ expect(mockNetwork).toHaveBeenCalledWith(
137
+ expect.objectContaining({
138
+ params: { address: ADDRESS, offset: 2, limit: 2, providerId: 'hyperliquid' },
139
+ })
140
+ )
141
+ })
142
+
143
+ it('ignores minHeight and order instead of throwing', async () => {
144
+ mockNetwork.mockResolvedValueOnce({
145
+ status: 200,
146
+ data: { total: 1, offset: 0, limit: 100, items: [action({ id: 'dep' })] },
147
+ })
148
+
149
+ const { items } = await listOperations(ADDRESS, { minHeight: 5, order: 'asc' })
150
+
151
+ expect(items).toHaveLength(1)
152
+ expect(mockNetwork).toHaveBeenCalled()
153
+ })
154
+
155
+ it('rejects an invalid cursor', async () => {
156
+ await expect(listOperations(ADDRESS, { minHeight: 0, cursor: 'not-a-number' })).rejects.toThrow(
157
+ 'invalid cursor'
158
+ )
159
+ expect(mockNetwork).not.toHaveBeenCalled()
160
+ })
161
+ })
@@ -0,0 +1,76 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type {
5
+ ListOperationsOptions,
6
+ Operation,
7
+ Page,
8
+ } from '@ledgerhq/coin-module-framework/api/types'
9
+ import {
10
+ type ActionDto,
11
+ FUND_ACTION,
12
+ USDC_DECIMALS,
13
+ WITHDRAW_ACTION,
14
+ fetchActions,
15
+ } from '../network/yield'
16
+ import { scaleToBigInt } from '../utils'
17
+
18
+ const DEFAULT_LIMIT = 100
19
+
20
+ // Deposits/withdrawals that actually settled — drafts (status CREATED) have no completedAt.
21
+ function isSettledTransfer(action: ActionDto): boolean {
22
+ const isTransfer = action.action === FUND_ACTION || action.action === WITHDRAW_ACTION
23
+ return isTransfer && action.completedAt !== null
24
+ }
25
+
26
+ function toOperation(address: string, dto: ActionDto): Operation {
27
+ const isDeposit = dto.action === FUND_ACTION
28
+ const amount = dto.summary?.amount ?? '0'
29
+ const date = new Date(dto.completedAt ?? dto.createdAt)
30
+
31
+ return {
32
+ id: `${address}-${dto.id}`,
33
+ type: isDeposit ? 'IN' : 'OUT',
34
+ senders: [address],
35
+ recipients: [address],
36
+ value: scaleToBigInt(amount, USDC_DECIMALS),
37
+ asset: { type: 'native' },
38
+ tx: {
39
+ hash: dto.id,
40
+ // HyperCore actions have no block height on this API.
41
+ block: { height: 0, hash: '', time: date },
42
+ fees: 0n,
43
+ date,
44
+ failed: dto.status.toUpperCase() === 'FAILED',
45
+ },
46
+ }
47
+ }
48
+
49
+ // The proxy only supports offset pagination, so the cursor is the numeric offset.
50
+ function parseCursor(cursor: string | undefined): number {
51
+ if (cursor === undefined) return 0
52
+ const offset = Number(cursor)
53
+ if (!Number.isInteger(offset) || offset < 0) {
54
+ throw new Error(`listOperations received an invalid cursor: ${cursor}`)
55
+ }
56
+ return offset
57
+ }
58
+
59
+ export async function listOperations(
60
+ address: string,
61
+ { cursor, limit }: ListOperationsOptions
62
+ ): Promise<Page<Operation>> {
63
+ const offset = parseCursor(cursor)
64
+ const pageLimit = limit ?? DEFAULT_LIMIT
65
+ const page = await fetchActions(address, offset, pageLimit)
66
+
67
+ const items = page.items.filter(isSettledTransfer).map((action) => toOperation(address, action))
68
+
69
+ // Advance by raw items consumed, not the filtered count, so no action is skipped.
70
+ // The cursor is offset-based (volatile if new actions appear between calls); the proxy
71
+ // exposes no stable id/date-based pagination.
72
+ const nextOffset = offset + page.items.length
73
+ const next = nextOffset < page.total ? String(nextOffset) : undefined
74
+
75
+ return { items, next }
76
+ }
@@ -0,0 +1,72 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import network from '@ledgerhq/live-network'
5
+ import coinConfig from '../config'
6
+ import { fetchBalance, fetchActions } from './yield'
7
+
8
+ jest.mock('@ledgerhq/live-network')
9
+
10
+ const mockNetwork = jest.mocked(network)
11
+
12
+ const BASE_CONFIG = { node: 'https://perps.live.ledger.com/proxy/perps' }
13
+
14
+ beforeAll(() => {
15
+ coinConfig.setCoinConfig(() => ({ ...BASE_CONFIG, status: { type: 'active' } }))
16
+ })
17
+
18
+ afterEach(() => {
19
+ mockNetwork.mockClear()
20
+ })
21
+
22
+ const COLLATERAL = {
23
+ symbol: 'USDC',
24
+ name: 'USD Coin',
25
+ network: 'arbitrum',
26
+ decimals: 6,
27
+ address: null,
28
+ logoURI: null,
29
+ }
30
+
31
+ describe('fetchBalance', () => {
32
+ it('calls POST /v1/balances with correct args', async () => {
33
+ const dto = {
34
+ providerId: 'hyperliquid',
35
+ collateral: COLLATERAL,
36
+ accountValue: 100,
37
+ usedMargin: 20,
38
+ availableBalance: 80,
39
+ unrealizedPnl: 5,
40
+ }
41
+ mockNetwork.mockResolvedValueOnce({ status: 200, data: dto })
42
+
43
+ const result = await fetchBalance('0xabc')
44
+
45
+ expect(mockNetwork).toHaveBeenCalledWith(
46
+ expect.objectContaining({
47
+ method: 'POST',
48
+ url: 'https://perps.live.ledger.com/proxy/perps/v1/balances',
49
+ data: { providerId: 'hyperliquid', address: '0xabc' },
50
+ })
51
+ )
52
+ expect(result).toEqual(dto)
53
+ })
54
+ })
55
+
56
+ describe('fetchActions', () => {
57
+ it('calls GET /v1/actions with correct args', async () => {
58
+ const dto = { total: 0, offset: 10, limit: 100, items: [] }
59
+ mockNetwork.mockResolvedValueOnce({ status: 200, data: dto })
60
+
61
+ const result = await fetchActions('0xabc', 10, 100)
62
+
63
+ expect(mockNetwork).toHaveBeenCalledWith(
64
+ expect.objectContaining({
65
+ method: 'GET',
66
+ url: 'https://perps.live.ledger.com/proxy/perps/v1/actions',
67
+ params: { address: '0xabc', offset: 10, limit: 100, providerId: 'hyperliquid' },
68
+ })
69
+ )
70
+ expect(result).toEqual(dto)
71
+ })
72
+ })
@@ -0,0 +1,72 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import network from '@ledgerhq/live-network'
5
+ import coinConfig from '../config'
6
+
7
+ const PROVIDER_ID = 'hyperliquid'
8
+ const getBaseUrl = () => coinConfig.getCoinConfig().node
9
+
10
+ // The collateral is USDC and the proxy omits its decimals.
11
+ export const USDC_DECIMALS = 6
12
+ // A deposit is the `fund` action, a withdrawal the `withdraw` action.
13
+ export const FUND_ACTION = 'fund'
14
+ export const WITHDRAW_ACTION = 'withdraw'
15
+
16
+ export type TokenDto = {
17
+ symbol: string
18
+ network: string
19
+ // The proxy omits these fields for the perps collateral, so they are optional.
20
+ name?: string
21
+ decimals?: number
22
+ address?: string | null
23
+ logoURI?: string | null
24
+ }
25
+
26
+ export type YieldBalanceDto = {
27
+ providerId: string
28
+ collateral: TokenDto
29
+ accountValue: number
30
+ usedMargin: number
31
+ availableBalance: number
32
+ unrealizedPnl: number
33
+ }
34
+
35
+ export async function fetchBalance(address: string): Promise<YieldBalanceDto> {
36
+ const { data } = await network<YieldBalanceDto>({
37
+ method: 'POST',
38
+ url: `${getBaseUrl()}/v1/balances`,
39
+ data: { providerId: PROVIDER_ID, address },
40
+ })
41
+ return data
42
+ }
43
+
44
+ export type ActionDto = {
45
+ id: string
46
+ action: string
47
+ status: string
48
+ // `summary` is null for some action types.
49
+ summary: { amount: string } | null
50
+ createdAt: string
51
+ completedAt: string | null
52
+ }
53
+
54
+ export type ActionsPage = {
55
+ total: number
56
+ offset: number
57
+ limit: number
58
+ items: ActionDto[]
59
+ }
60
+
61
+ export async function fetchActions(
62
+ address: string,
63
+ offset: number,
64
+ limit: number
65
+ ): Promise<ActionsPage> {
66
+ const { data } = await network<ActionsPage>({
67
+ method: 'GET',
68
+ url: `${getBaseUrl()}/v1/actions`,
69
+ params: { address, offset, limit, providerId: PROVIDER_ID },
70
+ })
71
+ return data
72
+ }
@@ -0,0 +1,20 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { scaleToBigInt } from '.'
5
+
6
+ describe('scaleToBigInt', () => {
7
+ it.each([
8
+ ['557.13644', 6, 557_136_440n],
9
+ [557.13644, 6, 557_136_440n],
10
+ ['0', 6, 0n],
11
+ [0, 6, 0n],
12
+ ['10', 6, 10_000_000n],
13
+ ['0.000001', 6, 1n],
14
+ // extra fractional digits are rounded (framework uses BigNumber.integerValue)
15
+ ['1.2345678', 6, 1_234_568n],
16
+ ['-3.5', 6, -3_500_000n],
17
+ ])('scales %s with %i decimals', (amount, magnitude, expected) => {
18
+ expect(scaleToBigInt(amount, magnitude)).toBe(expected)
19
+ })
20
+ })
@@ -0,0 +1,9 @@
1
+ // SPDX-FileCopyrightText: © 2024 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { parseCurrencyUnit } from '@ledgerhq/coin-module-framework/currencies'
5
+
6
+ // Convert a decimal amount into its integer base-unit representation.
7
+ export function scaleToBigInt(amount: string | number, magnitude: number): bigint {
8
+ return BigInt(parseCurrencyUnit({ name: '', code: '', magnitude }, String(amount)).toFixed())
9
+ }