@edge-protocol/sdk 0.4.6 → 0.4.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/DOCS.md +777 -0
- package/package.json +1 -1
package/DOCS.md
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
# @edge-protocol/sdk — Full Developer Reference
|
|
2
|
+
|
|
3
|
+
> Programmable trust infrastructure for autonomous AI agents on Sui.
|
|
4
|
+
> Give agents your rules, not your keys.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pnpm add @edge-protocol/sdk
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Core Concepts](#core-concepts)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [EdgePass API](#edgepass-api)
|
|
17
|
+
- [Templates](#templates)
|
|
18
|
+
- [PolicyEngine](#policyengine)
|
|
19
|
+
- [Types](#types)
|
|
20
|
+
- [Constants](#constants)
|
|
21
|
+
- [Integration Examples](#integration-examples)
|
|
22
|
+
- [Error Handling](#error-handling)
|
|
23
|
+
- [Architecture](#architecture)
|
|
24
|
+
- [Move Contract](#move-contract)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### The Problem
|
|
31
|
+
|
|
32
|
+
Every developer building an autonomous agent faces the same unsolved problem:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Option A: Give the agent full wallet access → catastrophic risk
|
|
36
|
+
Option B: Human approves every transaction → defeats the purpose
|
|
37
|
+
Option C: Build custom policy logic → 6-8 weeks of work
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**EdgePass is Option D** — a programmable trust boundary enforced on-chain.
|
|
41
|
+
|
|
42
|
+
### EdgePass
|
|
43
|
+
|
|
44
|
+
An EdgePass is a Sui Move object that encodes a complete trust policy. It lives in the user's wallet — not in a contract. An agent executes against it without ever taking ownership.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
budget: $300 · auto-approve: <$50 · escalate: >$100 · merchants: [...] · expiry: 48h
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Transaction Outcomes
|
|
51
|
+
|
|
52
|
+
Every `sdk.execute()` returns one of three outcomes:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
✅ approved — executed on-chain, digest available
|
|
56
|
+
⚠️ escalated — exceeds threshold, needs user approval
|
|
57
|
+
🚫 blocked — policy rejected, reason provided
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### MIST
|
|
61
|
+
|
|
62
|
+
All amounts are in MIST — Sui's base unit.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
1 SUI = 1_000_000_000 MIST
|
|
66
|
+
|
|
67
|
+
import { MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
68
|
+
|
|
69
|
+
const budget = 300n * MIST_PER_SUI; // 300 SUI
|
|
70
|
+
const amount = 18_500_000_000n; // 18.5 SUI
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install @edge-protocol/sdk
|
|
79
|
+
# or
|
|
80
|
+
pnpm add @edge-protocol/sdk
|
|
81
|
+
# or
|
|
82
|
+
yarn add @edge-protocol/sdk
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Requirements
|
|
86
|
+
|
|
87
|
+
- Node.js 18+
|
|
88
|
+
- TypeScript 5.0+ (recommended)
|
|
89
|
+
- A Sui network (testnet or mainnet)
|
|
90
|
+
- An Enoki API key for gas sponsorship
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## EdgePass API
|
|
95
|
+
|
|
96
|
+
### `new EdgePass(config)`
|
|
97
|
+
|
|
98
|
+
Initialize the SDK client.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { EdgePass } from '@edge-protocol/sdk';
|
|
102
|
+
|
|
103
|
+
const sdk = new EdgePass({
|
|
104
|
+
network: 'mainnet', // 'mainnet' | 'testnet' | 'devnet'
|
|
105
|
+
enokiApiKey: 'YOUR_KEY',
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `sdk.create(config, signer)` → `Promise<EdgePassObject>`
|
|
112
|
+
|
|
113
|
+
Mint a new EdgePass as a Move object on Sui.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { EdgePass, MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
117
|
+
|
|
118
|
+
const pass = await sdk.create({
|
|
119
|
+
budget: 300n * MIST_PER_SUI, // total spend limit
|
|
120
|
+
autoThreshold: 50n * MIST_PER_SUI, // auto-approve below this
|
|
121
|
+
escalateThreshold: 100n * MIST_PER_SUI, // escalate above this
|
|
122
|
+
maxPerTransaction: 200n * MIST_PER_SUI, // optional hard cap per tx
|
|
123
|
+
approvedMerchants: ['Shuttle Express', 'Hydra Bar'],
|
|
124
|
+
expiryMs: 48 * 60 * 60 * 1000, // 48 hours
|
|
125
|
+
owner: userAddress,
|
|
126
|
+
}, signer);
|
|
127
|
+
|
|
128
|
+
console.log(pass.id); // Sui object ID — verifiable on Suiscan
|
|
129
|
+
console.log(pass.expiresAt); // Unix timestamp
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Parameters:**
|
|
133
|
+
|
|
134
|
+
| Field | Type | Required | Description |
|
|
135
|
+
|-------|------|----------|-------------|
|
|
136
|
+
| `budget` | `bigint` | ✅ | Total spend limit in MIST |
|
|
137
|
+
| `autoThreshold` | `bigint` | ✅ | Auto-approve below this amount |
|
|
138
|
+
| `escalateThreshold` | `bigint` | ✅ | Escalate above this amount |
|
|
139
|
+
| `maxPerTransaction` | `bigint` | ❌ | Hard cap per single transaction |
|
|
140
|
+
| `approvedMerchants` | `string[]` | ✅ | Allowlist of merchant identifiers |
|
|
141
|
+
| `expiryMs` | `number` | ✅ | Duration until expiry in milliseconds |
|
|
142
|
+
| `owner` | `string` | ✅ | Sui address of the pass owner |
|
|
143
|
+
|
|
144
|
+
**Constraint:** `autoThreshold < escalateThreshold < budget`
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### `EdgePass.fromTemplate(template, overrides)` → `EdgePassConfig`
|
|
149
|
+
|
|
150
|
+
Create a config from a pre-built template. Override any field.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Use a template as-is
|
|
154
|
+
const config = EdgePass.fromTemplate('festival', { owner: userAddress });
|
|
155
|
+
|
|
156
|
+
// Override specific fields
|
|
157
|
+
const config = EdgePass.fromTemplate('defi', {
|
|
158
|
+
budget: 25_000n * MIST_PER_SUI,
|
|
159
|
+
approvedMerchants: ['DeepBook', 'Cetus', 'Turbos'],
|
|
160
|
+
owner: userAddress,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const pass = await sdk.create(config, signer);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### `sdk.execute(pass, request, signer)` → `Promise<TransactionOutcome>`
|
|
169
|
+
|
|
170
|
+
Execute a transaction against an EdgePass. Policy is validated before touching the chain — blocked and escalated transactions never reach Sui.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const outcome = await sdk.execute(pass, {
|
|
174
|
+
merchant: 'Shuttle Express',
|
|
175
|
+
amount: 18_500_000_000n, // 18.5 SUI in MIST
|
|
176
|
+
}, signer);
|
|
177
|
+
|
|
178
|
+
switch (outcome.status) {
|
|
179
|
+
case 'approved':
|
|
180
|
+
console.log('tx digest:', outcome.digest);
|
|
181
|
+
// audit receipt written to Walrus automatically
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case 'escalated':
|
|
185
|
+
// notify user — outcome.reason explains why
|
|
186
|
+
await sendPushNotification(outcome.reason);
|
|
187
|
+
// re-execute after user approves
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case 'blocked':
|
|
191
|
+
// policy rejected — outcome.reason explains why
|
|
192
|
+
console.log('blocked:', outcome.reason);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### `sdk.validate(pass, request)` → `PolicyValidation`
|
|
200
|
+
|
|
201
|
+
Preview the outcome without executing. Zero network calls. Sub-millisecond. Use for UI previews before execution.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const preview = sdk.validate(pass, {
|
|
205
|
+
merchant: 'Shuttle Express',
|
|
206
|
+
amount: 18_500_000_000n,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// { allowed: true, requiresEscalation: false, reason: 'Auto-approved' }
|
|
210
|
+
// { allowed: true, requiresEscalation: true, reason: 'Amount exceeds escalation threshold...' }
|
|
211
|
+
// { allowed: false, requiresEscalation: false, reason: 'Merchant "X" is not approved' }
|
|
212
|
+
|
|
213
|
+
if (!preview.allowed) {
|
|
214
|
+
showBlockedUI(preview.reason);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (preview.requiresEscalation) {
|
|
219
|
+
showEscalationUI(preview.reason);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Safe to execute
|
|
224
|
+
const outcome = await sdk.execute(pass, request, signer);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### `sdk.revoke(pass, signer)` → `Promise<{ digest: string }>`
|
|
230
|
+
|
|
231
|
+
Revoke an EdgePass on-chain. All future `execute()` calls return `blocked` immediately.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const { digest } = await sdk.revoke(pass, signer);
|
|
235
|
+
console.log('revoked:', digest);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
### `sdk.fetch(objectId)` → `Promise<EdgePassObject | null>`
|
|
241
|
+
|
|
242
|
+
Fetch a live EdgePass from the Sui network.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const pass = await sdk.fetch('0x4e2f...8b91');
|
|
246
|
+
|
|
247
|
+
if (!pass) {
|
|
248
|
+
console.log('EdgePass not found');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const remaining = sdk.remainingBudget(pass);
|
|
253
|
+
console.log('remaining:', remaining);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### `sdk.remainingBudget(pass)` → `bigint`
|
|
259
|
+
|
|
260
|
+
Returns remaining budget in MIST.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const remaining = sdk.remainingBudget(pass);
|
|
264
|
+
const remainingSUI = Number(remaining) / Number(MIST_PER_SUI);
|
|
265
|
+
console.log(`$${remainingSUI.toFixed(2)} remaining`);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### `sdk.isValid(pass)` → `boolean`
|
|
271
|
+
|
|
272
|
+
Returns `true` if the pass is active and not expired.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
if (!sdk.isValid(pass)) {
|
|
276
|
+
// create a new pass
|
|
277
|
+
pass = await sdk.create(config, signer);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Templates
|
|
284
|
+
|
|
285
|
+
Pre-configured trust boundaries for common use cases. Every template is a starting point — override any field.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { EdgePass, EDGE_TEMPLATES } from '@edge-protocol/sdk';
|
|
289
|
+
|
|
290
|
+
// Available templates
|
|
291
|
+
EdgePass.fromTemplate('festival', { owner }) // $300 / 48h
|
|
292
|
+
EdgePass.fromTemplate('gaming', { owner }) // $50 / 4h session
|
|
293
|
+
EdgePass.fromTemplate('subscription', { owner }) // $200 / 30 days
|
|
294
|
+
EdgePass.fromTemplate('defi', { owner }) // $10k / 7 days
|
|
295
|
+
EdgePass.fromTemplate('enterprise', { owner }) // $50k / 30 days
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Template defaults
|
|
299
|
+
|
|
300
|
+
| Template | Budget | Auto ≤ | Escalate ≥ | Max/tx | Expiry |
|
|
301
|
+
|----------|--------|--------|------------|--------|--------|
|
|
302
|
+
| `festival` | 300 SUI | 50 SUI | 100 SUI | 200 SUI | 48h |
|
|
303
|
+
| `gaming` | 50 SUI | 2 SUI | 10 SUI | 10 SUI | 4h |
|
|
304
|
+
| `subscription` | 200 SUI | 20 SUI | 50 SUI | 50 SUI | 30d |
|
|
305
|
+
| `defi` | 10,000 SUI | 500 SUI | 1,000 SUI | 2,000 SUI | 7d |
|
|
306
|
+
| `enterprise` | 50,000 SUI | 1,000 SUI | 5,000 SUI | 10,000 SUI | 30d |
|
|
307
|
+
|
|
308
|
+
### Accessing raw template values
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { EDGE_TEMPLATES } from '@edge-protocol/sdk';
|
|
312
|
+
|
|
313
|
+
console.log(EDGE_TEMPLATES.festival.budget); // 300_000_000_000n
|
|
314
|
+
console.log(EDGE_TEMPLATES.defi.escalateThreshold); // 1_000_000_000_000n
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## PolicyEngine
|
|
320
|
+
|
|
321
|
+
Access the policy engine directly for custom validation flows.
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { PolicyEngine } from '@edge-protocol/sdk';
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `PolicyEngine.validate(pass, request)` → `PolicyValidation`
|
|
328
|
+
|
|
329
|
+
Validates a transaction request. Same logic used internally by `sdk.execute()`.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
const validation = PolicyEngine.validate(pass, {
|
|
333
|
+
merchant: 'Shuttle Express',
|
|
334
|
+
amount: 18_500_000_000n,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// validation.allowed → boolean
|
|
338
|
+
// validation.requiresEscalation → boolean
|
|
339
|
+
// validation.reason → string
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Validation rules (in order):**
|
|
343
|
+
|
|
344
|
+
1. Pass must be active
|
|
345
|
+
2. Pass must not be expired
|
|
346
|
+
3. Merchant must be in `approvedMerchants`
|
|
347
|
+
4. Amount must not exceed remaining budget
|
|
348
|
+
5. Amount must not exceed `maxPerTransaction` (if set)
|
|
349
|
+
6. If amount > `escalateThreshold` → escalate
|
|
350
|
+
7. If amount ≤ `autoThreshold` → auto-approve
|
|
351
|
+
|
|
352
|
+
### `PolicyEngine.isValid(pass)` → `boolean`
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
const valid = PolicyEngine.isValid(pass);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### `PolicyEngine.remainingBudget(pass)` → `bigint`
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const remaining = PolicyEngine.remainingBudget(pass);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Types
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import type {
|
|
370
|
+
EdgePassConfig,
|
|
371
|
+
EdgePassObject,
|
|
372
|
+
TransactionRequest,
|
|
373
|
+
TransactionOutcome,
|
|
374
|
+
PolicyValidation,
|
|
375
|
+
Network,
|
|
376
|
+
EdgeSDKConfig,
|
|
377
|
+
EdgePassTemplate,
|
|
378
|
+
} from '@edge-protocol/sdk';
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### `EdgePassConfig`
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
interface EdgePassConfig {
|
|
385
|
+
budget: bigint; // total spend limit in MIST
|
|
386
|
+
autoThreshold: bigint; // auto-approve below this
|
|
387
|
+
escalateThreshold: bigint; // escalate above this
|
|
388
|
+
maxPerTransaction?: bigint; // optional hard cap per tx
|
|
389
|
+
approvedMerchants: string[]; // merchant allowlist
|
|
390
|
+
expiryMs: number; // duration in milliseconds
|
|
391
|
+
owner: string; // Sui address
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### `EdgePassObject`
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
interface EdgePassObject {
|
|
399
|
+
id: string; // Sui object ID
|
|
400
|
+
config: EdgePassConfig;
|
|
401
|
+
spent: bigint; // total spent so far in MIST
|
|
402
|
+
active: boolean;
|
|
403
|
+
createdAt: number; // Unix timestamp
|
|
404
|
+
expiresAt: number; // Unix timestamp
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### `TransactionRequest`
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
interface TransactionRequest {
|
|
412
|
+
merchant: string; // merchant identifier
|
|
413
|
+
amount: bigint; // amount in MIST
|
|
414
|
+
metadata?: Record<string, string>; // optional metadata
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### `TransactionOutcome`
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
type TransactionOutcome =
|
|
422
|
+
| { status: 'approved'; digest: string; objectId?: string; auto: true }
|
|
423
|
+
| { status: 'escalated'; reason: string; auto: false }
|
|
424
|
+
| { status: 'blocked'; reason: string; auto: false };
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### `PolicyValidation`
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
interface PolicyValidation {
|
|
431
|
+
allowed: boolean;
|
|
432
|
+
requiresEscalation: boolean;
|
|
433
|
+
reason: string;
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### `Network`
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
type Network = 'mainnet' | 'testnet' | 'devnet';
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### `EdgePassTemplate`
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
type EdgePassTemplate = 'festival' | 'gaming' | 'subscription' | 'defi' | 'enterprise';
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Constants
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
import {
|
|
455
|
+
MIST_PER_SUI, // 1_000_000_000n
|
|
456
|
+
NETWORK_URLS, // { mainnet, testnet, devnet }
|
|
457
|
+
EDGE_PACKAGE_ID, // { mainnet, testnet, devnet }
|
|
458
|
+
EDGE_TEMPLATES, // all 5 templates
|
|
459
|
+
DEFAULT_GAS_BUDGET // 10_000_000n
|
|
460
|
+
} from '@edge-protocol/sdk';
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Integration Examples
|
|
466
|
+
|
|
467
|
+
### AI Agent with EdgePass
|
|
468
|
+
|
|
469
|
+
A Claude LLM making autonomous festival purchases within an EdgePass.
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { EdgePass, MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
473
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
474
|
+
|
|
475
|
+
const sdk = new EdgePass({ network: 'mainnet', enokiApiKey: KEY });
|
|
476
|
+
const claude = new Anthropic();
|
|
477
|
+
|
|
478
|
+
// 1. User creates EdgePass once
|
|
479
|
+
const pass = await sdk.create(
|
|
480
|
+
EdgePass.fromTemplate('festival', {
|
|
481
|
+
approvedMerchants: ['Shuttle Express', 'Hydra Bar', 'Stage Access VIP'],
|
|
482
|
+
owner: userAddress,
|
|
483
|
+
}),
|
|
484
|
+
signer
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// 2. Agent loop — runs autonomously
|
|
488
|
+
async function agentLoop(scenario: string) {
|
|
489
|
+
const response = await claude.messages.create({
|
|
490
|
+
model: 'claude-sonnet-4-6',
|
|
491
|
+
max_tokens: 500,
|
|
492
|
+
messages: [{
|
|
493
|
+
role: 'user',
|
|
494
|
+
content: `Festival scenario: "${scenario}".
|
|
495
|
+
Decide what to purchase. Return JSON:
|
|
496
|
+
{ merchant: string, amount: number }`
|
|
497
|
+
}]
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const { merchant, amount } = JSON.parse(response.content[0].text);
|
|
501
|
+
|
|
502
|
+
const outcome = await sdk.execute(pass, {
|
|
503
|
+
merchant,
|
|
504
|
+
amount: BigInt(Math.floor(amount * 1e9)),
|
|
505
|
+
}, signer);
|
|
506
|
+
|
|
507
|
+
if (outcome.status === 'escalated') {
|
|
508
|
+
await notifyUser(`Approve $${amount} at ${merchant}?`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return outcome;
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
### DeFi Trading Agent
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
const pass = await sdk.create(
|
|
521
|
+
EdgePass.fromTemplate('defi', {
|
|
522
|
+
approvedMerchants: ['DeepBook', 'Cetus', 'Turbos'],
|
|
523
|
+
budget: 5_000n * MIST_PER_SUI,
|
|
524
|
+
owner: userAddress,
|
|
525
|
+
}),
|
|
526
|
+
signer
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
async function executeTrade(dex: string, amount: bigint) {
|
|
530
|
+
// validate before network call
|
|
531
|
+
const preview = sdk.validate(pass, { merchant: dex, amount });
|
|
532
|
+
|
|
533
|
+
if (!preview.allowed) {
|
|
534
|
+
logger.warn(`Trade blocked: ${preview.reason}`);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (preview.requiresEscalation) {
|
|
539
|
+
await riskTeam.requestApproval({ dex, amount, reason: preview.reason });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const outcome = await sdk.execute(pass, { merchant: dex, amount }, signer);
|
|
544
|
+
// audit log written to Walrus automatically
|
|
545
|
+
logger.info(`Trade executed: ${outcome.digest}`);
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
### Enterprise Payroll Agent
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
const pass = await sdk.create(
|
|
555
|
+
EdgePass.fromTemplate('enterprise', {
|
|
556
|
+
approvedMerchants: ['vendor-a.sui', 'vendor-b.sui'],
|
|
557
|
+
budget: 100_000n * MIST_PER_SUI,
|
|
558
|
+
escalateThreshold: 10_000n * MIST_PER_SUI,
|
|
559
|
+
owner: cfoAddress,
|
|
560
|
+
}),
|
|
561
|
+
signer
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
for (const payment of scheduledPayments) {
|
|
565
|
+
const outcome = await sdk.execute(pass, {
|
|
566
|
+
merchant: payment.vendor,
|
|
567
|
+
amount: payment.amount,
|
|
568
|
+
}, signer);
|
|
569
|
+
|
|
570
|
+
if (outcome.status === 'escalated') {
|
|
571
|
+
await cfo.requestApproval(payment); // new vendor or large amount
|
|
572
|
+
}
|
|
573
|
+
// every payment logged to Walrus for compliance audit
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
### Subscription Manager
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
const pass = await sdk.create(
|
|
583
|
+
EdgePass.fromTemplate('subscription', {
|
|
584
|
+
approvedMerchants: ['netflix.sui', 'spotify.sui', 'github.sui'],
|
|
585
|
+
owner: userAddress,
|
|
586
|
+
}),
|
|
587
|
+
signer
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
// Runs monthly — agent handles all renewals
|
|
591
|
+
async function processRenewals(subscriptions: Subscription[]) {
|
|
592
|
+
for (const sub of subscriptions) {
|
|
593
|
+
if (!sdk.isValid(pass)) {
|
|
594
|
+
pass = await sdk.create(
|
|
595
|
+
EdgePass.fromTemplate('subscription', { owner: userAddress }),
|
|
596
|
+
signer
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
await sdk.execute(pass, {
|
|
601
|
+
merchant: sub.merchant,
|
|
602
|
+
amount: sub.amount,
|
|
603
|
+
}, signer);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Error Handling
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
try {
|
|
614
|
+
const outcome = await sdk.execute(pass, request, signer);
|
|
615
|
+
|
|
616
|
+
switch (outcome.status) {
|
|
617
|
+
case 'approved':
|
|
618
|
+
// success — outcome.digest is the Sui tx hash
|
|
619
|
+
break;
|
|
620
|
+
case 'escalated':
|
|
621
|
+
// needs human review — outcome.reason explains why
|
|
622
|
+
break;
|
|
623
|
+
case 'blocked':
|
|
624
|
+
// policy rejected — outcome.reason explains why
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
} catch (error) {
|
|
628
|
+
// network error, signing failure, expired credentials, etc.
|
|
629
|
+
console.error('SDK error:', error);
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### Common reasons
|
|
634
|
+
|
|
635
|
+
| Reason | Cause |
|
|
636
|
+
|--------|-------|
|
|
637
|
+
| `EdgePass is inactive` | Pass was revoked |
|
|
638
|
+
| `EdgePass has expired` | `expiresAt` timestamp passed |
|
|
639
|
+
| `Merchant "X" is not approved` | Merchant not in allowlist |
|
|
640
|
+
| `Insufficient budget` | Remaining budget < amount |
|
|
641
|
+
| `Amount exceeds per-transaction limit` | Amount > `maxPerTransaction` |
|
|
642
|
+
| `Amount exceeds escalation threshold` | Amount > `escalateThreshold` (not blocked — escalated) |
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Architecture
|
|
647
|
+
|
|
648
|
+
```
|
|
649
|
+
User creates EdgePass (once)
|
|
650
|
+
│
|
|
651
|
+
▼
|
|
652
|
+
Agent calls sdk.execute() — many times, autonomously
|
|
653
|
+
│
|
|
654
|
+
├─▶ PolicyEngine.validate()
|
|
655
|
+
│ Pure TypeScript · no network · <1ms
|
|
656
|
+
│ 7 rules checked in order
|
|
657
|
+
│ blocked/escalated never touch Sui
|
|
658
|
+
│
|
|
659
|
+
├─▶ ExecutionEngine.buildPTB()
|
|
660
|
+
│ Programmable Transaction Block
|
|
661
|
+
│ validate → execute → update spent → emit event
|
|
662
|
+
│ atomic — any failure reverts everything
|
|
663
|
+
│
|
|
664
|
+
└─▶ Walrus audit receipt
|
|
665
|
+
immutable · decentralized · permanent
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Why PTBs matter
|
|
669
|
+
|
|
670
|
+
PTBs (Programmable Transaction Blocks) are Sui's killer feature. The policy check and the spend update happen in one atomic block. If any step fails, everything reverts. No partial state. No race conditions. This is what makes EdgePass trustworthy at the protocol level — not just application code.
|
|
671
|
+
|
|
672
|
+
### Why the object model matters
|
|
673
|
+
|
|
674
|
+
The EdgePass is a first-class owned object in the user's wallet. An agent can be passed the object to execute against — but the Sui protocol guarantees it can never take ownership. No contract upgrade, no admin key, no reentrancy attack can change that.
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## Move Contract
|
|
679
|
+
|
|
680
|
+
```
|
|
681
|
+
Package: 0x9f4065009494aa5acd92a5c72a6c22ce80939b2bddae3b34345459bc98d2501d
|
|
682
|
+
Network: Sui Testnet (Mainnet coming)
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
[View on Sui Explorer →](https://suiscan.xyz/testnet/object/0x9f4065009494aa5acd92a5c72a6c22ce80939b2bddae3b34345459bc98d2501d)
|
|
686
|
+
|
|
687
|
+
### Contract functions
|
|
688
|
+
|
|
689
|
+
```move
|
|
690
|
+
// Create a new EdgePass
|
|
691
|
+
public entry fun create_pass(
|
|
692
|
+
budget: u64,
|
|
693
|
+
auto_threshold: u64,
|
|
694
|
+
escalate_threshold: u64,
|
|
695
|
+
expiry_ms: u64,
|
|
696
|
+
approved_merchants: vector<String>,
|
|
697
|
+
clock: &Clock,
|
|
698
|
+
ctx: &mut TxContext,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
// Execute a transaction against an EdgePass
|
|
702
|
+
public entry fun execute_transaction(
|
|
703
|
+
pass: &mut EdgePass,
|
|
704
|
+
amount: u64,
|
|
705
|
+
merchant: String,
|
|
706
|
+
clock: &Clock,
|
|
707
|
+
ctx: &mut TxContext,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
// Revoke an EdgePass
|
|
711
|
+
public entry fun revoke_pass(
|
|
712
|
+
pass: &mut EdgePass,
|
|
713
|
+
ctx: &mut TxContext,
|
|
714
|
+
)
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Testing
|
|
720
|
+
|
|
721
|
+
```bash
|
|
722
|
+
cd packages/sdk && pnpm test
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
```
|
|
726
|
+
📋 PolicyEngine.validate()
|
|
727
|
+
✓ auto-approves under $50
|
|
728
|
+
✓ auto-approves at exactly $50
|
|
729
|
+
✓ escalates above $100
|
|
730
|
+
✓ escalates at exactly $101
|
|
731
|
+
✓ blocks unlisted merchant
|
|
732
|
+
✓ blocks when budget exceeded
|
|
733
|
+
✓ blocks when expired
|
|
734
|
+
✓ blocks when inactive
|
|
735
|
+
✓ blocks when maxPerTransaction exceeded
|
|
736
|
+
✓ allows when maxPerTransaction is undefined
|
|
737
|
+
|
|
738
|
+
📋 PolicyEngine helpers
|
|
739
|
+
✓ isValid returns true for active pass
|
|
740
|
+
✓ isValid returns false for expired pass
|
|
741
|
+
✓ isValid returns false for inactive pass
|
|
742
|
+
✓ remainingBudget calculates correctly
|
|
743
|
+
✓ remainingBudget returns full budget when nothing spent
|
|
744
|
+
|
|
745
|
+
📋 EdgePass.fromTemplate()
|
|
746
|
+
✓ festival template has correct defaults
|
|
747
|
+
✓ gaming template has correct expiry
|
|
748
|
+
✓ defi template has correct budget
|
|
749
|
+
✓ enterprise template has correct budget
|
|
750
|
+
✓ fromTemplate allows budget override
|
|
751
|
+
✓ fromTemplate allows merchant override
|
|
752
|
+
✓ fromTemplate preserves owner
|
|
753
|
+
|
|
754
|
+
📋 Constants
|
|
755
|
+
✓ MIST_PER_SUI is 1_000_000_000
|
|
756
|
+
✓ all 5 templates exist
|
|
757
|
+
✓ all templates have required fields
|
|
758
|
+
✓ all templates have autoThreshold < escalateThreshold
|
|
759
|
+
✓ all templates have escalateThreshold < budget
|
|
760
|
+
|
|
761
|
+
27 passed · 0 failed ✅
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
## Links
|
|
767
|
+
|
|
768
|
+
- **npm:** [npmjs.com/package/@edge-protocol/sdk](https://npmjs.com/package/@edge-protocol/sdk)
|
|
769
|
+
- **GitHub:** [github.com/fluturecode/edge](https://github.com/fluturecode/edge)
|
|
770
|
+
- **Live Demo:** [edge-web-cyan.vercel.app](https://edge-web-cyan.vercel.app)
|
|
771
|
+
- **Sui Explorer:** [Contract on Testnet](https://suiscan.xyz/testnet/object/0x9f4065009494aa5acd92a5c72a6c22ce80939b2bddae3b34345459bc98d2501d)
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
*The best infrastructure is invisible.*
|
|
776
|
+
|
|
777
|
+
Built for [Sui Overflow 2026](https://overflow.sui.io) · MIT License
|
package/package.json
CHANGED