@t2000/sdk 0.2.6 → 0.2.7

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
 
@@ -47,6 +48,18 @@ 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 {
@@ -701,6 +714,190 @@ async function getFundStatus(client, keypair) {
701
714
  projectedMonthly: earnings.dailyEarning * 30
702
715
  };
703
716
  }
717
+ function mapAgent(raw) {
718
+ return {
719
+ id: raw.agent_id,
720
+ objectId: raw.agent_object_id,
721
+ name: raw.agent_name,
722
+ model: raw.model ?? "unknown",
723
+ systemPrompt: raw.prompt,
724
+ attackFee: BigInt(raw.cost_per_message),
725
+ prizePool: BigInt(raw.total_balance),
726
+ totalAttacks: raw.total_attacks,
727
+ successfulBreaches: raw.successful_breaches ?? 0,
728
+ state: raw.state
729
+ };
730
+ }
731
+ async function listSentinels() {
732
+ const res = await fetch(SENTINEL.SENTINELS_API);
733
+ if (!res.ok) {
734
+ throw new T2000Error("SENTINEL_API_ERROR", `Sentinel API returned ${res.status}`);
735
+ }
736
+ const data = await res.json();
737
+ if (!Array.isArray(data.agents)) {
738
+ throw new T2000Error("SENTINEL_API_ERROR", "Unexpected API response shape");
739
+ }
740
+ return data.agents.filter((a) => a.state === "active").map(mapAgent);
741
+ }
742
+ async function getSentinelInfo(client, sentinelObjectId) {
743
+ const agents = await listSentinels();
744
+ const match = agents.find((a) => a.objectId === sentinelObjectId || a.id === sentinelObjectId);
745
+ if (match) return match;
746
+ const obj = await client.getObject({
747
+ id: sentinelObjectId,
748
+ options: { showContent: true, showType: true }
749
+ });
750
+ if (!obj.data) {
751
+ throw new T2000Error("SENTINEL_NOT_FOUND", `Sentinel ${sentinelObjectId} not found on-chain`);
752
+ }
753
+ const content = obj.data.content;
754
+ if (!content || content.dataType !== "moveObject") {
755
+ throw new T2000Error("SENTINEL_NOT_FOUND", `Object ${sentinelObjectId} is not a Move object`);
756
+ }
757
+ const fields = content.fields;
758
+ return {
759
+ id: fields.id?.id ?? sentinelObjectId,
760
+ objectId: sentinelObjectId,
761
+ name: fields.name ?? "Unknown",
762
+ model: fields.model ?? "unknown",
763
+ systemPrompt: fields.system_prompt ?? "",
764
+ attackFee: BigInt(fields.cost_per_message ?? "0"),
765
+ prizePool: BigInt(fields.balance ?? "0"),
766
+ totalAttacks: Number(fields.total_attacks ?? "0"),
767
+ successfulBreaches: Number(fields.successful_breaches ?? "0"),
768
+ state: fields.state ?? "unknown"
769
+ };
770
+ }
771
+ async function requestAttack(client, signer, sentinelObjectId, feeMist) {
772
+ if (feeMist < SENTINEL.MIN_FEE_MIST) {
773
+ throw new T2000Error("INVALID_AMOUNT", `Attack fee must be at least 0.1 SUI (${SENTINEL.MIN_FEE_MIST} MIST)`);
774
+ }
775
+ const tx = new transactions.Transaction();
776
+ const [coin] = tx.splitCoins(tx.gas, [Number(feeMist)]);
777
+ const [attack2] = tx.moveCall({
778
+ target: `${SENTINEL.PACKAGE}::sentinel::request_attack`,
779
+ arguments: [
780
+ tx.object(SENTINEL.AGENT_REGISTRY),
781
+ tx.object(sentinelObjectId),
782
+ tx.object(SENTINEL.PROTOCOL_CONFIG),
783
+ coin,
784
+ tx.object(SENTINEL.RANDOM),
785
+ tx.object(CLOCK_ID)
786
+ ]
787
+ });
788
+ const address = signer.toSuiAddress();
789
+ tx.transferObjects([attack2], address);
790
+ const result = await client.signAndExecuteTransaction({
791
+ signer,
792
+ transaction: tx,
793
+ options: { showObjectChanges: true, showEffects: true }
794
+ });
795
+ await client.waitForTransaction({ digest: result.digest });
796
+ const attackObj = result.objectChanges?.find(
797
+ (c) => c.type === "created" && c.objectType?.includes("::sentinel::Attack")
798
+ );
799
+ const attackObjectId = attackObj && "objectId" in attackObj ? attackObj.objectId : void 0;
800
+ if (!attackObjectId) {
801
+ throw new T2000Error("SENTINEL_TX_FAILED", "Attack object was not created \u2014 transaction may have failed");
802
+ }
803
+ return { attackObjectId, digest: result.digest };
804
+ }
805
+ async function submitPrompt(agentId, attackObjectId, prompt) {
806
+ const res = await fetch(SENTINEL.TEE_API, {
807
+ method: "POST",
808
+ headers: { "Content-Type": "application/json" },
809
+ body: JSON.stringify({
810
+ agent_id: agentId,
811
+ attack_object_id: attackObjectId,
812
+ message: prompt
813
+ })
814
+ });
815
+ if (!res.ok) {
816
+ const body = await res.text().catch(() => "");
817
+ throw new T2000Error("SENTINEL_TEE_ERROR", `TEE returned ${res.status}: ${body.slice(0, 200)}`);
818
+ }
819
+ const raw = await res.json();
820
+ const envelope = raw.response ?? raw;
821
+ const data = envelope.data ?? envelope;
822
+ const signature = raw.signature ?? data.signature;
823
+ const timestampMs = envelope.timestamp_ms ?? data.timestamp_ms;
824
+ if (typeof signature !== "string") {
825
+ throw new T2000Error("SENTINEL_TEE_ERROR", "TEE response missing signature");
826
+ }
827
+ return {
828
+ success: data.success ?? data.is_success,
829
+ score: data.score,
830
+ agentResponse: data.agent_response,
831
+ juryResponse: data.jury_response,
832
+ funResponse: data.fun_response ?? "",
833
+ signature,
834
+ timestampMs
835
+ };
836
+ }
837
+ async function settleAttack(client, signer, sentinelObjectId, attackObjectId, prompt, verdict) {
838
+ const sigBytes = Array.from(Buffer.from(verdict.signature.replace(/^0x/, ""), "hex"));
839
+ const tx = new transactions.Transaction();
840
+ tx.moveCall({
841
+ target: `${SENTINEL.PACKAGE}::sentinel::consume_prompt`,
842
+ arguments: [
843
+ tx.object(SENTINEL.AGENT_REGISTRY),
844
+ tx.object(SENTINEL.PROTOCOL_CONFIG),
845
+ tx.object(sentinelObjectId),
846
+ tx.pure.bool(verdict.success),
847
+ tx.pure.string(verdict.agentResponse),
848
+ tx.pure.string(verdict.juryResponse),
849
+ tx.pure.string(verdict.funResponse),
850
+ tx.pure.string(prompt),
851
+ tx.pure.u8(verdict.score),
852
+ tx.pure.u64(verdict.timestampMs),
853
+ tx.pure(bcs.bcs.vector(bcs.bcs.u8()).serialize(sigBytes)),
854
+ tx.object(SENTINEL.ENCLAVE),
855
+ tx.object(attackObjectId),
856
+ tx.object(CLOCK_ID)
857
+ ]
858
+ });
859
+ const result = await client.signAndExecuteTransaction({
860
+ signer,
861
+ transaction: tx,
862
+ options: { showEffects: true }
863
+ });
864
+ await client.waitForTransaction({ digest: result.digest });
865
+ const txSuccess = result.effects?.status?.status === "success";
866
+ return { digest: result.digest, success: txSuccess };
867
+ }
868
+ async function attack(client, signer, sentinelId, prompt, feeMist) {
869
+ const sentinel = await getSentinelInfo(client, sentinelId);
870
+ const fee = feeMist ?? sentinel.attackFee;
871
+ if (fee < SENTINEL.MIN_FEE_MIST) {
872
+ throw new T2000Error("INVALID_AMOUNT", `Attack fee must be at least 0.1 SUI`);
873
+ }
874
+ const { attackObjectId, digest: requestTx } = await requestAttack(
875
+ client,
876
+ signer,
877
+ sentinel.objectId,
878
+ fee
879
+ );
880
+ const verdict = await submitPrompt(sentinel.id, attackObjectId, prompt);
881
+ const { digest: settleTx } = await settleAttack(
882
+ client,
883
+ signer,
884
+ sentinel.objectId,
885
+ attackObjectId,
886
+ prompt,
887
+ verdict
888
+ );
889
+ const won = verdict.success && verdict.score >= 70;
890
+ return {
891
+ attackObjectId,
892
+ sentinelId: sentinel.id,
893
+ prompt,
894
+ verdict,
895
+ requestTx,
896
+ settleTx,
897
+ won,
898
+ feePaid: Number(fee) / Number(MIST_PER_SUI)
899
+ };
900
+ }
704
901
  function hasLeadingZeroBits(hash, bits) {
705
902
  const fullBytes = Math.floor(bits / 8);
706
903
  const remainingBits = bits % 8;
@@ -1318,6 +1515,16 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1318
1515
  async fundStatus() {
1319
1516
  return getFundStatus(this.client, this.keypair);
1320
1517
  }
1518
+ // -- Sentinel --
1519
+ async sentinelList() {
1520
+ return listSentinels();
1521
+ }
1522
+ async sentinelInfo(id) {
1523
+ return getSentinelInfo(this.client, id);
1524
+ }
1525
+ async sentinelAttack(id, prompt, fee) {
1526
+ return attack(this.client, this.keypair, id, prompt, fee);
1527
+ }
1321
1528
  // -- Helpers --
1322
1529
  emitBalanceChange(asset, amount, cause, tx) {
1323
1530
  this.emit("balanceChange", { asset, previous: 0, current: 0, cause, tx });
@@ -1421,6 +1628,7 @@ exports.BPS_DENOMINATOR = BPS_DENOMINATOR;
1421
1628
  exports.CLOCK_ID = CLOCK_ID;
1422
1629
  exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
1423
1630
  exports.MIST_PER_SUI = MIST_PER_SUI;
1631
+ exports.SENTINEL = SENTINEL;
1424
1632
  exports.SUI_DECIMALS = SUI_DECIMALS;
1425
1633
  exports.SUPPORTED_ASSETS = SUPPORTED_ASSETS;
1426
1634
  exports.T2000 = T2000;
@@ -1437,17 +1645,23 @@ exports.getAddress = getAddress;
1437
1645
  exports.getGasStatus = getGasStatus;
1438
1646
  exports.getPoolPrice = getPoolPrice;
1439
1647
  exports.getRates = getRates;
1648
+ exports.getSentinelInfo = getSentinelInfo;
1440
1649
  exports.getSwapQuote = getSwapQuote;
1441
1650
  exports.keypairFromPrivateKey = keypairFromPrivateKey;
1651
+ exports.listSentinels = listSentinels;
1442
1652
  exports.loadKey = loadKey;
1443
1653
  exports.mapMoveAbortCode = mapMoveAbortCode;
1444
1654
  exports.mapWalletError = mapWalletError;
1445
1655
  exports.mistToSui = mistToSui;
1446
1656
  exports.rawToUsdc = rawToUsdc;
1657
+ exports.requestAttack = requestAttack;
1447
1658
  exports.saveKey = saveKey;
1659
+ exports.sentinelAttack = attack;
1660
+ exports.settleAttack = settleAttack;
1448
1661
  exports.shouldAutoTopUp = shouldAutoTopUp;
1449
1662
  exports.simulateTransaction = simulateTransaction;
1450
1663
  exports.solveHashcash = solveHashcash;
1664
+ exports.submitPrompt = submitPrompt;
1451
1665
  exports.suiToMist = suiToMist;
1452
1666
  exports.throwIfSimulationFailed = throwIfSimulationFailed;
1453
1667
  exports.truncateAddress = truncateAddress;