@t2000/sdk 0.2.6 → 0.3.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/README.md CHANGED
@@ -111,6 +111,14 @@ const agent = T2000.fromPrivateKey('suiprivkey1q...');
111
111
  | `agent.deposit()` | Wallet address + funding instructions | `DepositInfo` |
112
112
  | `agent.history({ limit? })` | Transaction history (default: all) | `TransactionRecord[]` |
113
113
 
114
+ ### Sentinel Methods
115
+
116
+ | Method | Description | Returns |
117
+ |--------|-------------|---------|
118
+ | `agent.sentinelList()` | List active sentinels with prize pools | `SentinelAgent[]` |
119
+ | `agent.sentinelInfo(id)` | Get sentinel details (from API or on-chain) | `SentinelAgent` |
120
+ | `agent.sentinelAttack(id, prompt, fee?)` | Full attack flow: request → TEE → settle | `SentinelAttackResult` |
121
+
114
122
  ### Key Management
115
123
 
116
124
  ```typescript
package/dist/index.cjs CHANGED
@@ -12,6 +12,7 @@ var os = require('os');
12
12
  var transactions = require('@mysten/sui/transactions');
13
13
  var lending = require('@naviprotocol/lending');
14
14
  var suiClmmSdk = require('@cetusprotocol/sui-clmm-sdk');
15
+ var bcs = require('@mysten/sui/bcs');
15
16
 
16
17
  // src/t2000.ts
17
18
 
@@ -39,14 +40,26 @@ var SUPPORTED_ASSETS = {
39
40
  symbol: "SUI"
40
41
  }
41
42
  };
42
- process.env.T2000_PACKAGE_ID ?? "0x51c44bb2ad3ba608cf9adbc6e37ee67268ef9313a4ff70957d4c6e7955dc7eef";
43
- process.env.T2000_CONFIG_ID ?? "0xd30408960ac38eced670acc102df9e178b5b46b3a8c0e96a53ec2fd3f39b5936";
44
- process.env.T2000_TREASURY_ID ?? "0x2398c2759cfce40f1b0f2b3e524eeba9e8f6428fcb1d1e39235dd042d48defc8";
43
+ var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
44
+ var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
45
+ var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
45
46
  var DEFAULT_NETWORK = "mainnet";
46
47
  var DEFAULT_RPC_URL = "https://fullnode.mainnet.sui.io:443";
47
48
  var DEFAULT_KEY_PATH = "~/.t2000/wallet.key";
48
49
  var API_BASE_URL = process.env.T2000_API_URL ?? "https://api.t2000.ai";
49
50
  var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
51
+ var SENTINEL = {
52
+ PACKAGE: "0x88b83f36dafcd5f6dcdcf1d2cb5889b03f61264ab3cee9cae35db7aa940a21b7",
53
+ AGENT_REGISTRY: "0xc47564f5f14c12b31e0dfa1a3dc99a6380a1edf8929c28cb0eaa3359c8db36ac",
54
+ ENCLAVE: "0xfb1261aeb9583514cb1341a548a5ec12d1231bd96af22215f1792617a93e1213",
55
+ PROTOCOL_CONFIG: "0x2fa4fa4a1dd0498612304635ff9334e1b922e78af325000e9d9c0e88adea459f",
56
+ TEE_API: "https://app.suisentinel.xyz/api/consume-prompt",
57
+ SENTINELS_API: "https://api.suisentinel.xyz/agents/mainnet",
58
+ RANDOM: "0x8",
59
+ MIN_FEE_MIST: 100000000n,
60
+ // 0.1 SUI
61
+ MAX_PROMPT_TOKENS: 600
62
+ };
50
63
 
51
64
  // src/errors.ts
52
65
  var T2000Error = class extends Error {
@@ -89,7 +102,8 @@ function mapMoveAbortCode(code) {
89
102
  6: "Not authorized",
90
103
  7: "Package version mismatch \u2014 upgrade required",
91
104
  8: "Timelock is active \u2014 wait for expiry",
92
- 9: "No pending change to execute"
105
+ 9: "No pending change to execute",
106
+ 10: "Already at current version"
93
107
  };
94
108
  return abortMessages[code] ?? `Move abort code: ${code}`;
95
109
  }
@@ -360,6 +374,61 @@ function inferAction(txBlock) {
360
374
  if (kind === "ProgrammableTransaction") return "transaction";
361
375
  return kind ?? "unknown";
362
376
  }
377
+
378
+ // src/protocols/protocolFee.ts
379
+ var FEE_RATES = {
380
+ save: SAVE_FEE_BPS,
381
+ swap: SWAP_FEE_BPS,
382
+ borrow: BORROW_FEE_BPS
383
+ };
384
+ var OP_CODES = {
385
+ save: 0,
386
+ swap: 1,
387
+ borrow: 2
388
+ };
389
+ function calculateFee(operation, amount) {
390
+ const bps = FEE_RATES[operation];
391
+ const feeAmount = amount * Number(bps) / Number(BPS_DENOMINATOR);
392
+ const rawAmount = usdcToRaw(feeAmount);
393
+ return {
394
+ amount: feeAmount,
395
+ asset: "USDC",
396
+ rate: Number(bps) / Number(BPS_DENOMINATOR),
397
+ rawAmount
398
+ };
399
+ }
400
+ function addCollectFeeToTx(tx, paymentCoin, operation) {
401
+ const bps = FEE_RATES[operation];
402
+ if (bps <= 0n) return;
403
+ tx.moveCall({
404
+ target: `${T2000_PACKAGE_ID}::treasury::collect_fee`,
405
+ typeArguments: [SUPPORTED_ASSETS.USDC.type],
406
+ arguments: [
407
+ tx.object(T2000_TREASURY_ID),
408
+ tx.object(T2000_CONFIG_ID),
409
+ paymentCoin,
410
+ tx.pure.u8(OP_CODES[operation])
411
+ ]
412
+ });
413
+ }
414
+ async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest) {
415
+ try {
416
+ await fetch(`${API_BASE_URL}/api/fees`, {
417
+ method: "POST",
418
+ headers: { "Content-Type": "application/json" },
419
+ body: JSON.stringify({
420
+ agentAddress,
421
+ operation,
422
+ feeAmount: feeAmount.toString(),
423
+ feeRate: feeRate.toString(),
424
+ txDigest
425
+ })
426
+ });
427
+ } catch {
428
+ }
429
+ }
430
+
431
+ // src/protocols/navi.ts
363
432
  var ENV = { env: "prod" };
364
433
  var USDC_TYPE = SUPPORTED_ASSETS.USDC.type;
365
434
  var RATE_DECIMALS = 27;
@@ -402,7 +471,7 @@ async function updateOracle(tx, client, address) {
402
471
  } catch {
403
472
  }
404
473
  }
405
- async function buildSaveTx(client, address, amount) {
474
+ async function buildSaveTx(client, address, amount, options = {}) {
406
475
  const rawAmount = Number(usdcToRaw(amount));
407
476
  const coins = await lending.getCoins(address, { coinType: USDC_TYPE, client });
408
477
  if (!coins || coins.length === 0) {
@@ -411,6 +480,9 @@ async function buildSaveTx(client, address, amount) {
411
480
  const tx = new transactions.Transaction();
412
481
  tx.setSender(address);
413
482
  const coinObj = lending.mergeCoinsPTB(tx, coins, { balance: rawAmount });
483
+ if (options.collectFee) {
484
+ addCollectFeeToTx(tx, coinObj, "save");
485
+ }
414
486
  await lending.depositCoinPTB(tx, USDC_TYPE, coinObj, ENV);
415
487
  return tx;
416
488
  }
@@ -428,12 +500,15 @@ async function buildWithdrawTx(client, address, amount) {
428
500
  tx.transferObjects([withdrawnCoin], address);
429
501
  return { tx, effectiveAmount };
430
502
  }
431
- async function buildBorrowTx(client, address, amount) {
503
+ async function buildBorrowTx(client, address, amount, options = {}) {
432
504
  const rawAmount = Number(usdcToRaw(amount));
433
505
  const tx = new transactions.Transaction();
434
506
  tx.setSender(address);
435
507
  await updateOracle(tx, client, address);
436
508
  const borrowedCoin = await lending.borrowCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
509
+ if (options.collectFee) {
510
+ addCollectFeeToTx(tx, borrowedCoin, "borrow");
511
+ }
437
512
  tx.transferObjects([borrowedCoin], address);
438
513
  return tx;
439
514
  }
@@ -641,40 +716,6 @@ async function getSwapQuote(client, fromAsset, toAsset, amount) {
641
716
  }
642
717
  }
643
718
 
644
- // src/protocols/protocolFee.ts
645
- var FEE_RATES = {
646
- save: SAVE_FEE_BPS,
647
- swap: SWAP_FEE_BPS,
648
- borrow: BORROW_FEE_BPS
649
- };
650
- function calculateFee(operation, amount) {
651
- const bps = FEE_RATES[operation];
652
- const feeAmount = amount * Number(bps) / Number(BPS_DENOMINATOR);
653
- const rawAmount = usdcToRaw(feeAmount);
654
- return {
655
- amount: feeAmount,
656
- asset: "USDC",
657
- rate: Number(bps) / Number(BPS_DENOMINATOR),
658
- rawAmount
659
- };
660
- }
661
- async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest) {
662
- try {
663
- await fetch(`${API_BASE_URL}/api/fees`, {
664
- method: "POST",
665
- headers: { "Content-Type": "application/json" },
666
- body: JSON.stringify({
667
- agentAddress,
668
- operation,
669
- feeAmount: feeAmount.toString(),
670
- feeRate: feeRate.toString(),
671
- txDigest
672
- })
673
- });
674
- } catch {
675
- }
676
- }
677
-
678
719
  // src/protocols/yieldTracker.ts
679
720
  async function getEarnings(client, keypair) {
680
721
  const hf = await getHealthFactor(client, keypair);
@@ -701,6 +742,190 @@ async function getFundStatus(client, keypair) {
701
742
  projectedMonthly: earnings.dailyEarning * 30
702
743
  };
703
744
  }
745
+ function mapAgent(raw) {
746
+ return {
747
+ id: raw.agent_id,
748
+ objectId: raw.agent_object_id,
749
+ name: raw.agent_name,
750
+ model: raw.model ?? "unknown",
751
+ systemPrompt: raw.prompt,
752
+ attackFee: BigInt(raw.cost_per_message),
753
+ prizePool: BigInt(raw.total_balance),
754
+ totalAttacks: raw.total_attacks,
755
+ successfulBreaches: raw.successful_breaches ?? 0,
756
+ state: raw.state
757
+ };
758
+ }
759
+ async function listSentinels() {
760
+ const res = await fetch(SENTINEL.SENTINELS_API);
761
+ if (!res.ok) {
762
+ throw new T2000Error("SENTINEL_API_ERROR", `Sentinel API returned ${res.status}`);
763
+ }
764
+ const data = await res.json();
765
+ if (!Array.isArray(data.agents)) {
766
+ throw new T2000Error("SENTINEL_API_ERROR", "Unexpected API response shape");
767
+ }
768
+ return data.agents.filter((a) => a.state === "active").map(mapAgent);
769
+ }
770
+ async function getSentinelInfo(client, sentinelObjectId) {
771
+ const agents = await listSentinels();
772
+ const match = agents.find((a) => a.objectId === sentinelObjectId || a.id === sentinelObjectId);
773
+ if (match) return match;
774
+ const obj = await client.getObject({
775
+ id: sentinelObjectId,
776
+ options: { showContent: true, showType: true }
777
+ });
778
+ if (!obj.data) {
779
+ throw new T2000Error("SENTINEL_NOT_FOUND", `Sentinel ${sentinelObjectId} not found on-chain`);
780
+ }
781
+ const content = obj.data.content;
782
+ if (!content || content.dataType !== "moveObject") {
783
+ throw new T2000Error("SENTINEL_NOT_FOUND", `Object ${sentinelObjectId} is not a Move object`);
784
+ }
785
+ const fields = content.fields;
786
+ return {
787
+ id: fields.id?.id ?? sentinelObjectId,
788
+ objectId: sentinelObjectId,
789
+ name: fields.name ?? "Unknown",
790
+ model: fields.model ?? "unknown",
791
+ systemPrompt: fields.system_prompt ?? "",
792
+ attackFee: BigInt(fields.cost_per_message ?? "0"),
793
+ prizePool: BigInt(fields.balance ?? "0"),
794
+ totalAttacks: Number(fields.total_attacks ?? "0"),
795
+ successfulBreaches: Number(fields.successful_breaches ?? "0"),
796
+ state: fields.state ?? "unknown"
797
+ };
798
+ }
799
+ async function requestAttack(client, signer, sentinelObjectId, feeMist) {
800
+ if (feeMist < SENTINEL.MIN_FEE_MIST) {
801
+ throw new T2000Error("INVALID_AMOUNT", `Attack fee must be at least 0.1 SUI (${SENTINEL.MIN_FEE_MIST} MIST)`);
802
+ }
803
+ const tx = new transactions.Transaction();
804
+ const [coin] = tx.splitCoins(tx.gas, [Number(feeMist)]);
805
+ const [attack2] = tx.moveCall({
806
+ target: `${SENTINEL.PACKAGE}::sentinel::request_attack`,
807
+ arguments: [
808
+ tx.object(SENTINEL.AGENT_REGISTRY),
809
+ tx.object(sentinelObjectId),
810
+ tx.object(SENTINEL.PROTOCOL_CONFIG),
811
+ coin,
812
+ tx.object(SENTINEL.RANDOM),
813
+ tx.object(CLOCK_ID)
814
+ ]
815
+ });
816
+ const address = signer.toSuiAddress();
817
+ tx.transferObjects([attack2], address);
818
+ const result = await client.signAndExecuteTransaction({
819
+ signer,
820
+ transaction: tx,
821
+ options: { showObjectChanges: true, showEffects: true }
822
+ });
823
+ await client.waitForTransaction({ digest: result.digest });
824
+ const attackObj = result.objectChanges?.find(
825
+ (c) => c.type === "created" && c.objectType?.includes("::sentinel::Attack")
826
+ );
827
+ const attackObjectId = attackObj && "objectId" in attackObj ? attackObj.objectId : void 0;
828
+ if (!attackObjectId) {
829
+ throw new T2000Error("SENTINEL_TX_FAILED", "Attack object was not created \u2014 transaction may have failed");
830
+ }
831
+ return { attackObjectId, digest: result.digest };
832
+ }
833
+ async function submitPrompt(agentId, attackObjectId, prompt) {
834
+ const res = await fetch(SENTINEL.TEE_API, {
835
+ method: "POST",
836
+ headers: { "Content-Type": "application/json" },
837
+ body: JSON.stringify({
838
+ agent_id: agentId,
839
+ attack_object_id: attackObjectId,
840
+ message: prompt
841
+ })
842
+ });
843
+ if (!res.ok) {
844
+ const body = await res.text().catch(() => "");
845
+ throw new T2000Error("SENTINEL_TEE_ERROR", `TEE returned ${res.status}: ${body.slice(0, 200)}`);
846
+ }
847
+ const raw = await res.json();
848
+ const envelope = raw.response ?? raw;
849
+ const data = envelope.data ?? envelope;
850
+ const signature = raw.signature ?? data.signature;
851
+ const timestampMs = envelope.timestamp_ms ?? data.timestamp_ms;
852
+ if (typeof signature !== "string") {
853
+ throw new T2000Error("SENTINEL_TEE_ERROR", "TEE response missing signature");
854
+ }
855
+ return {
856
+ success: data.success ?? data.is_success,
857
+ score: data.score,
858
+ agentResponse: data.agent_response,
859
+ juryResponse: data.jury_response,
860
+ funResponse: data.fun_response ?? "",
861
+ signature,
862
+ timestampMs
863
+ };
864
+ }
865
+ async function settleAttack(client, signer, sentinelObjectId, attackObjectId, prompt, verdict) {
866
+ const sigBytes = Array.from(Buffer.from(verdict.signature.replace(/^0x/, ""), "hex"));
867
+ const tx = new transactions.Transaction();
868
+ tx.moveCall({
869
+ target: `${SENTINEL.PACKAGE}::sentinel::consume_prompt`,
870
+ arguments: [
871
+ tx.object(SENTINEL.AGENT_REGISTRY),
872
+ tx.object(SENTINEL.PROTOCOL_CONFIG),
873
+ tx.object(sentinelObjectId),
874
+ tx.pure.bool(verdict.success),
875
+ tx.pure.string(verdict.agentResponse),
876
+ tx.pure.string(verdict.juryResponse),
877
+ tx.pure.string(verdict.funResponse),
878
+ tx.pure.string(prompt),
879
+ tx.pure.u8(verdict.score),
880
+ tx.pure.u64(verdict.timestampMs),
881
+ tx.pure(bcs.bcs.vector(bcs.bcs.u8()).serialize(sigBytes)),
882
+ tx.object(SENTINEL.ENCLAVE),
883
+ tx.object(attackObjectId),
884
+ tx.object(CLOCK_ID)
885
+ ]
886
+ });
887
+ const result = await client.signAndExecuteTransaction({
888
+ signer,
889
+ transaction: tx,
890
+ options: { showEffects: true }
891
+ });
892
+ await client.waitForTransaction({ digest: result.digest });
893
+ const txSuccess = result.effects?.status?.status === "success";
894
+ return { digest: result.digest, success: txSuccess };
895
+ }
896
+ async function attack(client, signer, sentinelId, prompt, feeMist) {
897
+ const sentinel = await getSentinelInfo(client, sentinelId);
898
+ const fee = feeMist ?? sentinel.attackFee;
899
+ if (fee < SENTINEL.MIN_FEE_MIST) {
900
+ throw new T2000Error("INVALID_AMOUNT", `Attack fee must be at least 0.1 SUI`);
901
+ }
902
+ const { attackObjectId, digest: requestTx } = await requestAttack(
903
+ client,
904
+ signer,
905
+ sentinel.objectId,
906
+ fee
907
+ );
908
+ const verdict = await submitPrompt(sentinel.id, attackObjectId, prompt);
909
+ const { digest: settleTx } = await settleAttack(
910
+ client,
911
+ signer,
912
+ sentinel.objectId,
913
+ attackObjectId,
914
+ prompt,
915
+ verdict
916
+ );
917
+ const won = verdict.success && verdict.score >= 70;
918
+ return {
919
+ attackObjectId,
920
+ sentinelId: sentinel.id,
921
+ prompt,
922
+ verdict,
923
+ requestTx,
924
+ settleTx,
925
+ won,
926
+ feePaid: Number(fee) / Number(MIST_PER_SUI)
927
+ };
928
+ }
704
929
  function hasLeadingZeroBits(hash, bits) {
705
930
  const fullBytes = Math.floor(bits / 8);
706
931
  const remainingBits = bits % 8;
@@ -1083,12 +1308,12 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1083
1308
  const gasResult = await executeWithGas(
1084
1309
  this.client,
1085
1310
  this.keypair,
1086
- () => buildSaveTx(this.client, this._address, saveAmount)
1311
+ () => buildSaveTx(this.client, this._address, saveAmount, { collectFee: true })
1087
1312
  );
1088
1313
  const rates = await getRates(this.client);
1089
1314
  reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
1090
1315
  this.emitBalanceChange("USDC", saveAmount, "save", gasResult.digest);
1091
- let savingsBalance = saveAmount - fee.amount;
1316
+ let savingsBalance = saveAmount;
1092
1317
  try {
1093
1318
  const positions = await this.positions();
1094
1319
  savingsBalance = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
@@ -1172,7 +1397,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1172
1397
  const gasResult = await executeWithGas(
1173
1398
  this.client,
1174
1399
  this.keypair,
1175
- () => buildBorrowTx(this.client, this._address, borrowAmount)
1400
+ () => buildBorrowTx(this.client, this._address, borrowAmount, { collectFee: true })
1176
1401
  );
1177
1402
  const hf = await getHealthFactor(this.client, this.keypair);
1178
1403
  reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
@@ -1318,6 +1543,16 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1318
1543
  async fundStatus() {
1319
1544
  return getFundStatus(this.client, this.keypair);
1320
1545
  }
1546
+ // -- Sentinel --
1547
+ async sentinelList() {
1548
+ return listSentinels();
1549
+ }
1550
+ async sentinelInfo(id) {
1551
+ return getSentinelInfo(this.client, id);
1552
+ }
1553
+ async sentinelAttack(id, prompt, fee) {
1554
+ return attack(this.client, this.keypair, id, prompt, fee);
1555
+ }
1321
1556
  // -- Helpers --
1322
1557
  emitBalanceChange(asset, amount, cause, tx) {
1323
1558
  this.emit("balanceChange", { asset, previous: 0, current: 0, cause, tx });
@@ -1421,11 +1656,13 @@ exports.BPS_DENOMINATOR = BPS_DENOMINATOR;
1421
1656
  exports.CLOCK_ID = CLOCK_ID;
1422
1657
  exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
1423
1658
  exports.MIST_PER_SUI = MIST_PER_SUI;
1659
+ exports.SENTINEL = SENTINEL;
1424
1660
  exports.SUI_DECIMALS = SUI_DECIMALS;
1425
1661
  exports.SUPPORTED_ASSETS = SUPPORTED_ASSETS;
1426
1662
  exports.T2000 = T2000;
1427
1663
  exports.T2000Error = T2000Error;
1428
1664
  exports.USDC_DECIMALS = USDC_DECIMALS;
1665
+ exports.addCollectFeeToTx = addCollectFeeToTx;
1429
1666
  exports.calculateFee = calculateFee;
1430
1667
  exports.executeAutoTopUp = executeAutoTopUp;
1431
1668
  exports.executeWithGas = executeWithGas;
@@ -1437,17 +1674,23 @@ exports.getAddress = getAddress;
1437
1674
  exports.getGasStatus = getGasStatus;
1438
1675
  exports.getPoolPrice = getPoolPrice;
1439
1676
  exports.getRates = getRates;
1677
+ exports.getSentinelInfo = getSentinelInfo;
1440
1678
  exports.getSwapQuote = getSwapQuote;
1441
1679
  exports.keypairFromPrivateKey = keypairFromPrivateKey;
1680
+ exports.listSentinels = listSentinels;
1442
1681
  exports.loadKey = loadKey;
1443
1682
  exports.mapMoveAbortCode = mapMoveAbortCode;
1444
1683
  exports.mapWalletError = mapWalletError;
1445
1684
  exports.mistToSui = mistToSui;
1446
1685
  exports.rawToUsdc = rawToUsdc;
1686
+ exports.requestAttack = requestAttack;
1447
1687
  exports.saveKey = saveKey;
1688
+ exports.sentinelAttack = attack;
1689
+ exports.settleAttack = settleAttack;
1448
1690
  exports.shouldAutoTopUp = shouldAutoTopUp;
1449
1691
  exports.simulateTransaction = simulateTransaction;
1450
1692
  exports.solveHashcash = solveHashcash;
1693
+ exports.submitPrompt = submitPrompt;
1451
1694
  exports.suiToMist = suiToMist;
1452
1695
  exports.throwIfSimulationFailed = throwIfSimulationFailed;
1453
1696
  exports.truncateAddress = truncateAddress;