@t2000/sdk 5.0.0 → 5.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.
package/dist/browser.cjs CHANGED
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ var transactions = require('@mysten/sui/transactions');
4
+ var utils = require('@mysten/sui/utils');
3
5
  require('@mysten/sui/grpc');
4
6
  require('@mysten/sui/graphql');
5
- var utils = require('@mysten/sui/utils');
6
7
  var aggregatorSdk = require('@cetusprotocol/aggregator-sdk');
7
8
  require('bn.js');
8
9
 
@@ -106,6 +107,34 @@ var init_errors = __esm({
106
107
  });
107
108
 
108
109
  // src/token-registry.ts
110
+ var token_registry_exports = {};
111
+ __export(token_registry_exports, {
112
+ COIN_REGISTRY: () => exports.COIN_REGISTRY,
113
+ ETH_TYPE: () => exports.ETH_TYPE,
114
+ IKA_TYPE: () => exports.IKA_TYPE,
115
+ LOFI_TYPE: () => exports.LOFI_TYPE,
116
+ MANIFEST_TYPE: () => exports.MANIFEST_TYPE,
117
+ NAVX_TYPE: () => exports.NAVX_TYPE,
118
+ SUI_TYPE: () => exports.SUI_TYPE,
119
+ TOKEN_MAP: () => exports.TOKEN_MAP,
120
+ USDC_TYPE: () => exports.USDC_TYPE,
121
+ USDE_TYPE: () => exports.USDE_TYPE,
122
+ USDSUI_TYPE: () => exports.USDSUI_TYPE,
123
+ USDT_TYPE: () => exports.USDT_TYPE,
124
+ WAL_TYPE: () => exports.WAL_TYPE,
125
+ WBTC_TYPE: () => exports.WBTC_TYPE,
126
+ getCoinMeta: () => getCoinMeta,
127
+ getDecimalsForCoinType: () => getDecimalsForCoinType,
128
+ isInRegistry: () => isInRegistry,
129
+ resolveSymbol: () => resolveSymbol,
130
+ resolveTokenType: () => resolveTokenType
131
+ });
132
+ function getCoinMeta(coinType) {
133
+ return BY_TYPE.get(coinType);
134
+ }
135
+ function isInRegistry(coinType) {
136
+ return BY_TYPE.has(coinType);
137
+ }
109
138
  function getDecimalsForCoinType(coinType) {
110
139
  const direct = BY_TYPE.get(coinType);
111
140
  if (direct) return direct.decimals;
@@ -188,6 +217,136 @@ var init_token_registry = __esm({
188
217
  }
189
218
  });
190
219
 
220
+ // src/wallet/coinSelection.ts
221
+ var coinSelection_exports = {};
222
+ __export(coinSelection_exports, {
223
+ fetchAllCoins: () => fetchAllCoins,
224
+ selectAndSplitCoin: () => selectAndSplitCoin,
225
+ selectSuiCoin: () => selectSuiCoin
226
+ });
227
+ async function fetchAllCoins(client, owner, coinType) {
228
+ const [balance, ids] = await Promise.all([
229
+ client.core.getBalance({ owner, coinType }),
230
+ (async () => {
231
+ const out = [];
232
+ let cursor;
233
+ let hasNext = true;
234
+ while (hasNext) {
235
+ const page = await client.core.listCoins({ owner, coinType, cursor: cursor ?? void 0 });
236
+ for (const c of page.objects) out.push(c.objectId);
237
+ cursor = page.cursor;
238
+ hasNext = page.hasNextPage;
239
+ }
240
+ return out;
241
+ })()
242
+ ]);
243
+ return { ids, totalBalance: BigInt(balance.balance.balance) };
244
+ }
245
+ async function selectAndSplitCoin(tx, client, owner, coinType, amount, options = {}) {
246
+ if (options.sponsoredContext) {
247
+ return selectCoinObjectsOnly(
248
+ tx,
249
+ client,
250
+ owner,
251
+ coinType,
252
+ amount,
253
+ options.allowSwapAll ?? true,
254
+ options.mergeCache
255
+ );
256
+ }
257
+ const balanceResp = await client.core.getBalance({ owner, coinType });
258
+ const totalBalance = BigInt(balanceResp.balance.balance);
259
+ if (totalBalance === 0n) {
260
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No balance found for ${coinType}`);
261
+ }
262
+ const allowSwapAll = options.allowSwapAll ?? true;
263
+ if (amount !== "all" && amount > totalBalance && !allowSwapAll) {
264
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance for ${coinType}`, {
265
+ available: totalBalance.toString(),
266
+ required: amount.toString()
267
+ });
268
+ }
269
+ const requested = amount === "all" ? totalBalance : amount;
270
+ const swapAll = amount === "all" || requested >= totalBalance;
271
+ const effectiveAmount = swapAll ? totalBalance : requested;
272
+ const coin = transactions.coinWithBalance({ type: coinType, balance: effectiveAmount })(tx);
273
+ return { coin, effectiveAmount, swapAll };
274
+ }
275
+ async function selectCoinObjectsOnly(tx, client, owner, coinType, amount, allowSwapAll, mergeCache) {
276
+ const cached = mergeCache?.get(coinType);
277
+ if (cached) {
278
+ const requested2 = amount === "all" ? cached.remaining : amount;
279
+ if (cached.remaining === 0n || requested2 > cached.remaining) {
280
+ throw new exports.T2000Error(
281
+ "ADDRESS_BALANCE_UNSPONSORABLE",
282
+ `Not enough ${coinType} in coin objects to cover all legs of this sponsored bundle. The remaining funds are in your address balance, which sponsored transactions can't access yet.`,
283
+ { remaining: cached.remaining.toString(), requested: requested2.toString(), coinType }
284
+ );
285
+ }
286
+ const swapAll2 = amount === "all" || requested2 >= cached.remaining;
287
+ const effectiveAmount2 = swapAll2 ? cached.remaining : requested2;
288
+ const coin2 = swapAll2 ? cached.primary : tx.splitCoins(cached.primary, [effectiveAmount2])[0];
289
+ cached.remaining -= effectiveAmount2;
290
+ return { coin: coin2, effectiveAmount: effectiveAmount2, swapAll: swapAll2 };
291
+ }
292
+ const objects = [];
293
+ let coinObjectTotal = 0n;
294
+ let cursor;
295
+ let hasNext = true;
296
+ while (hasNext) {
297
+ const page = await client.core.listCoins({ owner, coinType, cursor: cursor ?? void 0 });
298
+ for (const c of page.objects) {
299
+ objects.push({ objectId: c.objectId, balance: BigInt(c.balance) });
300
+ coinObjectTotal += BigInt(c.balance);
301
+ }
302
+ cursor = page.cursor;
303
+ hasNext = page.hasNextPage;
304
+ }
305
+ const unsponsorable = () => new exports.T2000Error(
306
+ "ADDRESS_BALANCE_UNSPONSORABLE",
307
+ `These funds are in your address balance, which sponsored transactions can't access yet. (Funds received via gasless transfers land there.) This will work once the gas sponsor adds address-balance support.`,
308
+ { coinObjectTotal: coinObjectTotal.toString(), coinType }
309
+ );
310
+ if (coinObjectTotal === 0n) {
311
+ throw unsponsorable();
312
+ }
313
+ const requested = amount === "all" ? coinObjectTotal : amount;
314
+ if (requested > coinObjectTotal) {
315
+ if (allowSwapAll && amount === "all") ; else {
316
+ throw unsponsorable();
317
+ }
318
+ }
319
+ const swapAll = amount === "all" || requested >= coinObjectTotal;
320
+ const effectiveAmount = swapAll ? coinObjectTotal : requested;
321
+ const [first, ...rest] = objects;
322
+ const primary = tx.object(first.objectId);
323
+ if (rest.length > 0) {
324
+ tx.mergeCoins(
325
+ primary,
326
+ rest.map((o) => tx.object(o.objectId))
327
+ );
328
+ }
329
+ const coin = swapAll ? primary : tx.splitCoins(primary, [effectiveAmount])[0];
330
+ mergeCache?.set(coinType, {
331
+ primary,
332
+ remaining: coinObjectTotal - effectiveAmount
333
+ });
334
+ return { coin, effectiveAmount, swapAll };
335
+ }
336
+ async function selectSuiCoin(tx, client, owner, amountMist, sponsoredContext, mergeCache) {
337
+ if (sponsoredContext) {
338
+ const { SUI_TYPE: SUI_TYPE2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
339
+ return selectCoinObjectsOnly(tx, client, owner, SUI_TYPE2, amountMist, false, mergeCache);
340
+ }
341
+ const [coin] = tx.splitCoins(tx.gas, [amountMist]);
342
+ return { coin, effectiveAmount: amountMist, swapAll: false };
343
+ }
344
+ var init_coinSelection = __esm({
345
+ "src/wallet/coinSelection.ts"() {
346
+ init_errors();
347
+ }
348
+ });
349
+
191
350
  // src/wallet/keypairSigner.ts
192
351
  var KeypairSigner = class {
193
352
  constructor(keypair) {
@@ -247,12 +406,8 @@ var ZkLoginSigner = class {
247
406
  }
248
407
  };
249
408
 
250
- // src/mpp-cost.ts
251
- function parseChallengeAmount(challenge) {
252
- const raw = challenge?.request?.amount;
253
- const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : Number.NaN;
254
- return Number.isFinite(n) ? n : void 0;
255
- }
409
+ // src/wallet/pay.ts
410
+ init_errors();
256
411
 
257
412
  // src/wallet/executeTx.ts
258
413
  async function executeTx(client, signer, buildTx, options = {}) {
@@ -280,45 +435,122 @@ async function executeTx(client, signer, buildTx, options = {}) {
280
435
  // src/wallet/pay.ts
281
436
  async function payWithMpp(args) {
282
437
  const { signer, client, options } = args;
283
- const { Mppx } = await import('mppx/client');
284
- const { sui, USDC } = await import('@suimpp/mpp/client');
285
- const { SuiGrpcClient: SuiGrpcClient2 } = await import('@mysten/sui/grpc');
286
- const signerAddress = signer.getAddress();
287
- let paymentDigest;
288
- let gasCostSui = 0;
289
- let chargedAmount;
290
- const network = client.network === "testnet" ? "testnet" : "mainnet";
291
- const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
292
- const grpcClient = new SuiGrpcClient2({ baseUrl: grpcBaseUrl, network });
293
- const mppx = Mppx.create({
294
- polyfill: false,
295
- onChallenge: async (challenge) => {
296
- const parsed = parseChallengeAmount(challenge);
297
- if (parsed !== void 0) chargedAmount = parsed;
298
- return void 0;
299
- },
300
- methods: [sui({
301
- client,
302
- currency: USDC,
303
- signer: {
304
- toSuiAddress: () => signerAddress,
305
- signPersonalMessage: (bytes) => signer.signPersonalMessage(bytes)
306
- },
307
- execute: async (tx) => {
308
- const result = await executeTx(client, signer, () => tx, { buildClient: grpcClient });
309
- paymentDigest = result.digest;
310
- gasCostSui = result.gasCostSui;
311
- return { digest: result.digest };
312
- }
313
- })]
314
- });
315
438
  const method = (options.method ?? "GET").toUpperCase();
316
439
  const canHaveBody = method !== "GET" && method !== "HEAD";
317
- const response = await mppx.fetch(options.url, {
440
+ const reqInit = {
318
441
  method,
319
442
  headers: options.headers,
320
443
  body: canHaveBody ? options.body : void 0
444
+ };
445
+ const probe = await fetch(options.url, reqInit);
446
+ if (probe.status !== 402) {
447
+ return finalize(probe, { paid: false });
448
+ }
449
+ const requirements = await pickSuiExactRequirements(probe, client.network);
450
+ if (!requirements) {
451
+ throw new exports.T2000Error(
452
+ "FACILITATOR_REJECTION",
453
+ `Endpoint returned 402 without an x402 'exact' / sui:${client.network} payment requirement. This SDK only speaks the x402 dialect.`
454
+ );
455
+ }
456
+ return payViaX402({ signer, client, options, reqInit, requirements });
457
+ }
458
+ async function pickSuiExactRequirements(response, network) {
459
+ try {
460
+ const body = await response.clone().json();
461
+ const want = `sui:${network === "testnet" ? "testnet" : "mainnet"}`;
462
+ return body.accepts?.find((a) => a.scheme === "exact" && a.network === want);
463
+ } catch {
464
+ return void 0;
465
+ }
466
+ }
467
+ async function payViaX402(args) {
468
+ const { signer, client, options, reqInit, requirements } = args;
469
+ const { buildX402SignedPayment, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER } = await import('@suimpp/mpp/x402');
470
+ const amountRaw = BigInt(requirements.maxAmountRequired);
471
+ const migrationGasSui = await ensureAddressBalanceCovers({
472
+ signer,
473
+ client,
474
+ asset: requirements.asset,
475
+ amountRaw
476
+ });
477
+ const signerAdapter = {
478
+ toSuiAddress: () => signer.getAddress(),
479
+ signTransaction: (bytes) => signer.signTransaction(bytes)
480
+ };
481
+ const { header } = await buildX402SignedPayment({ requirements, signer: signerAdapter });
482
+ const res = await fetch(options.url, {
483
+ ...reqInit,
484
+ headers: { ...options.headers ?? {}, [X402_PAYMENT_HEADER]: header }
321
485
  });
486
+ const settleHeader = res.headers.get(X402_PAYMENT_RESPONSE_HEADER);
487
+ const paid = !!settleHeader;
488
+ let digest;
489
+ if (settleHeader) {
490
+ try {
491
+ digest = JSON.parse(new TextDecoder().decode(utils.fromBase64(settleHeader))).transaction;
492
+ } catch {
493
+ digest = void 0;
494
+ }
495
+ }
496
+ const result = await finalize(res, { paid });
497
+ if (!paid) return { ...result, dialect: "x402" };
498
+ return {
499
+ ...result,
500
+ dialect: "x402",
501
+ cost: atomicToHuman(amountRaw, await assetDecimals(requirements.asset)),
502
+ gasCostSui: migrationGasSui,
503
+ receipt: digest ? { reference: digest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : result.receipt
504
+ };
505
+ }
506
+ async function ensureAddressBalanceCovers(args) {
507
+ const { signer, client, asset, amountRaw } = args;
508
+ const owner = signer.getAddress();
509
+ const balanceResp = await client.core.getBalance({ owner, coinType: asset });
510
+ const total = BigInt(balanceResp.balance.balance);
511
+ if (total < amountRaw) {
512
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} to pay`, {
513
+ available: total.toString(),
514
+ required: amountRaw.toString()
515
+ });
516
+ }
517
+ let coinSum = 0n;
518
+ let cursor;
519
+ let hasNext = true;
520
+ while (hasNext) {
521
+ const page = await client.core.listCoins({ owner, coinType: asset, cursor: cursor ?? void 0 });
522
+ for (const c of page.objects) coinSum += BigInt(c.balance);
523
+ cursor = page.cursor;
524
+ hasNext = page.hasNextPage;
525
+ }
526
+ const addressBalance = total - coinSum;
527
+ if (addressBalance >= amountRaw) return 0;
528
+ const shortfall = amountRaw - addressBalance;
529
+ const { Transaction: Transaction2 } = await import('@mysten/sui/transactions');
530
+ const { selectAndSplitCoin: selectAndSplitCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
531
+ const grpcClient = await makeGrpcBuildClient(client);
532
+ const migration = await executeTx(
533
+ client,
534
+ signer,
535
+ async () => {
536
+ const tx = new Transaction2();
537
+ const { coin } = await selectAndSplitCoin2(tx, client, owner, asset, shortfall, {
538
+ sponsoredContext: true,
539
+ // coin-objects-only — never the address balance
540
+ allowSwapAll: false
541
+ });
542
+ tx.moveCall({
543
+ target: "0x2::coin::send_funds",
544
+ typeArguments: [asset],
545
+ arguments: [coin, tx.pure.address(owner)]
546
+ });
547
+ return tx;
548
+ },
549
+ { buildClient: grpcClient }
550
+ );
551
+ return migration.gasCostSui;
552
+ }
553
+ async function finalize(response, opts) {
322
554
  const contentType = response.headers.get("content-type") ?? "";
323
555
  let body;
324
556
  try {
@@ -326,15 +558,25 @@ async function payWithMpp(args) {
326
558
  } catch {
327
559
  body = null;
328
560
  }
329
- const paid = !!paymentDigest;
330
- return {
331
- status: response.status,
332
- body,
333
- paid,
334
- cost: paid ? chargedAmount ?? options.maxPrice ?? void 0 : void 0,
335
- gasCostSui: paid ? gasCostSui : void 0,
336
- receipt: paymentDigest ? { reference: paymentDigest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
337
- };
561
+ return { status: response.status, body, paid: opts.paid };
562
+ }
563
+ async function makeGrpcBuildClient(client) {
564
+ const { SuiGrpcClient: SuiGrpcClient2 } = await import('@mysten/sui/grpc');
565
+ const network = client.network === "testnet" ? "testnet" : "mainnet";
566
+ const baseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
567
+ return new SuiGrpcClient2({ baseUrl, network });
568
+ }
569
+ function atomicToHuman(raw, decimals) {
570
+ return Number(raw) / 10 ** decimals;
571
+ }
572
+ async function assetDecimals(coinType) {
573
+ try {
574
+ const { getDecimalsForCoinType: getDecimalsForCoinType2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
575
+ const d = getDecimalsForCoinType2(coinType);
576
+ return typeof d === "number" ? d : 6;
577
+ } catch {
578
+ return 6;
579
+ }
338
580
  }
339
581
 
340
582
  // src/browser.ts
@@ -676,7 +918,7 @@ function toBase64(bytes) {
676
918
  for (const byte of bytes) binary += String.fromCharCode(byte);
677
919
  return btoa(binary);
678
920
  }
679
- function fromBase64(b64) {
921
+ function fromBase642(b64) {
680
922
  const binary = atob(b64);
681
923
  const bytes = new Uint8Array(binary.length);
682
924
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
@@ -826,7 +1068,7 @@ exports.findSwapRoute = findSwapRoute;
826
1068
  exports.formatAssetAmount = formatAssetAmount;
827
1069
  exports.formatSui = formatSui;
828
1070
  exports.formatUsd = formatUsd;
829
- exports.fromBase64 = fromBase64;
1071
+ exports.fromBase64 = fromBase642;
830
1072
  exports.getDecimals = getDecimals;
831
1073
  exports.getDecimalsForCoinType = getDecimalsForCoinType;
832
1074
  exports.mapMoveAbortCode = mapMoveAbortCode;