@ledgerhq/coin-sui 0.10.0-nightly.1 → 0.10.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +26 -14
- package/lib/bridge/buildTransaction.d.ts +1 -1
- package/lib/bridge/buildTransaction.d.ts.map +1 -1
- package/lib/bridge/buildTransaction.integration.test.js +0 -12
- package/lib/bridge/buildTransaction.integration.test.js.map +1 -1
- package/lib/bridge/buildTransaction.js +4 -8
- package/lib/bridge/buildTransaction.js.map +1 -1
- package/lib/bridge/buildTransaction.test.js +10 -14
- package/lib/bridge/buildTransaction.test.js.map +1 -1
- package/lib/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib/bridge/getFeesForTransaction.js +3 -2
- package/lib/bridge/getFeesForTransaction.js.map +1 -1
- package/lib/logic/craftTransaction.js +1 -1
- package/lib/logic/craftTransaction.js.map +1 -1
- package/lib/network/sdk.d.ts +3 -18
- package/lib/network/sdk.d.ts.map +1 -1
- package/lib/network/sdk.js +51 -64
- package/lib/network/sdk.js.map +1 -1
- package/lib/network/sdk.test.js +65 -148
- package/lib/network/sdk.test.js.map +1 -1
- package/lib-es/bridge/buildTransaction.d.ts +1 -1
- package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
- package/lib-es/bridge/buildTransaction.integration.test.js +0 -12
- package/lib-es/bridge/buildTransaction.integration.test.js.map +1 -1
- package/lib-es/bridge/buildTransaction.js +5 -9
- package/lib-es/bridge/buildTransaction.js.map +1 -1
- package/lib-es/bridge/buildTransaction.test.js +10 -14
- package/lib-es/bridge/buildTransaction.test.js.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.js +3 -2
- package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
- package/lib-es/logic/craftTransaction.js +1 -1
- package/lib-es/logic/craftTransaction.js.map +1 -1
- package/lib-es/network/sdk.d.ts +3 -18
- package/lib-es/network/sdk.d.ts.map +1 -1
- package/lib-es/network/sdk.js +48 -61
- package/lib-es/network/sdk.js.map +1 -1
- package/lib-es/network/sdk.test.js +65 -148
- package/lib-es/network/sdk.test.js.map +1 -1
- package/package.json +8 -8
- package/src/bridge/buildTransaction.integration.test.ts +0 -13
- package/src/bridge/buildTransaction.test.ts +25 -25
- package/src/bridge/buildTransaction.ts +5 -10
- package/src/bridge/getFeesForTransaction.ts +3 -2
- package/src/logic/craftTransaction.ts +1 -1
- package/src/network/sdk.test.ts +77 -186
- package/src/network/sdk.ts +62 -82
package/src/network/sdk.test.ts
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
// Move all jest.mock calls to the very top
|
|
2
|
+
jest.mock("../config", () => ({
|
|
3
|
+
__esModule: true,
|
|
4
|
+
default: {
|
|
5
|
+
getCoinConfig: jest.fn(() => ({ node: { url: "http://test.com" } })),
|
|
6
|
+
setCoinConfig: jest.fn(),
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock("../utils", () => ({
|
|
11
|
+
ensureAddressFormat: jest.fn((addr: string) => addr),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock("@ledgerhq/live-network/cache", () => ({
|
|
15
|
+
makeLRUCache: jest.fn(() => jest.fn()),
|
|
16
|
+
minutes: jest.fn(() => 60000),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock("@ledgerhq/logs", () => ({
|
|
20
|
+
log: jest.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
jest.mock("@mysten/sui/client", () => {
|
|
24
|
+
const mockClient = {
|
|
25
|
+
queryTransactionBlocks: jest.fn(),
|
|
26
|
+
getBalance: jest.fn(),
|
|
27
|
+
getLatestCheckpointSequenceNumber: jest.fn(),
|
|
28
|
+
getCheckpoint: jest.fn(),
|
|
29
|
+
dryRunTransactionBlock: jest.fn(),
|
|
30
|
+
executeTransactionBlock: jest.fn(),
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
SuiClient: jest.fn().mockImplementation(() => mockClient),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
jest.mock("@mysten/sui/transactions", () => ({
|
|
38
|
+
Transaction: jest.fn().mockImplementation(() => ({
|
|
39
|
+
setSender: jest.fn().mockReturnThis(),
|
|
40
|
+
splitCoins: jest.fn().mockReturnValue([{ id: "coin1" }]),
|
|
41
|
+
transferObjects: jest.fn().mockReturnThis(),
|
|
42
|
+
gas: { id: "gas" },
|
|
43
|
+
build: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3])),
|
|
44
|
+
})),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
// Now import after mocks
|
|
1
48
|
import * as sdk from "./sdk";
|
|
2
49
|
import coinConfig from "../config";
|
|
3
50
|
|
|
@@ -168,16 +215,6 @@ const mockTransaction = {
|
|
|
168
215
|
|
|
169
216
|
const mockApi = new SuiClient({ url: "mock" }) as jest.Mocked<SuiClient>;
|
|
170
217
|
|
|
171
|
-
// Helper function to generate mock coins from an array of balances
|
|
172
|
-
const createMockCoins = (balances: string[]): any[] => {
|
|
173
|
-
return balances.map((balance, index) => ({
|
|
174
|
-
coinObjectId: `0xcoin${index + 1}`,
|
|
175
|
-
balance,
|
|
176
|
-
digest: `0xdigest${index + 1}`,
|
|
177
|
-
version: "1",
|
|
178
|
-
}));
|
|
179
|
-
};
|
|
180
|
-
|
|
181
218
|
beforeAll(() => {
|
|
182
219
|
coinConfig.setCoinConfig(() => ({
|
|
183
220
|
status: {
|
|
@@ -283,9 +320,9 @@ describe("SDK Functions", () => {
|
|
|
283
320
|
});
|
|
284
321
|
|
|
285
322
|
test("getOperationDate should return correct date", () => {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
323
|
+
expect(sdk.getOperationDate(mockTransaction as SuiTransactionBlockResponse)).toEqual(
|
|
324
|
+
new Date("2025-03-18T10:40:54.878Z"),
|
|
325
|
+
);
|
|
289
326
|
});
|
|
290
327
|
|
|
291
328
|
test("getOperationCoinType should extract token coin type", () => {
|
|
@@ -497,6 +534,33 @@ describe("SDK Functions", () => {
|
|
|
497
534
|
expect(info).toHaveProperty("fees");
|
|
498
535
|
});
|
|
499
536
|
|
|
537
|
+
test("getCoinObjectIds should return array of object IDs for token transactions", async () => {
|
|
538
|
+
const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
|
|
539
|
+
const transaction = {
|
|
540
|
+
mode: "token.send" as const,
|
|
541
|
+
coinType: "0x123::test::TOKEN",
|
|
542
|
+
amount: new BigNumber(100),
|
|
543
|
+
recipient: "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164",
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const coinObjectIds = await sdk.getCoinObjectIds(address, transaction);
|
|
547
|
+
expect(Array.isArray(coinObjectIds)).toBe(true);
|
|
548
|
+
expect(coinObjectIds).toContain("0xtest_coin_object_id");
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("getCoinObjectIds should return null for SUI transactions", async () => {
|
|
552
|
+
const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
|
|
553
|
+
const transaction = {
|
|
554
|
+
mode: "send" as const,
|
|
555
|
+
coinType: sdk.DEFAULT_COIN_TYPE,
|
|
556
|
+
amount: new BigNumber(100),
|
|
557
|
+
recipient: "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164",
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const coinObjectIds = await sdk.getCoinObjectIds(address, transaction);
|
|
561
|
+
expect(coinObjectIds).toBeNull();
|
|
562
|
+
});
|
|
563
|
+
|
|
500
564
|
test("createTransaction should build a transaction", async () => {
|
|
501
565
|
const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
|
|
502
566
|
const transaction = {
|
|
@@ -1467,176 +1531,3 @@ describe("filterOperations", () => {
|
|
|
1467
1531
|
});
|
|
1468
1532
|
});
|
|
1469
1533
|
});
|
|
1470
|
-
|
|
1471
|
-
describe("getCoinsForAmount", () => {
|
|
1472
|
-
const mockAddress = "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164";
|
|
1473
|
-
const mockCoinType = "0x2::sui::SUI";
|
|
1474
|
-
|
|
1475
|
-
beforeEach(() => {
|
|
1476
|
-
mockApi.getCoins.mockReset();
|
|
1477
|
-
});
|
|
1478
|
-
|
|
1479
|
-
describe("basic functionality", () => {
|
|
1480
|
-
test("handles single coin scenarios", async () => {
|
|
1481
|
-
const sufficientCoins = createMockCoins(["1000"]);
|
|
1482
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: sufficientCoins, hasNextPage: false });
|
|
1483
|
-
|
|
1484
|
-
let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1485
|
-
expect(result).toHaveLength(1);
|
|
1486
|
-
expect(result[0].balance).toBe("1000");
|
|
1487
|
-
|
|
1488
|
-
const insufficientCoins = createMockCoins(["500"]);
|
|
1489
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: insufficientCoins, hasNextPage: false });
|
|
1490
|
-
|
|
1491
|
-
result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1492
|
-
expect(result).toHaveLength(1);
|
|
1493
|
-
expect(result[0].balance).toBe("500");
|
|
1494
|
-
});
|
|
1495
|
-
|
|
1496
|
-
test("selects minimum coins needed", async () => {
|
|
1497
|
-
const exactMatchCoins = createMockCoins(["600", "400", "300"]);
|
|
1498
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: exactMatchCoins, hasNextPage: false });
|
|
1499
|
-
|
|
1500
|
-
let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1501
|
-
expect(result).toHaveLength(2);
|
|
1502
|
-
expect(result[0].balance).toBe("600");
|
|
1503
|
-
expect(result[1].balance).toBe("400");
|
|
1504
|
-
|
|
1505
|
-
const exceedCoins = createMockCoins(["800", "400", "200"]);
|
|
1506
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: exceedCoins, hasNextPage: false });
|
|
1507
|
-
|
|
1508
|
-
result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1509
|
-
expect(result).toHaveLength(2);
|
|
1510
|
-
expect(result[0].balance).toBe("800");
|
|
1511
|
-
expect(result[1].balance).toBe("400");
|
|
1512
|
-
});
|
|
1513
|
-
|
|
1514
|
-
test("handles edge cases", async () => {
|
|
1515
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: [], hasNextPage: false });
|
|
1516
|
-
|
|
1517
|
-
let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1518
|
-
expect(result).toHaveLength(0);
|
|
1519
|
-
|
|
1520
|
-
const coins = createMockCoins(["1000"]);
|
|
1521
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: coins, hasNextPage: false });
|
|
1522
|
-
|
|
1523
|
-
result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 0);
|
|
1524
|
-
expect(result).toHaveLength(0);
|
|
1525
|
-
});
|
|
1526
|
-
});
|
|
1527
|
-
|
|
1528
|
-
describe("sorting and filtering", () => {
|
|
1529
|
-
test("filters zero balance coins", async () => {
|
|
1530
|
-
const mockCoins = createMockCoins(["1000", "500"]);
|
|
1531
|
-
mockCoins.splice(1, 0, createMockCoins(["0"])[0]);
|
|
1532
|
-
mockCoins.push({ coinObjectId: "0xcoin4", balance: "0", digest: "0xdigest4", version: "1" });
|
|
1533
|
-
|
|
1534
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: mockCoins, hasNextPage: false });
|
|
1535
|
-
|
|
1536
|
-
const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1537
|
-
|
|
1538
|
-
expect(result).toHaveLength(1);
|
|
1539
|
-
expect(result[0].balance).toBe("1000");
|
|
1540
|
-
expect(result.every(coin => parseInt(coin.balance) > 0)).toBe(true);
|
|
1541
|
-
});
|
|
1542
|
-
|
|
1543
|
-
test("sorts and optimizes coin selection", async () => {
|
|
1544
|
-
const unsortedCoins = createMockCoins(["100", "800", "300", "500"]);
|
|
1545
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: unsortedCoins, hasNextPage: false });
|
|
1546
|
-
|
|
1547
|
-
let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1548
|
-
expect(result).toHaveLength(2);
|
|
1549
|
-
expect(result[0].balance).toBe("800");
|
|
1550
|
-
expect(result[1].balance).toBe("500");
|
|
1551
|
-
|
|
1552
|
-
const mixedCoins = createMockCoins(["200", "800", "400"]);
|
|
1553
|
-
mixedCoins.unshift(createMockCoins(["0"])[0]);
|
|
1554
|
-
mixedCoins.splice(2, 0, createMockCoins(["0"])[0]);
|
|
1555
|
-
|
|
1556
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: mixedCoins, hasNextPage: false });
|
|
1557
|
-
|
|
1558
|
-
result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1559
|
-
expect(result).toHaveLength(2);
|
|
1560
|
-
expect(result[0].balance).toBe("800");
|
|
1561
|
-
expect(result[1].balance).toBe("400");
|
|
1562
|
-
expect(result.every(coin => parseInt(coin.balance) > 0)).toBe(true);
|
|
1563
|
-
});
|
|
1564
|
-
|
|
1565
|
-
test("handles all zero balance coins", async () => {
|
|
1566
|
-
const mockCoins = createMockCoins(["0", "0", "0"]);
|
|
1567
|
-
mockApi.getCoins.mockResolvedValueOnce({ data: mockCoins, hasNextPage: false });
|
|
1568
|
-
|
|
1569
|
-
const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1570
|
-
|
|
1571
|
-
expect(result).toHaveLength(0);
|
|
1572
|
-
expect(result).toEqual([]);
|
|
1573
|
-
});
|
|
1574
|
-
});
|
|
1575
|
-
|
|
1576
|
-
describe("pagination", () => {
|
|
1577
|
-
test("handles single page scenarios", async () => {
|
|
1578
|
-
const mockCoins = createMockCoins(["800", "400", "300"]);
|
|
1579
|
-
mockApi.getCoins.mockResolvedValueOnce({
|
|
1580
|
-
data: mockCoins,
|
|
1581
|
-
hasNextPage: true,
|
|
1582
|
-
nextCursor: "cursor1",
|
|
1583
|
-
});
|
|
1584
|
-
|
|
1585
|
-
const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1586
|
-
|
|
1587
|
-
expect(result).toHaveLength(2);
|
|
1588
|
-
expect(result[0].balance).toBe("800");
|
|
1589
|
-
expect(result[1].balance).toBe("400");
|
|
1590
|
-
expect(mockApi.getCoins).toHaveBeenCalledTimes(1);
|
|
1591
|
-
});
|
|
1592
|
-
|
|
1593
|
-
test("handles multi-page scenarios", async () => {
|
|
1594
|
-
const firstPageCoins = createMockCoins(["300", "200"]);
|
|
1595
|
-
const secondPageCoins = createMockCoins(["600", "400", "100"]);
|
|
1596
|
-
|
|
1597
|
-
mockApi.getCoins
|
|
1598
|
-
.mockResolvedValueOnce({
|
|
1599
|
-
data: firstPageCoins,
|
|
1600
|
-
hasNextPage: true,
|
|
1601
|
-
nextCursor: "cursor1",
|
|
1602
|
-
})
|
|
1603
|
-
.mockResolvedValueOnce({
|
|
1604
|
-
data: secondPageCoins,
|
|
1605
|
-
hasNextPage: false,
|
|
1606
|
-
});
|
|
1607
|
-
|
|
1608
|
-
const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1609
|
-
|
|
1610
|
-
expect(result).toHaveLength(3);
|
|
1611
|
-
expect(result[0].balance).toBe("300");
|
|
1612
|
-
expect(result[1].balance).toBe("200");
|
|
1613
|
-
expect(result[2].balance).toBe("600");
|
|
1614
|
-
expect(mockApi.getCoins).toHaveBeenCalledTimes(2);
|
|
1615
|
-
});
|
|
1616
|
-
|
|
1617
|
-
test("handles insufficient funds across pages", async () => {
|
|
1618
|
-
const firstPageCoins = createMockCoins(["300", "200"]);
|
|
1619
|
-
const secondPageCoins = createMockCoins(["200", "100"]);
|
|
1620
|
-
|
|
1621
|
-
mockApi.getCoins
|
|
1622
|
-
.mockResolvedValueOnce({
|
|
1623
|
-
data: firstPageCoins,
|
|
1624
|
-
hasNextPage: true,
|
|
1625
|
-
nextCursor: "cursor1",
|
|
1626
|
-
})
|
|
1627
|
-
.mockResolvedValueOnce({
|
|
1628
|
-
data: secondPageCoins,
|
|
1629
|
-
hasNextPage: false,
|
|
1630
|
-
});
|
|
1631
|
-
|
|
1632
|
-
const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
|
|
1633
|
-
|
|
1634
|
-
expect(result).toHaveLength(4);
|
|
1635
|
-
expect(result[0].balance).toBe("300");
|
|
1636
|
-
expect(result[1].balance).toBe("200");
|
|
1637
|
-
expect(result[2].balance).toBe("200");
|
|
1638
|
-
expect(result[3].balance).toBe("100");
|
|
1639
|
-
expect(mockApi.getCoins).toHaveBeenCalledTimes(2);
|
|
1640
|
-
});
|
|
1641
|
-
});
|
|
1642
|
-
});
|
package/src/network/sdk.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BalanceChange,
|
|
3
2
|
Checkpoint,
|
|
4
3
|
ExecuteTransactionBlockParams,
|
|
5
4
|
PaginatedTransactionResponse,
|
|
6
|
-
QueryTransactionBlocksParams,
|
|
7
5
|
SuiCallArg,
|
|
8
6
|
SuiClient,
|
|
9
|
-
SuiHTTPTransport,
|
|
10
7
|
SuiTransactionBlockResponse,
|
|
8
|
+
TransactionBlockData,
|
|
9
|
+
SuiHTTPTransport,
|
|
10
|
+
TransactionEffects,
|
|
11
|
+
QueryTransactionBlocksParams,
|
|
12
|
+
BalanceChange,
|
|
11
13
|
SuiTransactionBlockResponseOptions,
|
|
12
14
|
DelegatedStake,
|
|
13
15
|
StakeObject,
|
|
14
|
-
TransactionBlockData,
|
|
15
|
-
TransactionEffects,
|
|
16
16
|
} from "@mysten/sui/client";
|
|
17
17
|
import { Transaction } from "@mysten/sui/transactions";
|
|
18
18
|
import { BigNumber } from "bignumber.js";
|
|
@@ -481,79 +481,75 @@ const getTotalGasUsed = (effects?: TransactionEffects | null): bigint => {
|
|
|
481
481
|
);
|
|
482
482
|
};
|
|
483
483
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
export const getCoinsForAmount = async (
|
|
489
|
-
api: SuiClient,
|
|
490
|
-
address: string,
|
|
491
|
-
coinType: string,
|
|
492
|
-
requiredAmount: number,
|
|
493
|
-
) => {
|
|
494
|
-
const coins = [];
|
|
495
|
-
let cursor = null;
|
|
496
|
-
let hasNextPage = true;
|
|
497
|
-
let totalBalance = 0;
|
|
498
|
-
|
|
499
|
-
while (hasNextPage && totalBalance < requiredAmount) {
|
|
500
|
-
const response = await api.getCoins({
|
|
501
|
-
owner: address,
|
|
502
|
-
coinType,
|
|
503
|
-
cursor,
|
|
504
|
-
});
|
|
484
|
+
const FALLBACK_GAS_BUDGET = {
|
|
485
|
+
SUI_TRANSFER: "3976000",
|
|
486
|
+
TOKEN_TRANSFER: "4461792",
|
|
487
|
+
};
|
|
505
488
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
489
|
+
export const paymentInfo = async (sender: string, fakeTransaction: TransactionType) =>
|
|
490
|
+
withApi(async api => {
|
|
491
|
+
const tx = new Transaction();
|
|
492
|
+
tx.setSender(ensureAddressFormat(sender));
|
|
493
|
+
const coinObjects = await getCoinObjectIds(sender, fakeTransaction);
|
|
494
|
+
|
|
495
|
+
const [coin] = tx.splitCoins(Array.isArray(coinObjects) ? coinObjects[0] : tx.gas, [
|
|
496
|
+
fakeTransaction.amount.toNumber(),
|
|
497
|
+
]);
|
|
498
|
+
tx.transferObjects([coin], fakeTransaction.recipient);
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
const txb = await tx.build({ client: api });
|
|
502
|
+
const dryRunTxResponse = await api.dryRunTransactionBlock({ transactionBlock: txb });
|
|
503
|
+
const fees = getTotalGasUsed(dryRunTxResponse.effects);
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
gasBudget: dryRunTxResponse.input.gasData.budget,
|
|
507
|
+
totalGasUsed: fees,
|
|
508
|
+
fees,
|
|
509
|
+
};
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.warn("Fee estimation failed:", error);
|
|
512
|
+
// If dry run fails return a reasonable default gas budget as fallback
|
|
513
|
+
return {
|
|
514
|
+
gasBudget: Array.isArray(coinObjects)
|
|
515
|
+
? FALLBACK_GAS_BUDGET.TOKEN_TRANSFER
|
|
516
|
+
: FALLBACK_GAS_BUDGET.SUI_TRANSFER,
|
|
517
|
+
totalGasUsed: BigInt(1000000),
|
|
518
|
+
fees: BigInt(1000000),
|
|
519
|
+
};
|
|
518
520
|
}
|
|
519
|
-
|
|
521
|
+
});
|
|
520
522
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
523
|
+
export const getCoinObjectIds = async (
|
|
524
|
+
address: string,
|
|
525
|
+
transaction: CreateExtrinsicArg | TransactionType,
|
|
526
|
+
) =>
|
|
527
|
+
withApi(async api => {
|
|
528
|
+
const coinObjectId = null;
|
|
524
529
|
|
|
525
|
-
|
|
526
|
-
|
|
530
|
+
if (transaction.coinType !== DEFAULT_COIN_TYPE) {
|
|
531
|
+
const tokenInfo = await api.getCoins({
|
|
532
|
+
owner: address,
|
|
533
|
+
coinType: transaction.coinType,
|
|
534
|
+
});
|
|
535
|
+
return tokenInfo.data.map(coin => coin.coinObjectId);
|
|
536
|
+
}
|
|
537
|
+
return coinObjectId;
|
|
538
|
+
});
|
|
527
539
|
|
|
528
|
-
/**
|
|
529
|
-
* Creates a Sui transaction block for transferring coins.
|
|
530
|
-
*
|
|
531
|
-
* @param address - The sender's address
|
|
532
|
-
* @param transaction - The transaction details including recipient, amount, and coin type
|
|
533
|
-
* @returns Promise<TransactionBlock> - A built transaction block ready for execution
|
|
534
|
-
*
|
|
535
|
-
*/
|
|
536
540
|
export const createTransaction = async (address: string, transaction: CreateExtrinsicArg) =>
|
|
537
541
|
withApi(async api => {
|
|
538
542
|
const tx = new Transaction();
|
|
539
543
|
tx.setSender(ensureAddressFormat(address));
|
|
540
544
|
|
|
541
|
-
|
|
542
|
-
const requiredAmount = transaction.amount.toNumber();
|
|
543
|
-
|
|
544
|
-
const coins = await getCoinsForAmount(api, address, transaction.coinType, requiredAmount);
|
|
545
|
+
const coinObjects = await getCoinObjectIds(address, transaction);
|
|
545
546
|
|
|
546
|
-
|
|
547
|
-
|
|
547
|
+
if (Array.isArray(coinObjects) && transaction.coinType !== DEFAULT_COIN_TYPE) {
|
|
548
|
+
const coins = coinObjects.map(coinId => tx.object(coinId));
|
|
549
|
+
if (coins.length > 1) {
|
|
550
|
+
tx.mergeCoins(coins[0], coins.slice(1));
|
|
548
551
|
}
|
|
549
|
-
|
|
550
|
-
const coinObjects = coins.map(coin => tx.object(coin.coinObjectId));
|
|
551
|
-
|
|
552
|
-
if (coinObjects.length > 1) {
|
|
553
|
-
tx.mergeCoins(coinObjects[0], coinObjects.slice(1));
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const [coin] = tx.splitCoins(coinObjects[0], [transaction.amount.toNumber()]);
|
|
552
|
+
const [coin] = tx.splitCoins(coins[0], [transaction.amount.toNumber()]);
|
|
557
553
|
tx.transferObjects([coin], transaction.recipient);
|
|
558
554
|
} else {
|
|
559
555
|
const [coin] = tx.splitCoins(tx.gas, [transaction.amount.toNumber()]);
|
|
@@ -563,22 +559,6 @@ export const createTransaction = async (address: string, transaction: CreateExtr
|
|
|
563
559
|
return tx.build({ client: api });
|
|
564
560
|
});
|
|
565
561
|
|
|
566
|
-
/**
|
|
567
|
-
* Performs a dry run of a transaction to estimate gas costs and fees
|
|
568
|
-
*/
|
|
569
|
-
export const paymentInfo = async (sender: string, fakeTransaction: TransactionType) =>
|
|
570
|
-
withApi(async api => {
|
|
571
|
-
const txb = await createTransaction(sender, fakeTransaction);
|
|
572
|
-
const dryRunTxResponse = await api.dryRunTransactionBlock({ transactionBlock: txb });
|
|
573
|
-
const fees = getTotalGasUsed(dryRunTxResponse.effects);
|
|
574
|
-
|
|
575
|
-
return {
|
|
576
|
-
gasBudget: dryRunTxResponse.input.gasData.budget,
|
|
577
|
-
totalGasUsed: fees,
|
|
578
|
-
fees,
|
|
579
|
-
};
|
|
580
|
-
});
|
|
581
|
-
|
|
582
562
|
export const executeTransactionBlock = async (params: ExecuteTransactionBlockParams) =>
|
|
583
563
|
withApi(async api => {
|
|
584
564
|
return api.executeTransactionBlock(params);
|