@xchainjs/xchain-sui 0.1.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.
@@ -0,0 +1,30 @@
1
+ import { AssetInfo, BaseXChainClient, Fees, Network, PreparedTx, TxHash, TxHistoryParams } from '@xchainjs/xchain-client';
2
+ import { Address, TokenAsset } from '@xchainjs/xchain-util';
3
+ import { Balance, SUIClientParams, Tx, TxParams, TxsPage } from './types';
4
+ export declare class Client extends BaseXChainClient {
5
+ private explorerProviders;
6
+ private suiClient;
7
+ private clientUrls?;
8
+ constructor(params?: SUIClientParams);
9
+ setNetwork(network: Network): void;
10
+ getAssetInfo(): AssetInfo;
11
+ getExplorerUrl(): string;
12
+ getExplorerAddressUrl(address: Address): string;
13
+ getExplorerTxUrl(txID: TxHash): string;
14
+ getFullDerivationPath(walletIndex: number): string;
15
+ getAddressAsync(index?: number): Promise<string>;
16
+ getAddress(): string;
17
+ validateAddress(address: Address): boolean;
18
+ getBalance(address: Address, assets?: TokenAsset[]): Promise<Balance[]>;
19
+ getFees(): Promise<Fees>;
20
+ getTransactionData(txId: string): Promise<Tx>;
21
+ getTransactions(params?: TxHistoryParams): Promise<TxsPage>;
22
+ transfer({ walletIndex, recipient, asset, amount, memo, gasBudget }: TxParams): Promise<string>;
23
+ broadcastTx(txHex: string): Promise<TxHash>;
24
+ prepareTx({ walletIndex, memo, recipient, asset, amount, gasBudget, }: TxParams): Promise<PreparedTx>;
25
+ private getKeypair;
26
+ private getCoinDecimals;
27
+ private fetchAllTransactionBlocks;
28
+ private parseTransaction;
29
+ private coinTypeToSymbol;
30
+ }
package/lib/const.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Asset } from '@xchainjs/xchain-util';
2
+ import { SUIClientParams } from './types';
3
+ export declare const SUIChain: "SUI";
4
+ export declare const SUI_DECIMALS = 9;
5
+ export declare const SUIAsset: Asset;
6
+ export declare const SUI_TYPE_TAG = "0x2::sui::SUI";
7
+ export declare const defaultSuiParams: SUIClientParams;
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { Client } from './client';
2
+ export * from './types';
3
+ export * from './const';
@@ -0,0 +1,410 @@
1
+ import { SuiClient } from '@mysten/sui/client';
2
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
3
+ import { Transaction } from '@mysten/sui/transactions';
4
+ import { isValidSuiAddress } from '@mysten/sui/utils';
5
+ import { ExplorerProvider, Network, BaseXChainClient, FeeType, FeeOption, TxType } from '@xchainjs/xchain-client';
6
+ import { getSeed } from '@xchainjs/xchain-crypto';
7
+ import { AssetType, baseAmount, assetFromStringEx, eqAsset, getContractAddressFromAsset } from '@xchainjs/xchain-util';
8
+ import slip10 from 'micro-key-producer/slip10.js';
9
+
10
+ /******************************************************************************
11
+ Copyright (c) Microsoft Corporation.
12
+
13
+ Permission to use, copy, modify, and/or distribute this software for any
14
+ purpose with or without fee is hereby granted.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
17
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
19
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
20
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
21
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22
+ PERFORMANCE OF THIS SOFTWARE.
23
+ ***************************************************************************** */
24
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
25
+
26
+
27
+ function __awaiter(thisArg, _arguments, P, generator) {
28
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
29
+ return new (P || (P = Promise))(function (resolve, reject) {
30
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
31
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
32
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
33
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
34
+ });
35
+ }
36
+
37
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
38
+ var e = new Error(message);
39
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
40
+ };
41
+
42
+ const SUIChain = 'SUI';
43
+ const SUI_DECIMALS = 9;
44
+ const SUIAsset = {
45
+ chain: SUIChain,
46
+ ticker: 'SUI',
47
+ symbol: 'SUI',
48
+ type: AssetType.NATIVE,
49
+ };
50
+ const SUI_TYPE_TAG = '0x2::sui::SUI';
51
+ const mainnetExplorer = new ExplorerProvider('https://suiscan.xyz/mainnet', 'https://suiscan.xyz/mainnet/account/%%ADDRESS%%', 'https://suiscan.xyz/mainnet/tx/%%TX_ID%%');
52
+ const defaultSuiParams = {
53
+ network: Network.Mainnet,
54
+ rootDerivationPaths: {
55
+ [Network.Mainnet]: "m/44'/784'/",
56
+ [Network.Testnet]: "m/44'/784'/",
57
+ [Network.Stagenet]: "m/44'/784'/",
58
+ },
59
+ explorerProviders: {
60
+ [Network.Mainnet]: mainnetExplorer,
61
+ [Network.Testnet]: new ExplorerProvider('https://suiscan.xyz/testnet', 'https://suiscan.xyz/testnet/account/%%ADDRESS%%', 'https://suiscan.xyz/testnet/tx/%%TX_ID%%'),
62
+ [Network.Stagenet]: mainnetExplorer,
63
+ },
64
+ };
65
+
66
+ const getDefaultClientUrl = (network) => {
67
+ const networkMap = {
68
+ [Network.Mainnet]: 'https://fullnode.mainnet.sui.io:443',
69
+ [Network.Stagenet]: 'https://fullnode.mainnet.sui.io:443',
70
+ [Network.Testnet]: 'https://fullnode.testnet.sui.io:443',
71
+ };
72
+ return networkMap[network];
73
+ };
74
+
75
+ class Client extends BaseXChainClient {
76
+ constructor(params = defaultSuiParams) {
77
+ var _a;
78
+ const mergedParams = Object.assign(Object.assign({}, defaultSuiParams), params);
79
+ super(SUIChain, mergedParams);
80
+ this.explorerProviders = mergedParams.explorerProviders;
81
+ this.clientUrls = mergedParams.clientUrls;
82
+ this.suiClient = new SuiClient({
83
+ url: ((_a = this.clientUrls) === null || _a === void 0 ? void 0 : _a[this.getNetwork()]) || getDefaultClientUrl(this.getNetwork()),
84
+ });
85
+ }
86
+ setNetwork(network) {
87
+ var _a;
88
+ super.setNetwork(network);
89
+ this.suiClient = new SuiClient({
90
+ url: ((_a = this.clientUrls) === null || _a === void 0 ? void 0 : _a[this.getNetwork()]) || getDefaultClientUrl(this.getNetwork()),
91
+ });
92
+ }
93
+ getAssetInfo() {
94
+ return {
95
+ asset: SUIAsset,
96
+ decimal: SUI_DECIMALS,
97
+ };
98
+ }
99
+ getExplorerUrl() {
100
+ return this.explorerProviders[this.getNetwork()].getExplorerUrl();
101
+ }
102
+ getExplorerAddressUrl(address) {
103
+ return this.explorerProviders[this.getNetwork()].getExplorerAddressUrl(address);
104
+ }
105
+ getExplorerTxUrl(txID) {
106
+ return this.explorerProviders[this.getNetwork()].getExplorerTxUrl(txID);
107
+ }
108
+ getFullDerivationPath(walletIndex) {
109
+ if (!this.rootDerivationPaths) {
110
+ throw Error('Can not generate derivation path due to root derivation path is undefined');
111
+ }
112
+ return `${this.rootDerivationPaths[this.getNetwork()]}${walletIndex}'/0'`;
113
+ }
114
+ getAddressAsync(index) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ return this.getKeypair(index || 0).getPublicKey().toSuiAddress();
117
+ });
118
+ }
119
+ getAddress() {
120
+ throw Error('Sync method not supported');
121
+ }
122
+ validateAddress(address) {
123
+ return isValidSuiAddress(address);
124
+ }
125
+ getBalance(address, assets) {
126
+ return __awaiter(this, void 0, void 0, function* () {
127
+ const balances = [];
128
+ // Get native SUI balance
129
+ const suiBalance = yield this.suiClient.getBalance({ owner: address });
130
+ balances.push({
131
+ asset: SUIAsset,
132
+ amount: baseAmount(suiBalance.totalBalance, SUI_DECIMALS),
133
+ });
134
+ // Get all coin balances if no specific assets requested, or filter by requested assets
135
+ const allBalances = yield this.suiClient.getAllBalances({ owner: address });
136
+ for (const coinBalance of allBalances) {
137
+ // Skip native SUI (already added)
138
+ if (coinBalance.coinType === SUI_TYPE_TAG)
139
+ continue;
140
+ const tokenAsset = assetFromStringEx(`SUI.${this.coinTypeToSymbol(coinBalance.coinType)}`);
141
+ if (assets && !assets.some((a) => eqAsset(a, tokenAsset)))
142
+ continue;
143
+ const decimals = yield this.getCoinDecimals(coinBalance.coinType);
144
+ balances.push({
145
+ asset: tokenAsset,
146
+ amount: baseAmount(coinBalance.totalBalance, decimals),
147
+ });
148
+ }
149
+ return balances;
150
+ });
151
+ }
152
+ getFees() {
153
+ return __awaiter(this, void 0, void 0, function* () {
154
+ // SUI uses a gas-based fee model. Reference gas price from the network.
155
+ const gasPrice = yield this.suiClient.getReferenceGasPrice();
156
+ const baseFee = BigInt(gasPrice) * BigInt(2000); // Estimate for a simple transfer
157
+ return {
158
+ type: FeeType.FlatFee,
159
+ [FeeOption.Average]: baseAmount(baseFee.toString(), SUI_DECIMALS),
160
+ [FeeOption.Fast]: baseAmount(baseFee.toString(), SUI_DECIMALS),
161
+ [FeeOption.Fastest]: baseAmount(baseFee.toString(), SUI_DECIMALS),
162
+ };
163
+ });
164
+ }
165
+ getTransactionData(txId) {
166
+ return __awaiter(this, void 0, void 0, function* () {
167
+ const txResponse = yield this.suiClient.getTransactionBlock({
168
+ digest: txId,
169
+ options: {
170
+ showInput: true,
171
+ showEffects: true,
172
+ showBalanceChanges: true,
173
+ },
174
+ });
175
+ return yield this.parseTransaction(txResponse);
176
+ });
177
+ }
178
+ getTransactions(params) {
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ const address = (params === null || params === void 0 ? void 0 : params.address) || (yield this.getAddressAsync());
181
+ const limit = (params === null || params === void 0 ? void 0 : params.limit) || 50;
182
+ // Fetch all pages for FromAddress
183
+ const fromResponses = yield this.fetchAllTransactionBlocks({ FromAddress: address }, limit);
184
+ // Fetch all pages for ToAddress
185
+ const toResponses = yield this.fetchAllTransactionBlocks({ ToAddress: address }, limit);
186
+ // Merge and deduplicate by digest
187
+ const seen = new Set();
188
+ const allTxResponses = [];
189
+ for (const tx of [...fromResponses, ...toResponses]) {
190
+ if (!seen.has(tx.digest)) {
191
+ seen.add(tx.digest);
192
+ allTxResponses.push(tx);
193
+ }
194
+ }
195
+ // Sort by timestamp descending
196
+ allTxResponses.sort((a, b) => {
197
+ const timeA = Number(a.timestampMs || 0);
198
+ const timeB = Number(b.timestampMs || 0);
199
+ return timeB - timeA;
200
+ });
201
+ const txs = [];
202
+ for (const txResponse of allTxResponses) {
203
+ try {
204
+ txs.push(yield this.parseTransaction(txResponse));
205
+ }
206
+ catch (_a) {
207
+ // Skip unparseable transactions
208
+ }
209
+ }
210
+ const offset = (params === null || params === void 0 ? void 0 : params.offset) || 0;
211
+ const paged = txs.slice(offset, offset + limit);
212
+ return {
213
+ txs: paged,
214
+ total: txs.length,
215
+ };
216
+ });
217
+ }
218
+ transfer(_a) {
219
+ return __awaiter(this, arguments, void 0, function* ({ walletIndex, recipient, asset, amount, memo, gasBudget }) {
220
+ var _b, _c, _d, _e;
221
+ if (memo)
222
+ throw Error('Memo is not supported for SUI transfers');
223
+ const keypair = this.getKeypair(walletIndex || 0);
224
+ const tx = new Transaction();
225
+ if (!asset || eqAsset(asset, SUIAsset)) {
226
+ // Native SUI transfer
227
+ const [coin] = tx.splitCoins(tx.gas, [amount.amount().toString()]);
228
+ tx.transferObjects([coin], recipient);
229
+ }
230
+ else {
231
+ // Token transfer
232
+ const coinType = getContractAddressFromAsset(asset);
233
+ const coins = yield this.suiClient.getCoins({
234
+ owner: keypair.getPublicKey().toSuiAddress(),
235
+ coinType,
236
+ });
237
+ if (coins.data.length === 0) {
238
+ throw Error('No coins found for the specified asset');
239
+ }
240
+ // Merge all coins into the first one if there are multiple
241
+ const primaryCoin = tx.object(coins.data[0].coinObjectId);
242
+ if (coins.data.length > 1) {
243
+ tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
244
+ }
245
+ const [splitCoin] = tx.splitCoins(primaryCoin, [amount.amount().toString()]);
246
+ tx.transferObjects([splitCoin], recipient);
247
+ }
248
+ if (gasBudget) {
249
+ tx.setGasBudget(gasBudget.amount().toNumber());
250
+ }
251
+ const result = yield this.suiClient.signAndExecuteTransaction({
252
+ signer: keypair,
253
+ transaction: tx,
254
+ options: { showEffects: true },
255
+ });
256
+ if (((_c = (_b = result.effects) === null || _b === void 0 ? void 0 : _b.status) === null || _c === void 0 ? void 0 : _c.status) !== 'success') {
257
+ throw Error(`Transaction failed: ${((_e = (_d = result.effects) === null || _d === void 0 ? void 0 : _d.status) === null || _e === void 0 ? void 0 : _e.error) || 'unknown error'}`);
258
+ }
259
+ yield this.suiClient.waitForTransaction({ digest: result.digest });
260
+ return result.digest;
261
+ });
262
+ }
263
+ broadcastTx(txHex) {
264
+ return __awaiter(this, void 0, void 0, function* () {
265
+ var _a, _b, _c, _d;
266
+ const txBytes = Uint8Array.from(Buffer.from(txHex, 'hex'));
267
+ const keypair = this.getKeypair(0);
268
+ const { signature, bytes } = yield keypair.signTransaction(txBytes);
269
+ const result = yield this.suiClient.executeTransactionBlock({
270
+ transactionBlock: bytes,
271
+ signature,
272
+ options: { showEffects: true },
273
+ });
274
+ if (((_b = (_a = result.effects) === null || _a === void 0 ? void 0 : _a.status) === null || _b === void 0 ? void 0 : _b.status) !== 'success') {
275
+ throw Error(`Transaction failed: ${((_d = (_c = result.effects) === null || _c === void 0 ? void 0 : _c.status) === null || _d === void 0 ? void 0 : _d.error) || 'unknown error'}`);
276
+ }
277
+ return result.digest;
278
+ });
279
+ }
280
+ prepareTx(_a) {
281
+ return __awaiter(this, arguments, void 0, function* ({ walletIndex, memo, recipient, asset, amount, gasBudget, }) {
282
+ if (memo)
283
+ throw Error('Memo is not supported for SUI transfers');
284
+ const sender = yield this.getAddressAsync(walletIndex !== null && walletIndex !== void 0 ? walletIndex : 0);
285
+ const tx = new Transaction();
286
+ tx.setSender(sender);
287
+ if (!asset || eqAsset(asset, SUIAsset)) {
288
+ const [coin] = tx.splitCoins(tx.gas, [amount.amount().toString()]);
289
+ tx.transferObjects([coin], recipient);
290
+ }
291
+ else {
292
+ const coinType = getContractAddressFromAsset(asset);
293
+ const coins = yield this.suiClient.getCoins({
294
+ owner: sender,
295
+ coinType,
296
+ });
297
+ if (coins.data.length === 0) {
298
+ throw Error('No coins found for the specified asset');
299
+ }
300
+ const primaryCoin = tx.object(coins.data[0].coinObjectId);
301
+ if (coins.data.length > 1) {
302
+ tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
303
+ }
304
+ const [splitCoin] = tx.splitCoins(primaryCoin, [amount.amount().toString()]);
305
+ tx.transferObjects([splitCoin], recipient);
306
+ }
307
+ if (gasBudget) {
308
+ tx.setGasBudget(gasBudget.amount().toNumber());
309
+ }
310
+ const builtTx = yield tx.build({ client: this.suiClient });
311
+ return { rawUnsignedTx: Buffer.from(builtTx).toString('hex') };
312
+ });
313
+ }
314
+ getKeypair(index) {
315
+ if (!this.phrase)
316
+ throw new Error('Phrase must be provided');
317
+ const seed = getSeed(this.phrase);
318
+ const hd = slip10.fromMasterSeed(seed);
319
+ const derived = hd.derive(this.getFullDerivationPath(index));
320
+ return Ed25519Keypair.fromSecretKey(derived.privateKey);
321
+ }
322
+ getCoinDecimals(coinType) {
323
+ return __awaiter(this, void 0, void 0, function* () {
324
+ var _a;
325
+ if (coinType === SUI_TYPE_TAG)
326
+ return SUI_DECIMALS;
327
+ try {
328
+ const metadata = yield this.suiClient.getCoinMetadata({ coinType });
329
+ return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.decimals) !== null && _a !== void 0 ? _a : SUI_DECIMALS;
330
+ }
331
+ catch (_b) {
332
+ return SUI_DECIMALS;
333
+ }
334
+ });
335
+ }
336
+ fetchAllTransactionBlocks(filter, maxResults) {
337
+ return __awaiter(this, void 0, void 0, function* () {
338
+ const results = [];
339
+ let cursor = undefined;
340
+ const pageSize = Math.min(maxResults, 50);
341
+ do {
342
+ const page = yield this.suiClient.queryTransactionBlocks({
343
+ filter,
344
+ options: {
345
+ showInput: true,
346
+ showEffects: true,
347
+ showBalanceChanges: true,
348
+ },
349
+ limit: pageSize,
350
+ cursor: cursor !== null && cursor !== void 0 ? cursor : undefined,
351
+ });
352
+ results.push(...page.data);
353
+ cursor = page.nextCursor;
354
+ if (!page.hasNextPage || results.length >= maxResults)
355
+ break;
356
+ } while (cursor);
357
+ return results.slice(0, maxResults);
358
+ });
359
+ }
360
+ parseTransaction(txResponse) {
361
+ return __awaiter(this, void 0, void 0, function* () {
362
+ const from = [];
363
+ const to = [];
364
+ if (txResponse.balanceChanges) {
365
+ for (const change of txResponse.balanceChanges) {
366
+ const isSui = change.coinType === SUI_TYPE_TAG;
367
+ const decimals = yield this.getCoinDecimals(change.coinType);
368
+ const asset = isSui
369
+ ? SUIAsset
370
+ : assetFromStringEx(`SUI.${this.coinTypeToSymbol(change.coinType)}`);
371
+ const changeAmount = BigInt(change.amount);
372
+ const ownerAddress = change.owner && typeof change.owner === 'object' && 'AddressOwner' in change.owner
373
+ ? change.owner.AddressOwner
374
+ : '';
375
+ if (changeAmount < BigInt(0)) {
376
+ from.push({
377
+ from: ownerAddress,
378
+ amount: baseAmount((-changeAmount).toString(), decimals),
379
+ asset,
380
+ });
381
+ }
382
+ else if (changeAmount > BigInt(0)) {
383
+ to.push({
384
+ to: ownerAddress,
385
+ amount: baseAmount(changeAmount.toString(), decimals),
386
+ asset,
387
+ });
388
+ }
389
+ }
390
+ }
391
+ return {
392
+ asset: SUIAsset,
393
+ date: new Date(Number(txResponse.timestampMs || 0)),
394
+ type: TxType.Transfer,
395
+ hash: txResponse.digest,
396
+ from,
397
+ to,
398
+ };
399
+ });
400
+ }
401
+ coinTypeToSymbol(coinType) {
402
+ // coinType format: "0xpackage::module::Type"
403
+ // Convert to symbol format for xchainjs: "TYPE-0xpackage::module::Type"
404
+ const parts = coinType.split('::');
405
+ const typeName = parts[parts.length - 1] || coinType;
406
+ return `${typeName}-${coinType}`;
407
+ }
408
+ }
409
+
410
+ export { Client, SUIAsset, SUIChain, SUI_DECIMALS, SUI_TYPE_TAG, defaultSuiParams };
package/lib/index.js ADDED
@@ -0,0 +1,421 @@
1
+ 'use strict';
2
+
3
+ var client = require('@mysten/sui/client');
4
+ var ed25519 = require('@mysten/sui/keypairs/ed25519');
5
+ var transactions = require('@mysten/sui/transactions');
6
+ var utils = require('@mysten/sui/utils');
7
+ var xchainClient = require('@xchainjs/xchain-client');
8
+ var xchainCrypto = require('@xchainjs/xchain-crypto');
9
+ var xchainUtil = require('@xchainjs/xchain-util');
10
+ var slip10 = require('micro-key-producer/slip10.js');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var slip10__default = /*#__PURE__*/_interopDefault(slip10);
15
+
16
+ /******************************************************************************
17
+ Copyright (c) Microsoft Corporation.
18
+
19
+ Permission to use, copy, modify, and/or distribute this software for any
20
+ purpose with or without fee is hereby granted.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
23
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
24
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
25
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
26
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
27
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
28
+ PERFORMANCE OF THIS SOFTWARE.
29
+ ***************************************************************************** */
30
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
31
+
32
+
33
+ function __awaiter(thisArg, _arguments, P, generator) {
34
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
35
+ return new (P || (P = Promise))(function (resolve, reject) {
36
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
37
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
38
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
39
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
40
+ });
41
+ }
42
+
43
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
44
+ var e = new Error(message);
45
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
46
+ };
47
+
48
+ const SUIChain = 'SUI';
49
+ const SUI_DECIMALS = 9;
50
+ const SUIAsset = {
51
+ chain: SUIChain,
52
+ ticker: 'SUI',
53
+ symbol: 'SUI',
54
+ type: xchainUtil.AssetType.NATIVE,
55
+ };
56
+ const SUI_TYPE_TAG = '0x2::sui::SUI';
57
+ const mainnetExplorer = new xchainClient.ExplorerProvider('https://suiscan.xyz/mainnet', 'https://suiscan.xyz/mainnet/account/%%ADDRESS%%', 'https://suiscan.xyz/mainnet/tx/%%TX_ID%%');
58
+ const defaultSuiParams = {
59
+ network: xchainClient.Network.Mainnet,
60
+ rootDerivationPaths: {
61
+ [xchainClient.Network.Mainnet]: "m/44'/784'/",
62
+ [xchainClient.Network.Testnet]: "m/44'/784'/",
63
+ [xchainClient.Network.Stagenet]: "m/44'/784'/",
64
+ },
65
+ explorerProviders: {
66
+ [xchainClient.Network.Mainnet]: mainnetExplorer,
67
+ [xchainClient.Network.Testnet]: new xchainClient.ExplorerProvider('https://suiscan.xyz/testnet', 'https://suiscan.xyz/testnet/account/%%ADDRESS%%', 'https://suiscan.xyz/testnet/tx/%%TX_ID%%'),
68
+ [xchainClient.Network.Stagenet]: mainnetExplorer,
69
+ },
70
+ };
71
+
72
+ const getDefaultClientUrl = (network) => {
73
+ const networkMap = {
74
+ [xchainClient.Network.Mainnet]: 'https://fullnode.mainnet.sui.io:443',
75
+ [xchainClient.Network.Stagenet]: 'https://fullnode.mainnet.sui.io:443',
76
+ [xchainClient.Network.Testnet]: 'https://fullnode.testnet.sui.io:443',
77
+ };
78
+ return networkMap[network];
79
+ };
80
+
81
+ class Client extends xchainClient.BaseXChainClient {
82
+ constructor(params = defaultSuiParams) {
83
+ var _a;
84
+ const mergedParams = Object.assign(Object.assign({}, defaultSuiParams), params);
85
+ super(SUIChain, mergedParams);
86
+ this.explorerProviders = mergedParams.explorerProviders;
87
+ this.clientUrls = mergedParams.clientUrls;
88
+ this.suiClient = new client.SuiClient({
89
+ url: ((_a = this.clientUrls) === null || _a === void 0 ? void 0 : _a[this.getNetwork()]) || getDefaultClientUrl(this.getNetwork()),
90
+ });
91
+ }
92
+ setNetwork(network) {
93
+ var _a;
94
+ super.setNetwork(network);
95
+ this.suiClient = new client.SuiClient({
96
+ url: ((_a = this.clientUrls) === null || _a === void 0 ? void 0 : _a[this.getNetwork()]) || getDefaultClientUrl(this.getNetwork()),
97
+ });
98
+ }
99
+ getAssetInfo() {
100
+ return {
101
+ asset: SUIAsset,
102
+ decimal: SUI_DECIMALS,
103
+ };
104
+ }
105
+ getExplorerUrl() {
106
+ return this.explorerProviders[this.getNetwork()].getExplorerUrl();
107
+ }
108
+ getExplorerAddressUrl(address) {
109
+ return this.explorerProviders[this.getNetwork()].getExplorerAddressUrl(address);
110
+ }
111
+ getExplorerTxUrl(txID) {
112
+ return this.explorerProviders[this.getNetwork()].getExplorerTxUrl(txID);
113
+ }
114
+ getFullDerivationPath(walletIndex) {
115
+ if (!this.rootDerivationPaths) {
116
+ throw Error('Can not generate derivation path due to root derivation path is undefined');
117
+ }
118
+ return `${this.rootDerivationPaths[this.getNetwork()]}${walletIndex}'/0'`;
119
+ }
120
+ getAddressAsync(index) {
121
+ return __awaiter(this, void 0, void 0, function* () {
122
+ return this.getKeypair(index || 0).getPublicKey().toSuiAddress();
123
+ });
124
+ }
125
+ getAddress() {
126
+ throw Error('Sync method not supported');
127
+ }
128
+ validateAddress(address) {
129
+ return utils.isValidSuiAddress(address);
130
+ }
131
+ getBalance(address, assets) {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ const balances = [];
134
+ // Get native SUI balance
135
+ const suiBalance = yield this.suiClient.getBalance({ owner: address });
136
+ balances.push({
137
+ asset: SUIAsset,
138
+ amount: xchainUtil.baseAmount(suiBalance.totalBalance, SUI_DECIMALS),
139
+ });
140
+ // Get all coin balances if no specific assets requested, or filter by requested assets
141
+ const allBalances = yield this.suiClient.getAllBalances({ owner: address });
142
+ for (const coinBalance of allBalances) {
143
+ // Skip native SUI (already added)
144
+ if (coinBalance.coinType === SUI_TYPE_TAG)
145
+ continue;
146
+ const tokenAsset = xchainUtil.assetFromStringEx(`SUI.${this.coinTypeToSymbol(coinBalance.coinType)}`);
147
+ if (assets && !assets.some((a) => xchainUtil.eqAsset(a, tokenAsset)))
148
+ continue;
149
+ const decimals = yield this.getCoinDecimals(coinBalance.coinType);
150
+ balances.push({
151
+ asset: tokenAsset,
152
+ amount: xchainUtil.baseAmount(coinBalance.totalBalance, decimals),
153
+ });
154
+ }
155
+ return balances;
156
+ });
157
+ }
158
+ getFees() {
159
+ return __awaiter(this, void 0, void 0, function* () {
160
+ // SUI uses a gas-based fee model. Reference gas price from the network.
161
+ const gasPrice = yield this.suiClient.getReferenceGasPrice();
162
+ const baseFee = BigInt(gasPrice) * BigInt(2000); // Estimate for a simple transfer
163
+ return {
164
+ type: xchainClient.FeeType.FlatFee,
165
+ [xchainClient.FeeOption.Average]: xchainUtil.baseAmount(baseFee.toString(), SUI_DECIMALS),
166
+ [xchainClient.FeeOption.Fast]: xchainUtil.baseAmount(baseFee.toString(), SUI_DECIMALS),
167
+ [xchainClient.FeeOption.Fastest]: xchainUtil.baseAmount(baseFee.toString(), SUI_DECIMALS),
168
+ };
169
+ });
170
+ }
171
+ getTransactionData(txId) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const txResponse = yield this.suiClient.getTransactionBlock({
174
+ digest: txId,
175
+ options: {
176
+ showInput: true,
177
+ showEffects: true,
178
+ showBalanceChanges: true,
179
+ },
180
+ });
181
+ return yield this.parseTransaction(txResponse);
182
+ });
183
+ }
184
+ getTransactions(params) {
185
+ return __awaiter(this, void 0, void 0, function* () {
186
+ const address = (params === null || params === void 0 ? void 0 : params.address) || (yield this.getAddressAsync());
187
+ const limit = (params === null || params === void 0 ? void 0 : params.limit) || 50;
188
+ // Fetch all pages for FromAddress
189
+ const fromResponses = yield this.fetchAllTransactionBlocks({ FromAddress: address }, limit);
190
+ // Fetch all pages for ToAddress
191
+ const toResponses = yield this.fetchAllTransactionBlocks({ ToAddress: address }, limit);
192
+ // Merge and deduplicate by digest
193
+ const seen = new Set();
194
+ const allTxResponses = [];
195
+ for (const tx of [...fromResponses, ...toResponses]) {
196
+ if (!seen.has(tx.digest)) {
197
+ seen.add(tx.digest);
198
+ allTxResponses.push(tx);
199
+ }
200
+ }
201
+ // Sort by timestamp descending
202
+ allTxResponses.sort((a, b) => {
203
+ const timeA = Number(a.timestampMs || 0);
204
+ const timeB = Number(b.timestampMs || 0);
205
+ return timeB - timeA;
206
+ });
207
+ const txs = [];
208
+ for (const txResponse of allTxResponses) {
209
+ try {
210
+ txs.push(yield this.parseTransaction(txResponse));
211
+ }
212
+ catch (_a) {
213
+ // Skip unparseable transactions
214
+ }
215
+ }
216
+ const offset = (params === null || params === void 0 ? void 0 : params.offset) || 0;
217
+ const paged = txs.slice(offset, offset + limit);
218
+ return {
219
+ txs: paged,
220
+ total: txs.length,
221
+ };
222
+ });
223
+ }
224
+ transfer(_a) {
225
+ return __awaiter(this, arguments, void 0, function* ({ walletIndex, recipient, asset, amount, memo, gasBudget }) {
226
+ var _b, _c, _d, _e;
227
+ if (memo)
228
+ throw Error('Memo is not supported for SUI transfers');
229
+ const keypair = this.getKeypair(walletIndex || 0);
230
+ const tx = new transactions.Transaction();
231
+ if (!asset || xchainUtil.eqAsset(asset, SUIAsset)) {
232
+ // Native SUI transfer
233
+ const [coin] = tx.splitCoins(tx.gas, [amount.amount().toString()]);
234
+ tx.transferObjects([coin], recipient);
235
+ }
236
+ else {
237
+ // Token transfer
238
+ const coinType = xchainUtil.getContractAddressFromAsset(asset);
239
+ const coins = yield this.suiClient.getCoins({
240
+ owner: keypair.getPublicKey().toSuiAddress(),
241
+ coinType,
242
+ });
243
+ if (coins.data.length === 0) {
244
+ throw Error('No coins found for the specified asset');
245
+ }
246
+ // Merge all coins into the first one if there are multiple
247
+ const primaryCoin = tx.object(coins.data[0].coinObjectId);
248
+ if (coins.data.length > 1) {
249
+ tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
250
+ }
251
+ const [splitCoin] = tx.splitCoins(primaryCoin, [amount.amount().toString()]);
252
+ tx.transferObjects([splitCoin], recipient);
253
+ }
254
+ if (gasBudget) {
255
+ tx.setGasBudget(gasBudget.amount().toNumber());
256
+ }
257
+ const result = yield this.suiClient.signAndExecuteTransaction({
258
+ signer: keypair,
259
+ transaction: tx,
260
+ options: { showEffects: true },
261
+ });
262
+ if (((_c = (_b = result.effects) === null || _b === void 0 ? void 0 : _b.status) === null || _c === void 0 ? void 0 : _c.status) !== 'success') {
263
+ throw Error(`Transaction failed: ${((_e = (_d = result.effects) === null || _d === void 0 ? void 0 : _d.status) === null || _e === void 0 ? void 0 : _e.error) || 'unknown error'}`);
264
+ }
265
+ yield this.suiClient.waitForTransaction({ digest: result.digest });
266
+ return result.digest;
267
+ });
268
+ }
269
+ broadcastTx(txHex) {
270
+ return __awaiter(this, void 0, void 0, function* () {
271
+ var _a, _b, _c, _d;
272
+ const txBytes = Uint8Array.from(Buffer.from(txHex, 'hex'));
273
+ const keypair = this.getKeypair(0);
274
+ const { signature, bytes } = yield keypair.signTransaction(txBytes);
275
+ const result = yield this.suiClient.executeTransactionBlock({
276
+ transactionBlock: bytes,
277
+ signature,
278
+ options: { showEffects: true },
279
+ });
280
+ if (((_b = (_a = result.effects) === null || _a === void 0 ? void 0 : _a.status) === null || _b === void 0 ? void 0 : _b.status) !== 'success') {
281
+ throw Error(`Transaction failed: ${((_d = (_c = result.effects) === null || _c === void 0 ? void 0 : _c.status) === null || _d === void 0 ? void 0 : _d.error) || 'unknown error'}`);
282
+ }
283
+ return result.digest;
284
+ });
285
+ }
286
+ prepareTx(_a) {
287
+ return __awaiter(this, arguments, void 0, function* ({ walletIndex, memo, recipient, asset, amount, gasBudget, }) {
288
+ if (memo)
289
+ throw Error('Memo is not supported for SUI transfers');
290
+ const sender = yield this.getAddressAsync(walletIndex !== null && walletIndex !== void 0 ? walletIndex : 0);
291
+ const tx = new transactions.Transaction();
292
+ tx.setSender(sender);
293
+ if (!asset || xchainUtil.eqAsset(asset, SUIAsset)) {
294
+ const [coin] = tx.splitCoins(tx.gas, [amount.amount().toString()]);
295
+ tx.transferObjects([coin], recipient);
296
+ }
297
+ else {
298
+ const coinType = xchainUtil.getContractAddressFromAsset(asset);
299
+ const coins = yield this.suiClient.getCoins({
300
+ owner: sender,
301
+ coinType,
302
+ });
303
+ if (coins.data.length === 0) {
304
+ throw Error('No coins found for the specified asset');
305
+ }
306
+ const primaryCoin = tx.object(coins.data[0].coinObjectId);
307
+ if (coins.data.length > 1) {
308
+ tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
309
+ }
310
+ const [splitCoin] = tx.splitCoins(primaryCoin, [amount.amount().toString()]);
311
+ tx.transferObjects([splitCoin], recipient);
312
+ }
313
+ if (gasBudget) {
314
+ tx.setGasBudget(gasBudget.amount().toNumber());
315
+ }
316
+ const builtTx = yield tx.build({ client: this.suiClient });
317
+ return { rawUnsignedTx: Buffer.from(builtTx).toString('hex') };
318
+ });
319
+ }
320
+ getKeypair(index) {
321
+ if (!this.phrase)
322
+ throw new Error('Phrase must be provided');
323
+ const seed = xchainCrypto.getSeed(this.phrase);
324
+ const hd = slip10__default.default.fromMasterSeed(seed);
325
+ const derived = hd.derive(this.getFullDerivationPath(index));
326
+ return ed25519.Ed25519Keypair.fromSecretKey(derived.privateKey);
327
+ }
328
+ getCoinDecimals(coinType) {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ var _a;
331
+ if (coinType === SUI_TYPE_TAG)
332
+ return SUI_DECIMALS;
333
+ try {
334
+ const metadata = yield this.suiClient.getCoinMetadata({ coinType });
335
+ return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.decimals) !== null && _a !== void 0 ? _a : SUI_DECIMALS;
336
+ }
337
+ catch (_b) {
338
+ return SUI_DECIMALS;
339
+ }
340
+ });
341
+ }
342
+ fetchAllTransactionBlocks(filter, maxResults) {
343
+ return __awaiter(this, void 0, void 0, function* () {
344
+ const results = [];
345
+ let cursor = undefined;
346
+ const pageSize = Math.min(maxResults, 50);
347
+ do {
348
+ const page = yield this.suiClient.queryTransactionBlocks({
349
+ filter,
350
+ options: {
351
+ showInput: true,
352
+ showEffects: true,
353
+ showBalanceChanges: true,
354
+ },
355
+ limit: pageSize,
356
+ cursor: cursor !== null && cursor !== void 0 ? cursor : undefined,
357
+ });
358
+ results.push(...page.data);
359
+ cursor = page.nextCursor;
360
+ if (!page.hasNextPage || results.length >= maxResults)
361
+ break;
362
+ } while (cursor);
363
+ return results.slice(0, maxResults);
364
+ });
365
+ }
366
+ parseTransaction(txResponse) {
367
+ return __awaiter(this, void 0, void 0, function* () {
368
+ const from = [];
369
+ const to = [];
370
+ if (txResponse.balanceChanges) {
371
+ for (const change of txResponse.balanceChanges) {
372
+ const isSui = change.coinType === SUI_TYPE_TAG;
373
+ const decimals = yield this.getCoinDecimals(change.coinType);
374
+ const asset = isSui
375
+ ? SUIAsset
376
+ : xchainUtil.assetFromStringEx(`SUI.${this.coinTypeToSymbol(change.coinType)}`);
377
+ const changeAmount = BigInt(change.amount);
378
+ const ownerAddress = change.owner && typeof change.owner === 'object' && 'AddressOwner' in change.owner
379
+ ? change.owner.AddressOwner
380
+ : '';
381
+ if (changeAmount < BigInt(0)) {
382
+ from.push({
383
+ from: ownerAddress,
384
+ amount: xchainUtil.baseAmount((-changeAmount).toString(), decimals),
385
+ asset,
386
+ });
387
+ }
388
+ else if (changeAmount > BigInt(0)) {
389
+ to.push({
390
+ to: ownerAddress,
391
+ amount: xchainUtil.baseAmount(changeAmount.toString(), decimals),
392
+ asset,
393
+ });
394
+ }
395
+ }
396
+ }
397
+ return {
398
+ asset: SUIAsset,
399
+ date: new Date(Number(txResponse.timestampMs || 0)),
400
+ type: xchainClient.TxType.Transfer,
401
+ hash: txResponse.digest,
402
+ from,
403
+ to,
404
+ };
405
+ });
406
+ }
407
+ coinTypeToSymbol(coinType) {
408
+ // coinType format: "0xpackage::module::Type"
409
+ // Convert to symbol format for xchainjs: "TYPE-0xpackage::module::Type"
410
+ const parts = coinType.split('::');
411
+ const typeName = parts[parts.length - 1] || coinType;
412
+ return `${typeName}-${coinType}`;
413
+ }
414
+ }
415
+
416
+ exports.Client = Client;
417
+ exports.SUIAsset = SUIAsset;
418
+ exports.SUIChain = SUIChain;
419
+ exports.SUI_DECIMALS = SUI_DECIMALS;
420
+ exports.SUI_TYPE_TAG = SUI_TYPE_TAG;
421
+ exports.defaultSuiParams = defaultSuiParams;
package/lib/types.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { Balance as BaseBalance, ExplorerProviders, Network, Tx as BaseTx, TxFrom as BaseTxFrom, TxParams as BaseTxParams, TxTo as BaseTxTo, TxsPage as BaseTxsPage, XChainClientParams } from '@xchainjs/xchain-client';
2
+ import { Asset, BaseAmount, TokenAsset } from '@xchainjs/xchain-util';
3
+ export type SUIClientParams = XChainClientParams & {
4
+ explorerProviders: ExplorerProviders;
5
+ clientUrls?: Record<Network, string>;
6
+ };
7
+ export type CompatibleAsset = Asset | TokenAsset;
8
+ export type Balance = BaseBalance & {
9
+ asset: CompatibleAsset;
10
+ };
11
+ export type TxParams = BaseTxParams & {
12
+ asset?: CompatibleAsset;
13
+ gasBudget?: BaseAmount;
14
+ };
15
+ export type TxFrom = BaseTxFrom & {
16
+ asset?: Asset | TokenAsset;
17
+ };
18
+ export type TxTo = BaseTxTo & {
19
+ asset?: Asset | TokenAsset;
20
+ };
21
+ export type Tx = BaseTx & {
22
+ asset: Asset | TokenAsset;
23
+ from: TxFrom[];
24
+ to: TxTo[];
25
+ };
26
+ export type TxsPage = BaseTxsPage & {
27
+ txs: Tx[];
28
+ };
package/lib/utils.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Network } from '@xchainjs/xchain-client';
2
+ export declare const getSuiNetwork: (network: Network) => "mainnet" | "testnet";
3
+ export declare const getDefaultClientUrl: (network: Network) => string;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@xchainjs/xchain-sui",
3
+ "version": "0.1.0",
4
+ "description": "Sui client for XChainJS",
5
+ "keywords": [
6
+ "Sui",
7
+ "XChain"
8
+ ],
9
+ "author": "THORChain",
10
+ "homepage": "https://github.com/xchainjs/xchainjs-lib",
11
+ "license": "MIT",
12
+ "main": "lib/index.js",
13
+ "module": "lib/index.esm.js",
14
+ "typings": "lib/index.d.ts",
15
+ "directories": {
16
+ "lib": "lib",
17
+ "test": "__tests__"
18
+ },
19
+ "files": [
20
+ "lib"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git@github.com:xchainjs/xchainjs-lib.git"
25
+ },
26
+ "scripts": {
27
+ "clean": "rm -rf .turbo && rm -rf lib",
28
+ "build": "yarn clean && rollup -c --bundleConfigAsCjs",
29
+ "build:release": "yarn exec rm -rf release && yarn pack && yarn exec \"mkdir release && tar zxvf package.tgz --directory release && rm package.tgz\"",
30
+ "test": "jest",
31
+ "lint": "eslint --config ../../eslint.config.mjs \"{src,__tests__}/**/*.ts\" --fix --max-warnings 0"
32
+ },
33
+ "dependencies": {
34
+ "@mysten/sui": "^1.21.0",
35
+ "@xchainjs/xchain-client": "2.0.11",
36
+ "@xchainjs/xchain-crypto": "1.0.6",
37
+ "@xchainjs/xchain-util": "2.0.6",
38
+ "micro-key-producer": "^0.7.6"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "directory": "release/package"
43
+ }
44
+ }