@t2000/sdk 0.2.5 → 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 +96 -25
- package/dist/index.cjs +214 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -2
- package/dist/index.d.ts +60 -2
- package/dist/index.js +208 -1
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -24,7 +24,10 @@ yarn add @t2000/sdk
|
|
|
24
24
|
```typescript
|
|
25
25
|
import { T2000 } from '@t2000/sdk';
|
|
26
26
|
|
|
27
|
-
// Create
|
|
27
|
+
// Create a new bank account
|
|
28
|
+
const { agent, address } = await T2000.init({ pin: 'my-secret' });
|
|
29
|
+
|
|
30
|
+
// Or load an existing one
|
|
28
31
|
const agent = await T2000.create({ pin: 'my-secret' });
|
|
29
32
|
|
|
30
33
|
// Check balance
|
|
@@ -46,30 +49,53 @@ await agent.borrow({ amount: 20, asset: 'USDC' });
|
|
|
46
49
|
|
|
47
50
|
## API Reference
|
|
48
51
|
|
|
49
|
-
### `T2000.
|
|
52
|
+
### `T2000.init(options)` — Create a new wallet
|
|
53
|
+
|
|
54
|
+
Creates a new bank account (generates keypair, encrypts, and saves to disk).
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const { agent, address, sponsored } = await T2000.init({
|
|
58
|
+
pin: 'my-secret', // Required — encrypts the key
|
|
59
|
+
keyPath: '~/.t2000/wallet.key', // Optional — custom key file path
|
|
60
|
+
name: 'my-agent', // Optional — agent name for sponsor registration
|
|
61
|
+
sponsored: true, // Optional — register with gas station (default: true)
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `T2000.create(options)` — Load an existing wallet
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
Loads an existing bank account from an encrypted key file. Throws `WALLET_NOT_FOUND` if no wallet exists.
|
|
52
68
|
|
|
53
69
|
```typescript
|
|
54
70
|
const agent = await T2000.create({
|
|
55
|
-
pin: 'my-secret',
|
|
56
|
-
|
|
57
|
-
rpcUrl: 'https://...',
|
|
58
|
-
keyPath: '~/.t2000/wallet.key', // Custom key file path (optional)
|
|
71
|
+
pin: 'my-secret', // Required — decrypts the key
|
|
72
|
+
keyPath: '~/.t2000/wallet.key', // Optional — custom key file path
|
|
73
|
+
rpcUrl: 'https://...', // Optional — custom Sui RPC endpoint
|
|
59
74
|
});
|
|
60
75
|
```
|
|
61
76
|
|
|
77
|
+
### `T2000.fromPrivateKey(key, options?)` — Load from raw key
|
|
78
|
+
|
|
79
|
+
Synchronous factory that creates an agent from a raw private key (bech32 `suiprivkey1...` or hex).
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const agent = T2000.fromPrivateKey('suiprivkey1q...');
|
|
83
|
+
```
|
|
84
|
+
|
|
62
85
|
### Core Methods
|
|
63
86
|
|
|
64
87
|
| Method | Description | Returns |
|
|
65
88
|
|--------|-------------|---------|
|
|
89
|
+
| `agent.address()` | Wallet Sui address | `string` |
|
|
66
90
|
| `agent.balance()` | Available USDC + savings + gas reserve | `BalanceResponse` |
|
|
67
|
-
| `agent.send({ to, amount })` | Transfer USDC to any Sui address | `SendResult` |
|
|
68
|
-
| `agent.save({ amount, asset
|
|
69
|
-
| `agent.withdraw({ amount, asset
|
|
70
|
-
| `agent.swap({ from, to, amount })` | Swap via Cetus CLMM DEX | `SwapResult` |
|
|
71
|
-
| `agent.
|
|
72
|
-
| `agent.
|
|
91
|
+
| `agent.send({ to, amount, asset? })` | Transfer USDC to any Sui address | `SendResult` |
|
|
92
|
+
| `agent.save({ amount, asset })` | Deposit USDC to NAVI Protocol (earn APY). `amount` can be `'all'`. | `SaveResult` |
|
|
93
|
+
| `agent.withdraw({ amount, asset })` | Withdraw USDC from savings. `amount` can be `'all'`. | `WithdrawResult` |
|
|
94
|
+
| `agent.swap({ from, to, amount, maxSlippage? })` | Swap via Cetus CLMM DEX. `maxSlippage` in % (default: 3). | `SwapResult` |
|
|
95
|
+
| `agent.swapQuote({ from, to, amount })` | Get swap quote without executing | `SwapQuote` |
|
|
96
|
+
| `agent.borrow({ amount, asset })` | Borrow USDC against collateral | `BorrowResult` |
|
|
97
|
+
| `agent.repay({ amount, asset })` | Repay outstanding borrows. `amount` can be `'all'`. | `RepayResult` |
|
|
98
|
+
| `agent.exportKey()` | Export private key (bech32 format) | `string` |
|
|
73
99
|
|
|
74
100
|
### Query Methods
|
|
75
101
|
|
|
@@ -83,7 +109,15 @@ const agent = await T2000.create({
|
|
|
83
109
|
| `agent.maxWithdraw()` | Max safe withdrawal amount | `MaxWithdrawResult` |
|
|
84
110
|
| `agent.maxBorrow()` | Max safe borrow amount | `MaxBorrowResult` |
|
|
85
111
|
| `agent.deposit()` | Wallet address + funding instructions | `DepositInfo` |
|
|
86
|
-
| `agent.history()` | Transaction history | `TransactionRecord[]` |
|
|
112
|
+
| `agent.history({ limit? })` | Transaction history (default: all) | `TransactionRecord[]` |
|
|
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` |
|
|
87
121
|
|
|
88
122
|
### Key Management
|
|
89
123
|
|
|
@@ -93,6 +127,9 @@ import {
|
|
|
93
127
|
keypairFromPrivateKey,
|
|
94
128
|
exportPrivateKey,
|
|
95
129
|
getAddress,
|
|
130
|
+
saveKey,
|
|
131
|
+
loadKey,
|
|
132
|
+
walletExists,
|
|
96
133
|
} from '@t2000/sdk';
|
|
97
134
|
|
|
98
135
|
// Generate a new keypair
|
|
@@ -106,21 +143,44 @@ const privkey = exportPrivateKey(keypair);
|
|
|
106
143
|
|
|
107
144
|
// Get the Sui address
|
|
108
145
|
const address = getAddress(keypair);
|
|
146
|
+
|
|
147
|
+
// Check if wallet exists on disk
|
|
148
|
+
const exists = await walletExists();
|
|
149
|
+
|
|
150
|
+
// Save/load encrypted key
|
|
151
|
+
await saveKey(keypair, 'my-pin');
|
|
152
|
+
const loaded = await loadKey('my-pin');
|
|
109
153
|
```
|
|
110
154
|
|
|
111
155
|
### Events
|
|
112
156
|
|
|
113
157
|
```typescript
|
|
114
158
|
agent.on('balanceChange', (e) => {
|
|
115
|
-
console.log(`${e.cause}: ${e.asset}
|
|
159
|
+
console.log(`${e.cause}: ${e.asset} ${e.previous} → ${e.current}`);
|
|
116
160
|
});
|
|
117
161
|
|
|
118
162
|
agent.on('healthWarning', (e) => {
|
|
119
|
-
console.log(`Health factor: ${e.healthFactor}`);
|
|
163
|
+
console.log(`Health factor: ${e.healthFactor} (warning)`);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
agent.on('healthCritical', (e) => {
|
|
167
|
+
console.log(`Health factor: ${e.healthFactor} (critical — below 1.2)`);
|
|
120
168
|
});
|
|
121
169
|
|
|
122
170
|
agent.on('yield', (e) => {
|
|
123
|
-
console.log(`Earned: $${e.earned}`);
|
|
171
|
+
console.log(`Earned: $${e.earned}, total: $${e.total}`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
agent.on('gasAutoTopUp', (e) => {
|
|
175
|
+
console.log(`Auto-topped up gas: $${e.usdcSpent} USDC → ${e.suiReceived} SUI`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
agent.on('gasStationFallback', (e) => {
|
|
179
|
+
console.log(`Gas station fallback: ${e.reason}`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
agent.on('error', (e) => {
|
|
183
|
+
console.error(`Error: ${e.code} — ${e.message}`);
|
|
124
184
|
});
|
|
125
185
|
```
|
|
126
186
|
|
|
@@ -140,11 +200,20 @@ import {
|
|
|
140
200
|
|
|
141
201
|
mistToSui(1_000_000_000n); // 1.0
|
|
142
202
|
usdcToRaw(10.50); // 10_500_000n
|
|
143
|
-
formatUsd(1234.5); // "$
|
|
144
|
-
truncateAddress('
|
|
203
|
+
formatUsd(1234.5); // "$1234.50"
|
|
204
|
+
truncateAddress('0xabcdef...1234'); // "0xabcd...1234"
|
|
145
205
|
validateAddress('0x...'); // throws if invalid
|
|
146
206
|
```
|
|
147
207
|
|
|
208
|
+
### Advanced: Exposed Internals
|
|
209
|
+
|
|
210
|
+
For integrations (like `@t2000/x402`), the agent exposes:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
agent.suiClient; // SuiClient instance
|
|
214
|
+
agent.signer; // Ed25519Keypair
|
|
215
|
+
```
|
|
216
|
+
|
|
148
217
|
## Gas Abstraction
|
|
149
218
|
|
|
150
219
|
Every operation (send, save, borrow, repay, withdraw, swap) routes through a 3-step gas resolution chain via `executeWithGas()`. The agent never fails due to low gas if it has USDC or the Gas Station is reachable:
|
|
@@ -164,12 +233,10 @@ Every transaction result includes a `gasMethod` field (`'self-funded'` | `'auto-
|
|
|
164
233
|
|
|
165
234
|
| Environment Variable | Description | Default |
|
|
166
235
|
|---------------------|-------------|---------|
|
|
167
|
-
| `T2000_PIN` | Bank account PIN | — |
|
|
168
|
-
| `T2000_NETWORK` | `mainnet` or `testnet` | `mainnet` |
|
|
169
|
-
| `T2000_RPC_URL` | Custom Sui RPC URL | Sui public fullnode |
|
|
170
|
-
| `T2000_KEY_PATH` | Path to encrypted key file | `~/.t2000/wallet.key` |
|
|
171
236
|
| `T2000_API_URL` | t2000 API base URL | `https://api.t2000.ai` |
|
|
172
237
|
|
|
238
|
+
Options like `pin`, `keyPath`, and `rpcUrl` are passed directly to `T2000.create()` or `T2000.init()`. The CLI handles env vars like `T2000_PIN` — see the [CLI README](https://www.npmjs.com/package/@t2000/cli).
|
|
239
|
+
|
|
173
240
|
## Supported Assets
|
|
174
241
|
|
|
175
242
|
| Asset | Type | Decimals |
|
|
@@ -192,12 +259,12 @@ try {
|
|
|
192
259
|
}
|
|
193
260
|
```
|
|
194
261
|
|
|
195
|
-
Common error codes: `INSUFFICIENT_BALANCE` · `INVALID_ADDRESS` · `INVALID_AMOUNT` · `HEALTH_FACTOR_TOO_LOW` · `NO_COLLATERAL` · `WALLET_NOT_FOUND` · `SIMULATION_FAILED` · `TRANSACTION_FAILED` · `PROTOCOL_PAUSED` · `INSUFFICIENT_GAS` · `SLIPPAGE_EXCEEDED` · `ASSET_NOT_SUPPORTED` · `WITHDRAW_WOULD_LIQUIDATE`
|
|
262
|
+
Common error codes: `INSUFFICIENT_BALANCE` · `INVALID_ADDRESS` · `INVALID_AMOUNT` · `HEALTH_FACTOR_TOO_LOW` · `NO_COLLATERAL` · `WALLET_NOT_FOUND` · `WALLET_LOCKED` · `WALLET_EXISTS` · `SIMULATION_FAILED` · `TRANSACTION_FAILED` · `PROTOCOL_PAUSED` · `INSUFFICIENT_GAS` · `SLIPPAGE_EXCEEDED` · `ASSET_NOT_SUPPORTED` · `WITHDRAW_WOULD_LIQUIDATE` · `AUTO_TOPUP_FAILED` · `GAS_STATION_UNAVAILABLE`
|
|
196
263
|
|
|
197
264
|
## Testing
|
|
198
265
|
|
|
199
266
|
```bash
|
|
200
|
-
# Run all SDK unit tests (
|
|
267
|
+
# Run all SDK unit tests (122 tests)
|
|
201
268
|
pnpm --filter @t2000/sdk test
|
|
202
269
|
```
|
|
203
270
|
|
|
@@ -210,6 +277,10 @@ pnpm --filter @t2000/sdk test
|
|
|
210
277
|
| `keyManager.test.ts` | Key generation, encryption, decryption, import/export |
|
|
211
278
|
| `errors.test.ts` | `T2000Error` construction, serialization, `mapWalletError`, `mapMoveAbortCode` |
|
|
212
279
|
| `navi.test.ts` | NAVI math utilities (health factor, APY, position calculations) |
|
|
280
|
+
| `send.test.ts` | Send transaction building and validation |
|
|
281
|
+
| `manager.test.ts` | Gas resolution chain (self-fund, auto-topup, sponsored fallback) |
|
|
282
|
+
| `autoTopUp.test.ts` | Auto-topup threshold logic and swap execution |
|
|
283
|
+
| `serialization.test.ts` | Transaction JSON serialization roundtrip |
|
|
213
284
|
|
|
214
285
|
## Protocol Fees
|
|
215
286
|
|
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;
|