@ledgerhq/coin-hypercore 0.2.1 → 0.4.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/CHANGELOG.md +12 -0
- package/lib/api/getBalance.d.ts +3 -0
- package/lib/api/getBalance.d.ts.map +1 -0
- package/lib/api/getBalance.js +21 -0
- package/lib/api/getBalance.js.map +1 -0
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +4 -2
- package/lib/api/index.js.map +1 -1
- package/lib/api/listOperations.d.ts +3 -0
- package/lib/api/listOperations.d.ts.map +1 -0
- package/lib/api/listOperations.js +64 -0
- package/lib/api/listOperations.js.map +1 -0
- package/lib/network/yield.d.ts +38 -0
- package/lib/network/yield.d.ts.map +1 -0
- package/lib/network/yield.js +36 -0
- package/lib/network/yield.js.map +1 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +11 -0
- package/lib/utils/index.js.map +1 -0
- package/lib-es/api/getBalance.d.ts +3 -0
- package/lib-es/api/getBalance.d.ts.map +1 -0
- package/lib-es/api/getBalance.js +18 -0
- package/lib-es/api/getBalance.js.map +1 -0
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +4 -2
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/api/listOperations.d.ts +3 -0
- package/lib-es/api/listOperations.d.ts.map +1 -0
- package/lib-es/api/listOperations.js +61 -0
- package/lib-es/api/listOperations.js.map +1 -0
- package/lib-es/network/yield.d.ts +38 -0
- package/lib-es/network/yield.d.ts.map +1 -0
- package/lib-es/network/yield.js +28 -0
- package/lib-es/network/yield.js.map +1 -0
- package/lib-es/utils/index.d.ts +2 -0
- package/lib-es/utils/index.d.ts.map +1 -0
- package/lib-es/utils/index.js +8 -0
- package/lib-es/utils/index.js.map +1 -0
- package/package.json +2 -2
- package/src/api/getBalance.test.ts +66 -0
- package/src/api/getBalance.ts +21 -0
- package/src/api/index.integ.test.ts +54 -2
- package/src/api/index.ts +4 -2
- package/src/api/listOperations.test.ts +163 -0
- package/src/api/listOperations.ts +84 -0
- package/src/network/yield.test.ts +72 -0
- package/src/network/yield.ts +72 -0
- package/src/utils/index.test.ts +20 -0
- package/src/utils/index.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ledgerhq/coin-hypercore
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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
|
|
8
|
+
|
|
9
|
+
## 0.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#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
|
|
14
|
+
|
|
3
15
|
## 0.2.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -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"}
|
package/lib/api/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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:
|
|
20
|
-
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'),
|
package/lib/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":";AAAA,4CAA4C;AAC5C,sCAAsC;;;;;
|
|
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, { minHeight, cursor, limit, order }: 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,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,qBAAqB,GACzD,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAsB1B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
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, { minHeight, cursor, limit, order }) {
|
|
46
|
+
// The proxy has no block height and always returns actions newest-first (descending).
|
|
47
|
+
if (minHeight > 0) {
|
|
48
|
+
throw new Error('listOperations with a minHeight is not supported');
|
|
49
|
+
}
|
|
50
|
+
if (order === 'asc') {
|
|
51
|
+
throw new Error('listOperations with an ascending order is not supported');
|
|
52
|
+
}
|
|
53
|
+
const offset = parseCursor(cursor);
|
|
54
|
+
const pageLimit = limit ?? DEFAULT_LIMIT;
|
|
55
|
+
const page = await (0, yield_1.fetchActions)(address, offset, pageLimit);
|
|
56
|
+
const items = page.items.filter(isSettledTransfer).map((action) => toOperation(address, action));
|
|
57
|
+
// Advance by raw items consumed, not the filtered count, so no action is skipped.
|
|
58
|
+
// The cursor is offset-based (volatile if new actions appear between calls); the proxy
|
|
59
|
+
// exposes no stable id/date-based pagination.
|
|
60
|
+
const nextOffset = offset + page.items.length;
|
|
61
|
+
const next = nextOffset < page.total ? String(nextOffset) : undefined;
|
|
62
|
+
return { items, next };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=listOperations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listOperations.js","sourceRoot":"","sources":["../../src/api/listOperations.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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;
|
|
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-es/api/index.js
CHANGED
|
@@ -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
|
|
14
|
-
listOperations
|
|
15
|
+
getBalance,
|
|
16
|
+
listOperations,
|
|
15
17
|
getStakes: notSupported('getStakes'),
|
|
16
18
|
getRewards: notSupported('getRewards'),
|
|
17
19
|
craftTransaction: notSupported('craftTransaction'),
|
package/lib-es/api/index.js.map
CHANGED
|
@@ -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;
|
|
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, { minHeight, cursor, limit, order }: 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,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,qBAAqB,GACzD,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAsB1B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
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, { minHeight, cursor, limit, order }) {
|
|
43
|
+
// The proxy has no block height and always returns actions newest-first (descending).
|
|
44
|
+
if (minHeight > 0) {
|
|
45
|
+
throw new Error('listOperations with a minHeight is not supported');
|
|
46
|
+
}
|
|
47
|
+
if (order === 'asc') {
|
|
48
|
+
throw new Error('listOperations with an ascending order is not supported');
|
|
49
|
+
}
|
|
50
|
+
const offset = parseCursor(cursor);
|
|
51
|
+
const pageLimit = limit ?? DEFAULT_LIMIT;
|
|
52
|
+
const page = await fetchActions(address, offset, pageLimit);
|
|
53
|
+
const items = page.items.filter(isSettledTransfer).map((action) => toOperation(address, action));
|
|
54
|
+
// Advance by raw items consumed, not the filtered count, so no action is skipped.
|
|
55
|
+
// The cursor is offset-based (volatile if new actions appear between calls); the proxy
|
|
56
|
+
// exposes no stable id/date-based pagination.
|
|
57
|
+
const nextOffset = offset + page.items.length;
|
|
58
|
+
const next = nextOffset < page.total ? String(nextOffset) : undefined;
|
|
59
|
+
return { items, next };
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=listOperations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listOperations.js","sourceRoot":"","sources":["../../src/api/listOperations.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 @@
|
|
|
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.
|
|
3
|
+
"version": "0.4.0",
|
|
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.
|
|
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,53 @@ 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('rejects a non-zero minHeight', async () => {
|
|
77
|
+
await expect(api.listOperations(ADDRESS, { minHeight: 1 })).rejects.toThrow(
|
|
78
|
+
'minHeight is not supported'
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
})
|
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
|
|
17
|
-
listOperations
|
|
18
|
+
getBalance,
|
|
19
|
+
listOperations,
|
|
18
20
|
getStakes: notSupported('getStakes'),
|
|
19
21
|
getRewards: notSupported('getRewards'),
|
|
20
22
|
craftTransaction: notSupported('craftTransaction'),
|
|
@@ -0,0 +1,163 @@
|
|
|
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('rejects a non-zero minHeight', async () => {
|
|
144
|
+
await expect(listOperations(ADDRESS, { minHeight: 5 })).rejects.toThrow(
|
|
145
|
+
'minHeight is not supported'
|
|
146
|
+
)
|
|
147
|
+
expect(mockNetwork).not.toHaveBeenCalled()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('rejects an ascending order', async () => {
|
|
151
|
+
await expect(listOperations(ADDRESS, { minHeight: 0, order: 'asc' })).rejects.toThrow(
|
|
152
|
+
'ascending order is not supported'
|
|
153
|
+
)
|
|
154
|
+
expect(mockNetwork).not.toHaveBeenCalled()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('rejects an invalid cursor', async () => {
|
|
158
|
+
await expect(listOperations(ADDRESS, { minHeight: 0, cursor: 'not-a-number' })).rejects.toThrow(
|
|
159
|
+
'invalid cursor'
|
|
160
|
+
)
|
|
161
|
+
expect(mockNetwork).not.toHaveBeenCalled()
|
|
162
|
+
})
|
|
163
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
{ minHeight, cursor, limit, order }: ListOperationsOptions
|
|
62
|
+
): Promise<Page<Operation>> {
|
|
63
|
+
// The proxy has no block height and always returns actions newest-first (descending).
|
|
64
|
+
if (minHeight > 0) {
|
|
65
|
+
throw new Error('listOperations with a minHeight is not supported')
|
|
66
|
+
}
|
|
67
|
+
if (order === 'asc') {
|
|
68
|
+
throw new Error('listOperations with an ascending order is not supported')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const offset = parseCursor(cursor)
|
|
72
|
+
const pageLimit = limit ?? DEFAULT_LIMIT
|
|
73
|
+
const page = await fetchActions(address, offset, pageLimit)
|
|
74
|
+
|
|
75
|
+
const items = page.items.filter(isSettledTransfer).map((action) => toOperation(address, action))
|
|
76
|
+
|
|
77
|
+
// Advance by raw items consumed, not the filtered count, so no action is skipped.
|
|
78
|
+
// The cursor is offset-based (volatile if new actions appear between calls); the proxy
|
|
79
|
+
// exposes no stable id/date-based pagination.
|
|
80
|
+
const nextOffset = offset + page.items.length
|
|
81
|
+
const next = nextOffset < page.total ? String(nextOffset) : undefined
|
|
82
|
+
|
|
83
|
+
return { items, next }
|
|
84
|
+
}
|
|
@@ -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
|
+
}
|