@edge-protocol/sdk 0.9.0 → 0.9.1
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 +357 -297
- package/README.md +122 -33
- package/package.json +2 -2
package/DOCS.md
CHANGED
|
@@ -14,7 +14,12 @@ pnpm add @edge-protocol/sdk
|
|
|
14
14
|
- [Core Concepts](#core-concepts)
|
|
15
15
|
- [Installation](#installation)
|
|
16
16
|
- [EdgePass API](#edgepass-api)
|
|
17
|
+
- [Simulation](#simulation)
|
|
18
|
+
- [Budget Intelligence](#budget-intelligence)
|
|
19
|
+
- [withPolicy](#withpolicy)
|
|
20
|
+
- [React Hooks](#react-hooks)
|
|
17
21
|
- [Templates](#templates)
|
|
22
|
+
- [Events System](#events-system)
|
|
18
23
|
- [PolicyEngine](#policyengine)
|
|
19
24
|
- [Types](#types)
|
|
20
25
|
- [Constants](#constants)
|
|
@@ -22,10 +27,8 @@ pnpm add @edge-protocol/sdk
|
|
|
22
27
|
- [Error Handling](#error-handling)
|
|
23
28
|
- [Architecture](#architecture)
|
|
24
29
|
- [Move Contract](#move-contract)
|
|
25
|
-
- [Competitive Positioning](#competitive-positioning)
|
|
26
30
|
- [Security Model](#security-model)
|
|
27
31
|
- [Testing](#testing)
|
|
28
|
-
- [Links](#links)
|
|
29
32
|
|
|
30
33
|
---
|
|
31
34
|
|
|
@@ -33,8 +36,6 @@ pnpm add @edge-protocol/sdk
|
|
|
33
36
|
|
|
34
37
|
### The Problem
|
|
35
38
|
|
|
36
|
-
Every developer building an autonomous agent faces the same unsolved problem:
|
|
37
|
-
|
|
38
39
|
```
|
|
39
40
|
Option A: Give the agent full wallet access → catastrophic risk
|
|
40
41
|
Option B: Human approves every transaction → defeats the purpose
|
|
@@ -48,17 +49,18 @@ Option C: Build custom policy logic → 6-8 weeks of work
|
|
|
48
49
|
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.
|
|
49
50
|
|
|
50
51
|
```
|
|
51
|
-
budget: $
|
|
52
|
+
budget: $500 · auto-approve: <$75 · escalate: >$150 · merchants: [...] · expiry: 48h
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
### Transaction Outcomes
|
|
55
56
|
|
|
56
|
-
Every `sdk.execute()` returns one of
|
|
57
|
+
Every `sdk.execute()` returns one of four outcomes:
|
|
57
58
|
|
|
58
59
|
```
|
|
59
60
|
✅ approved — executed on-chain, digest available
|
|
60
61
|
⚠️ escalated — exceeds threshold, needs user approval
|
|
61
62
|
🚫 blocked — policy rejected, reason provided
|
|
63
|
+
❌ error — infrastructure failure, tx NOT submitted
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
### MIST
|
|
@@ -67,11 +69,8 @@ All amounts are in MIST — Sui's base unit.
|
|
|
67
69
|
|
|
68
70
|
```typescript
|
|
69
71
|
1 SUI = 1_000_000_000 MIST
|
|
70
|
-
|
|
71
72
|
import { MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
72
|
-
|
|
73
|
-
const budget = 300n * MIST_PER_SUI; // 300 SUI
|
|
74
|
-
const amount = 18_500_000_000n; // 18.5 SUI
|
|
73
|
+
const budget = 500n * MIST_PER_SUI; // 500 SUI
|
|
75
74
|
```
|
|
76
75
|
|
|
77
76
|
---
|
|
@@ -80,18 +79,14 @@ const amount = 18_500_000_000n; // 18.5 SUI
|
|
|
80
79
|
|
|
81
80
|
```bash
|
|
82
81
|
npm install @edge-protocol/sdk
|
|
83
|
-
# or
|
|
84
82
|
pnpm add @edge-protocol/sdk
|
|
85
|
-
# or
|
|
86
83
|
yarn add @edge-protocol/sdk
|
|
87
84
|
```
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- A Sui network (testnet or mainnet)
|
|
94
|
-
- An Enoki API key for gas sponsorship
|
|
86
|
+
React hook requires React 18+:
|
|
87
|
+
```typescript
|
|
88
|
+
import { useEdgePass } from '@edge-protocol/sdk/react';
|
|
89
|
+
```
|
|
95
90
|
|
|
96
91
|
---
|
|
97
92
|
|
|
@@ -99,8 +94,6 @@ yarn add @edge-protocol/sdk
|
|
|
99
94
|
|
|
100
95
|
### `new EdgePass(config)`
|
|
101
96
|
|
|
102
|
-
Initialize the SDK client.
|
|
103
|
-
|
|
104
97
|
```typescript
|
|
105
98
|
import { EdgePass } from '@edge-protocol/sdk';
|
|
106
99
|
|
|
@@ -117,22 +110,22 @@ const sdk = new EdgePass({
|
|
|
117
110
|
Mint a new EdgePass as a Move object on Sui.
|
|
118
111
|
|
|
119
112
|
```typescript
|
|
120
|
-
import { EdgePass, MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
121
|
-
|
|
122
113
|
const pass = await sdk.create({
|
|
123
|
-
budget:
|
|
124
|
-
autoThreshold:
|
|
125
|
-
escalateThreshold:
|
|
126
|
-
maxPerTransaction:
|
|
127
|
-
approvedMerchants: ['Shuttle Express', 'Hydra Bar'],
|
|
128
|
-
expiryMs: 48 * 60 * 60 * 1000,
|
|
114
|
+
budget: 500n * MIST_PER_SUI,
|
|
115
|
+
autoThreshold: 75n * MIST_PER_SUI,
|
|
116
|
+
escalateThreshold: 150n * MIST_PER_SUI,
|
|
117
|
+
maxPerTransaction: 300n * MIST_PER_SUI, // optional
|
|
118
|
+
approvedMerchants: ['Shuttle Express', 'Hydra Bar', 'Festival Kitchen'],
|
|
119
|
+
expiryMs: 48 * 60 * 60 * 1000,
|
|
129
120
|
owner: userAddress,
|
|
130
121
|
}, signer);
|
|
131
122
|
|
|
132
|
-
console.log(pass.id); // Sui object ID
|
|
123
|
+
console.log(pass.id); // Sui object ID
|
|
133
124
|
console.log(pass.expiresAt); // Unix timestamp
|
|
134
125
|
```
|
|
135
126
|
|
|
127
|
+
**Constraints:** `autoThreshold < escalateThreshold ≤ budget`
|
|
128
|
+
|
|
136
129
|
**Parameters:**
|
|
137
130
|
|
|
138
131
|
| Field | Type | Required | Description |
|
|
@@ -142,28 +135,18 @@ console.log(pass.expiresAt); // Unix timestamp
|
|
|
142
135
|
| `escalateThreshold` | `bigint` | ✅ | Escalate above this amount |
|
|
143
136
|
| `maxPerTransaction` | `bigint` | ❌ | Hard cap per single transaction |
|
|
144
137
|
| `approvedMerchants` | `string[]` | ✅ | Allowlist of merchant identifiers |
|
|
145
|
-
| `expiryMs` | `number` | ✅ | Duration until expiry in
|
|
138
|
+
| `expiryMs` | `number` | ✅ | Duration until expiry in ms |
|
|
146
139
|
| `owner` | `string` | ✅ | Sui address of the pass owner |
|
|
147
140
|
|
|
148
|
-
**Constraint:** `autoThreshold < escalateThreshold < budget`
|
|
149
|
-
|
|
150
141
|
---
|
|
151
142
|
|
|
152
143
|
### `EdgePass.fromTemplate(template, overrides)` → `EdgePassConfig`
|
|
153
144
|
|
|
154
|
-
Create a config from a pre-built template. Override any field.
|
|
155
|
-
|
|
156
145
|
```typescript
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Override specific fields
|
|
161
|
-
const config = EdgePass.fromTemplate('defi', {
|
|
162
|
-
budget: 25_000n * MIST_PER_SUI,
|
|
163
|
-
approvedMerchants: ['DeepBook', 'Cetus', 'Turbos'],
|
|
146
|
+
const config = EdgePass.fromTemplate('festival', {
|
|
147
|
+
approvedMerchants: ['Shuttle Express', 'Hydra Bar'],
|
|
164
148
|
owner: userAddress,
|
|
165
149
|
});
|
|
166
|
-
|
|
167
150
|
const pass = await sdk.create(config, signer);
|
|
168
151
|
```
|
|
169
152
|
|
|
@@ -171,12 +154,12 @@ const pass = await sdk.create(config, signer);
|
|
|
171
154
|
|
|
172
155
|
### `sdk.execute(pass, request, signer)` → `Promise<TransactionOutcome>`
|
|
173
156
|
|
|
174
|
-
Execute a transaction against an EdgePass.
|
|
157
|
+
Execute a transaction against an EdgePass. Blocked and escalated decisions are validated locally — they never touch the chain.
|
|
175
158
|
|
|
176
159
|
```typescript
|
|
177
160
|
const outcome = await sdk.execute(pass, {
|
|
178
161
|
merchant: 'Shuttle Express',
|
|
179
|
-
amount:
|
|
162
|
+
amount: 45n * MIST_PER_SUI,
|
|
180
163
|
}, signer);
|
|
181
164
|
|
|
182
165
|
switch (outcome.status) {
|
|
@@ -189,6 +172,9 @@ switch (outcome.status) {
|
|
|
189
172
|
case 'blocked':
|
|
190
173
|
console.log('blocked:', outcome.reason);
|
|
191
174
|
break;
|
|
175
|
+
case 'error':
|
|
176
|
+
console.error('infrastructure failure:', outcome.reason);
|
|
177
|
+
break;
|
|
192
178
|
}
|
|
193
179
|
```
|
|
194
180
|
|
|
@@ -196,144 +182,260 @@ switch (outcome.status) {
|
|
|
196
182
|
|
|
197
183
|
### `sdk.validate(pass, request)` → `PolicyValidation`
|
|
198
184
|
|
|
199
|
-
Preview
|
|
185
|
+
Preview a single transaction outcome. Zero network calls. Sub-millisecond.
|
|
200
186
|
|
|
201
187
|
```typescript
|
|
202
|
-
const preview = sdk.validate(pass, {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
188
|
+
const preview = sdk.validate(pass, { merchant, amount });
|
|
189
|
+
if (!preview.allowed) return showBlockedUI(preview.reason);
|
|
190
|
+
if (preview.requiresEscalation) return showEscalationUI(preview.reason);
|
|
191
|
+
const outcome = await sdk.execute(pass, request, signer);
|
|
192
|
+
```
|
|
206
193
|
|
|
207
|
-
|
|
208
|
-
showBlockedUI(preview.reason);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
194
|
+
---
|
|
211
195
|
|
|
212
|
-
|
|
213
|
-
showEscalationUI(preview.reason);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
196
|
+
### `sdk.fetch(objectId)` → `Promise<EdgePassObject | null>`
|
|
216
197
|
|
|
217
|
-
|
|
198
|
+
Fetch a live EdgePass from the Sui network.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const pass = await sdk.fetch('0x4e2f...8b91');
|
|
202
|
+
if (!pass) { console.log('not found'); return; }
|
|
218
203
|
```
|
|
219
204
|
|
|
220
205
|
---
|
|
221
206
|
|
|
222
207
|
### `sdk.revoke(pass, signer)` → `Promise<{ digest: string }>`
|
|
223
208
|
|
|
224
|
-
Revoke an EdgePass on-chain.
|
|
209
|
+
Revoke an EdgePass on-chain.
|
|
225
210
|
|
|
226
211
|
```typescript
|
|
227
212
|
const { digest } = await sdk.revoke(pass, signer);
|
|
228
|
-
console.log('revoked:', digest);
|
|
229
213
|
```
|
|
230
214
|
|
|
231
215
|
---
|
|
232
216
|
|
|
233
|
-
### `sdk.
|
|
234
|
-
|
|
235
|
-
Fetch a live EdgePass from the Sui network.
|
|
217
|
+
### `sdk.isValid(pass)` → `boolean`
|
|
236
218
|
|
|
237
|
-
|
|
238
|
-
const pass = await sdk.fetch('0x4e2f...8b91');
|
|
239
|
-
if (!pass) { console.log('EdgePass not found'); return; }
|
|
240
|
-
const remaining = sdk.remainingBudget(pass);
|
|
241
|
-
```
|
|
219
|
+
Returns `true` if the pass is active and not expired.
|
|
242
220
|
|
|
243
221
|
---
|
|
244
222
|
|
|
245
|
-
|
|
223
|
+
## Simulation
|
|
246
224
|
|
|
247
|
-
|
|
225
|
+
Plan an agent session without executing. Zero network calls. Sub-millisecond.
|
|
226
|
+
|
|
227
|
+
### `sdk.simulate(pass, requests[])` → `SimulationResult`
|
|
248
228
|
|
|
249
229
|
```typescript
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
230
|
+
const plan = sdk.simulate(pass, [
|
|
231
|
+
{ merchant: 'Shuttle Express', amount: 45n * MIST_PER_SUI },
|
|
232
|
+
{ merchant: 'Festival Kitchen', amount: 22n * MIST_PER_SUI },
|
|
233
|
+
{ merchant: 'ShadyTokens.xyz', amount: 1n },
|
|
234
|
+
{ merchant: 'Stage Access VIP', amount: 220n * MIST_PER_SUI },
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
console.log(plan.summary);
|
|
238
|
+
// { approvedCount: 2, blockedCount: 1, escalatedCount: 1, totalDecisions: 4 }
|
|
239
|
+
|
|
240
|
+
console.log(plan.utilizationPct); // projected budget usage after approved decisions
|
|
241
|
+
console.log(plan.totalSpend); // total MIST of approved decisions only
|
|
242
|
+
console.log(plan.remainingBudget); // projected remaining after all approved
|
|
243
|
+
|
|
244
|
+
// Inspect individual decisions
|
|
245
|
+
plan.approved.forEach(d => {
|
|
246
|
+
console.log(d.request.merchant, d.projectedRemaining);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Show plan, then execute
|
|
250
|
+
for (const decision of plan.approved) {
|
|
251
|
+
await sdk.execute(pass, decision.request, signer);
|
|
252
|
+
}
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
**`SimulationResult`:**
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
```typescript
|
|
258
|
+
interface SimulationResult {
|
|
259
|
+
decisions: SimulatedDecision[]; // all decisions in order
|
|
260
|
+
approved: SimulatedDecision[]; // will execute on-chain
|
|
261
|
+
blocked: SimulatedDecision[]; // rejected by policy
|
|
262
|
+
escalated: SimulatedDecision[]; // need human approval
|
|
263
|
+
totalSpend: bigint; // sum of approved amounts
|
|
264
|
+
remainingBudget: bigint; // projected remaining
|
|
265
|
+
utilizationPct: number; // 0-100
|
|
266
|
+
summary: {
|
|
267
|
+
approvedCount: number;
|
|
268
|
+
blockedCount: number;
|
|
269
|
+
escalatedCount: number;
|
|
270
|
+
totalDecisions: number;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
```
|
|
258
274
|
|
|
259
|
-
|
|
275
|
+
**`SimulatedDecision`:**
|
|
260
276
|
|
|
261
277
|
```typescript
|
|
262
|
-
|
|
263
|
-
|
|
278
|
+
interface SimulatedDecision {
|
|
279
|
+
request: TransactionRequest;
|
|
280
|
+
outcome: 'approved' | 'escalated' | 'blocked';
|
|
281
|
+
reason: string;
|
|
282
|
+
projectedSpent: bigint; // pass.spent after this decision
|
|
283
|
+
projectedRemaining: bigint; // budget remaining after this decision
|
|
264
284
|
}
|
|
265
285
|
```
|
|
266
286
|
|
|
267
287
|
---
|
|
268
288
|
|
|
269
|
-
|
|
289
|
+
## Budget Intelligence
|
|
270
290
|
|
|
271
|
-
|
|
291
|
+
### `sdk.budgetStatus(pass, nearLimitThreshold?)` → `BudgetStatus`
|
|
272
292
|
|
|
273
293
|
```typescript
|
|
274
|
-
sdk
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
logger.warn(`blocked: ${outcome.reason}`);
|
|
284
|
-
});
|
|
294
|
+
const status = sdk.budgetStatus(pass);
|
|
295
|
+
// {
|
|
296
|
+
// budget: 500000000000n,
|
|
297
|
+
// spent: 218000000000n,
|
|
298
|
+
// remaining: 282000000000n,
|
|
299
|
+
// utilizationPct: 43.6,
|
|
300
|
+
// isNearLimit: false, // true when > 80% (configurable)
|
|
301
|
+
// isExhausted: false,
|
|
302
|
+
// }
|
|
285
303
|
|
|
286
|
-
|
|
304
|
+
if (status.isExhausted) stopAgent();
|
|
305
|
+
if (status.isNearLimit) warnUser(`${status.utilizationPct.toFixed(1)}% of budget used`);
|
|
287
306
|
```
|
|
288
307
|
|
|
289
|
-
|
|
308
|
+
### `sdk.utilizationPct(pass)` → `number`
|
|
309
|
+
|
|
310
|
+
Budget utilization as 0-100. Use for progress bars.
|
|
311
|
+
|
|
312
|
+
### `sdk.isNearLimit(pass, threshold?)` → `boolean`
|
|
313
|
+
|
|
314
|
+
Returns `true` if utilization exceeds threshold (default 80%).
|
|
290
315
|
|
|
291
316
|
```typescript
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
{ type: 'blocked', outcome: { status: 'blocked', reason: string, auto: false }, pass, request }
|
|
317
|
+
sdk.isNearLimit(pass) // true if > 80% spent
|
|
318
|
+
sdk.isNearLimit(pass, 0.5) // true if > 50% spent
|
|
295
319
|
```
|
|
296
320
|
|
|
321
|
+
### `sdk.remainingBudget(pass)` → `bigint`
|
|
322
|
+
|
|
323
|
+
Remaining budget in MIST.
|
|
324
|
+
|
|
325
|
+
### `sdk.timeRemaining(pass)` → `number`
|
|
326
|
+
|
|
327
|
+
Milliseconds until expiry. Returns 0 if expired.
|
|
328
|
+
|
|
329
|
+
### `sdk.isExpiringSoon(pass, withinMs?)` → `boolean`
|
|
330
|
+
|
|
331
|
+
Returns `true` if pass expires within the given window (default 1 hour).
|
|
332
|
+
|
|
297
333
|
---
|
|
298
334
|
|
|
299
|
-
|
|
335
|
+
## withPolicy
|
|
336
|
+
|
|
337
|
+
Wrap any async function with EdgePass policy enforcement. The wrapped function only executes if the transaction is approved.
|
|
300
338
|
|
|
301
|
-
|
|
339
|
+
### `EdgePass.withPolicy(pass, signer, sdk, fn)`
|
|
302
340
|
|
|
303
341
|
```typescript
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
342
|
+
const safePurchase = EdgePass.withPolicy(pass, signer, sdk, async (request) => {
|
|
343
|
+
// This only runs if EdgePass approves the transaction
|
|
344
|
+
return await purchaseItem(request.merchant, request.amount);
|
|
345
|
+
});
|
|
308
346
|
|
|
309
|
-
|
|
347
|
+
const { outcome, result } = await safePurchase({
|
|
348
|
+
merchant: 'Hydra Bar',
|
|
349
|
+
amount: 32n * MIST_PER_SUI,
|
|
350
|
+
});
|
|
310
351
|
|
|
311
|
-
|
|
352
|
+
// outcome.status === 'approved' | 'blocked' | 'escalated' | 'error'
|
|
353
|
+
// result is undefined if blocked/escalated/error
|
|
354
|
+
```
|
|
312
355
|
|
|
313
|
-
|
|
356
|
+
Perfect for wrapping Vercel AI SDK tools:
|
|
314
357
|
|
|
315
358
|
```typescript
|
|
316
|
-
|
|
317
|
-
|
|
359
|
+
import { tool } from 'ai';
|
|
360
|
+
import { z } from 'zod';
|
|
361
|
+
|
|
362
|
+
export const purchaseTool = tool({
|
|
363
|
+
description: 'Purchase within EdgePass policy boundaries',
|
|
364
|
+
parameters: z.object({ merchant: z.string(), amountSUI: z.number() }),
|
|
365
|
+
execute: async ({ merchant, amountSUI }) => {
|
|
366
|
+
const safePurchase = EdgePass.withPolicy(pass, signer, sdk, async (req) => {
|
|
367
|
+
return await processPayment(req);
|
|
368
|
+
});
|
|
369
|
+
const { outcome } = await safePurchase({ merchant, amount: BigInt(Math.floor(amountSUI * 1e9)) });
|
|
370
|
+
if (outcome.status !== 'approved') return { success: false, reason: outcome.reason };
|
|
371
|
+
return { success: true, digest: outcome.digest };
|
|
372
|
+
}
|
|
373
|
+
});
|
|
318
374
|
```
|
|
319
375
|
|
|
320
376
|
---
|
|
321
377
|
|
|
322
|
-
##
|
|
378
|
+
## React Hooks
|
|
323
379
|
|
|
324
|
-
|
|
380
|
+
```typescript
|
|
381
|
+
import { useEdgePass, useBudgetStatus, useSimulate } from '@edge-protocol/sdk/react';
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### `useEdgePass(config)` → `UseEdgePassResult`
|
|
385
|
+
|
|
386
|
+
Full-featured hook. Fetches pass on mount, exposes execute/simulate/budgetStatus, refreshes after approved transactions.
|
|
325
387
|
|
|
326
388
|
```typescript
|
|
327
|
-
|
|
389
|
+
const {
|
|
390
|
+
pass, // EdgePassObject | null
|
|
391
|
+
loading, // boolean
|
|
392
|
+
error, // Error | null
|
|
393
|
+
execute, // (request) => Promise<TransactionOutcome>
|
|
394
|
+
simulate, // (requests[]) => SimulationResult | null
|
|
395
|
+
budgetStatus, // BudgetStatus | null
|
|
396
|
+
refresh, // () => Promise<void> — manually re-fetch
|
|
397
|
+
sdk, // EdgePass instance
|
|
398
|
+
} = useEdgePass({
|
|
399
|
+
passId: 'YOUR_PASS_ID',
|
|
400
|
+
network: 'mainnet',
|
|
401
|
+
enokiApiKey: 'YOUR_KEY',
|
|
402
|
+
signer, // optional — needed for execute()
|
|
403
|
+
autoRefresh: true, // re-fetch after approved execute (default: true)
|
|
404
|
+
pollInterval: 30_000, // poll every 30s (default: 0 = disabled)
|
|
405
|
+
});
|
|
406
|
+
```
|
|
328
407
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
408
|
+
### `useBudgetStatus(config)` → `BudgetStatus | null`
|
|
409
|
+
|
|
410
|
+
Lightweight hook for budget display components.
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
function BudgetBar({ passId }) {
|
|
414
|
+
const status = useBudgetStatus({ passId, network: 'mainnet', enokiApiKey: KEY });
|
|
415
|
+
return <progress value={status?.utilizationPct ?? 0} max={100} />;
|
|
416
|
+
}
|
|
334
417
|
```
|
|
335
418
|
|
|
336
|
-
###
|
|
419
|
+
### `useSimulate(config, requests[])` → `SimulationResult | null`
|
|
420
|
+
|
|
421
|
+
Reactive simulation hook. Re-runs whenever requests change.
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
function AgentPlanPreview({ passId, decisions }) {
|
|
425
|
+
const plan = useSimulate({ passId, network: 'mainnet', enokiApiKey: KEY }, decisions);
|
|
426
|
+
if (!plan) return null;
|
|
427
|
+
return (
|
|
428
|
+
<div>
|
|
429
|
+
<span>{plan.summary.approvedCount} will execute</span>
|
|
430
|
+
<span>{plan.summary.blockedCount} will be blocked</span>
|
|
431
|
+
</div>
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Templates
|
|
337
439
|
|
|
338
440
|
| Template | Budget | Auto ≤ | Escalate ≥ | Max/tx | Expiry |
|
|
339
441
|
|----------|--------|--------|------------|--------|--------|
|
|
@@ -345,26 +447,45 @@ EdgePass.fromTemplate('enterprise', { owner }) // $50k / 30 days
|
|
|
345
447
|
|
|
346
448
|
---
|
|
347
449
|
|
|
348
|
-
##
|
|
450
|
+
## Events System
|
|
349
451
|
|
|
350
|
-
|
|
452
|
+
Subscribe to transaction outcomes without polling. Chain-able.
|
|
351
453
|
|
|
352
454
|
```typescript
|
|
353
|
-
|
|
455
|
+
sdk
|
|
456
|
+
.on('approved', ({ outcome, pass, request }) => {
|
|
457
|
+
updateBudgetUI(pass);
|
|
458
|
+
auditLog.write(outcome.digest);
|
|
459
|
+
})
|
|
460
|
+
.on('escalated', ({ request }) => {
|
|
461
|
+
slack.notify(`Approve $${request.amount} at ${request.merchant}?`);
|
|
462
|
+
})
|
|
463
|
+
.on('blocked', ({ outcome }) => {
|
|
464
|
+
logger.warn(outcome.reason);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
await sdk.execute(pass, request, signer);
|
|
468
|
+
|
|
469
|
+
// Cleanup
|
|
470
|
+
sdk.off('approved', handler);
|
|
471
|
+
sdk.removeAllListeners();
|
|
354
472
|
```
|
|
355
473
|
|
|
356
|
-
|
|
474
|
+
Events fire for `approved`, `escalated`, `blocked` only. Infrastructure errors (`status: 'error'`) do not fire events — check `outcome.status === 'error'` explicitly.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## PolicyEngine
|
|
479
|
+
|
|
480
|
+
Access the validation engine directly.
|
|
357
481
|
|
|
358
482
|
```typescript
|
|
359
|
-
|
|
360
|
-
merchant: 'Shuttle Express',
|
|
361
|
-
amount: 18_500_000_000n,
|
|
362
|
-
});
|
|
363
|
-
// validation.allowed · validation.requiresEscalation · validation.reason
|
|
483
|
+
import { PolicyEngine } from '@edge-protocol/sdk';
|
|
364
484
|
```
|
|
365
485
|
|
|
366
|
-
|
|
486
|
+
### `PolicyEngine.validate(pass, request)` → `PolicyValidation`
|
|
367
487
|
|
|
488
|
+
**Validation rules (in order):**
|
|
368
489
|
1. Pass must be active
|
|
369
490
|
2. Pass must not be expired
|
|
370
491
|
3. Merchant must be in `approvedMerchants`
|
|
@@ -373,9 +494,17 @@ const validation = PolicyEngine.validate(pass, {
|
|
|
373
494
|
6. If amount > `escalateThreshold` → escalate
|
|
374
495
|
7. If amount ≤ `autoThreshold` → auto-approve
|
|
375
496
|
|
|
376
|
-
### `PolicyEngine.
|
|
497
|
+
### `PolicyEngine.simulate(pass, requests[])` → `SimulationResult`
|
|
377
498
|
|
|
499
|
+
Static version of `sdk.simulate()`.
|
|
500
|
+
|
|
501
|
+
### `PolicyEngine.isValid(pass)` → `boolean`
|
|
378
502
|
### `PolicyEngine.remainingBudget(pass)` → `bigint`
|
|
503
|
+
### `PolicyEngine.utilizationPct(pass)` → `number`
|
|
504
|
+
### `PolicyEngine.isNearLimit(pass, threshold?)` → `boolean`
|
|
505
|
+
### `PolicyEngine.budgetStatus(pass, threshold?)` → `BudgetStatus`
|
|
506
|
+
### `PolicyEngine.timeRemaining(pass)` → `number`
|
|
507
|
+
### `PolicyEngine.isExpiringSoon(pass, withinMs?)` → `boolean`
|
|
379
508
|
|
|
380
509
|
---
|
|
381
510
|
|
|
@@ -388,9 +517,11 @@ import type {
|
|
|
388
517
|
TransactionRequest,
|
|
389
518
|
TransactionOutcome,
|
|
390
519
|
PolicyValidation,
|
|
520
|
+
SimulatedDecision,
|
|
521
|
+
SimulationResult,
|
|
522
|
+
BudgetStatus,
|
|
391
523
|
Network,
|
|
392
524
|
EdgeSDKConfig,
|
|
393
|
-
EdgePassTemplate,
|
|
394
525
|
} from '@edge-protocol/sdk';
|
|
395
526
|
```
|
|
396
527
|
|
|
@@ -439,29 +570,61 @@ type TransactionOutcome =
|
|
|
439
570
|
| { status: 'escalated'; reason: string; auto: false }
|
|
440
571
|
| { status: 'blocked'; reason: string; auto: false }
|
|
441
572
|
| { status: 'error'; reason: string; code?: string; auto: false };
|
|
442
|
-
// error = infrastructure failure — transaction NOT submitted to chain
|
|
443
573
|
```
|
|
444
574
|
|
|
445
|
-
### `
|
|
575
|
+
### `SimulationResult`
|
|
446
576
|
|
|
447
577
|
```typescript
|
|
448
|
-
interface
|
|
449
|
-
|
|
450
|
-
|
|
578
|
+
interface SimulationResult {
|
|
579
|
+
decisions: SimulatedDecision[];
|
|
580
|
+
approved: SimulatedDecision[];
|
|
581
|
+
blocked: SimulatedDecision[];
|
|
582
|
+
escalated: SimulatedDecision[];
|
|
583
|
+
totalSpend: bigint;
|
|
584
|
+
remainingBudget: bigint;
|
|
585
|
+
utilizationPct: number;
|
|
586
|
+
summary: {
|
|
587
|
+
approvedCount: number;
|
|
588
|
+
blockedCount: number;
|
|
589
|
+
escalatedCount: number;
|
|
590
|
+
totalDecisions: number;
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### `SimulatedDecision`
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
interface SimulatedDecision {
|
|
599
|
+
request: TransactionRequest;
|
|
600
|
+
outcome: 'approved' | 'escalated' | 'blocked';
|
|
451
601
|
reason: string;
|
|
602
|
+
projectedSpent: bigint;
|
|
603
|
+
projectedRemaining: bigint;
|
|
452
604
|
}
|
|
453
605
|
```
|
|
454
606
|
|
|
455
|
-
### `
|
|
607
|
+
### `BudgetStatus`
|
|
456
608
|
|
|
457
609
|
```typescript
|
|
458
|
-
|
|
610
|
+
interface BudgetStatus {
|
|
611
|
+
budget: bigint;
|
|
612
|
+
spent: bigint;
|
|
613
|
+
remaining: bigint;
|
|
614
|
+
utilizationPct: number;
|
|
615
|
+
isNearLimit: boolean;
|
|
616
|
+
isExhausted: boolean;
|
|
617
|
+
}
|
|
459
618
|
```
|
|
460
619
|
|
|
461
|
-
### `
|
|
620
|
+
### `PolicyValidation`
|
|
462
621
|
|
|
463
622
|
```typescript
|
|
464
|
-
|
|
623
|
+
interface PolicyValidation {
|
|
624
|
+
allowed: boolean;
|
|
625
|
+
requiresEscalation: boolean;
|
|
626
|
+
reason: string;
|
|
627
|
+
}
|
|
465
628
|
```
|
|
466
629
|
|
|
467
630
|
---
|
|
@@ -482,15 +645,13 @@ import {
|
|
|
482
645
|
|
|
483
646
|
## Integration Examples
|
|
484
647
|
|
|
485
|
-
### AI Agent with
|
|
648
|
+
### AI Agent with Claude
|
|
486
649
|
|
|
487
650
|
```typescript
|
|
488
651
|
import { EdgePass, MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
489
652
|
import Anthropic from '@anthropic-ai/sdk';
|
|
490
653
|
|
|
491
654
|
const sdk = new EdgePass({ network: 'mainnet', enokiApiKey: KEY });
|
|
492
|
-
const claude = new Anthropic();
|
|
493
|
-
|
|
494
655
|
const pass = await sdk.create(
|
|
495
656
|
EdgePass.fromTemplate('festival', {
|
|
496
657
|
approvedMerchants: ['Shuttle Express', 'Hydra Bar', 'Stage Access VIP'],
|
|
@@ -499,26 +660,30 @@ const pass = await sdk.create(
|
|
|
499
660
|
signer
|
|
500
661
|
);
|
|
501
662
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
663
|
+
const claude = new Anthropic();
|
|
664
|
+
const response = await claude.messages.create({
|
|
665
|
+
model: 'claude-sonnet-4-6',
|
|
666
|
+
max_tokens: 500,
|
|
667
|
+
messages: [{ role: 'user', content: 'Plan my festival spending. Return JSON array of { merchant, amount } decisions.' }]
|
|
668
|
+
});
|
|
508
669
|
|
|
509
|
-
|
|
510
|
-
const outcome = await sdk.execute(pass, {
|
|
511
|
-
merchant,
|
|
512
|
-
amount: BigInt(Math.floor(amount * 1e9)),
|
|
513
|
-
}, signer);
|
|
670
|
+
const decisions = JSON.parse(response.content[0].text);
|
|
514
671
|
|
|
515
|
-
|
|
516
|
-
|
|
672
|
+
// Simulate first — show plan before executing
|
|
673
|
+
const plan = sdk.simulate(pass, decisions.map(d => ({
|
|
674
|
+
merchant: d.merchant,
|
|
675
|
+
amount: BigInt(Math.floor(d.amount * 1e9)),
|
|
676
|
+
})));
|
|
677
|
+
|
|
678
|
+
console.log(`Plan: ${plan.summary.approvedCount} approved, ${plan.summary.blockedCount} blocked`);
|
|
679
|
+
|
|
680
|
+
// Execute approved decisions
|
|
681
|
+
for (const decision of plan.approved) {
|
|
682
|
+
const outcome = await sdk.execute(pass, decision.request, signer);
|
|
683
|
+
console.log(outcome.status, outcome.digest);
|
|
517
684
|
}
|
|
518
685
|
```
|
|
519
686
|
|
|
520
|
-
---
|
|
521
|
-
|
|
522
687
|
### DeFi Trading Agent
|
|
523
688
|
|
|
524
689
|
```typescript
|
|
@@ -540,9 +705,7 @@ async function executeTrade(dex: string, amount: bigint) {
|
|
|
540
705
|
}
|
|
541
706
|
```
|
|
542
707
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
### Enterprise Payroll Agent
|
|
708
|
+
### Enterprise Treasury Agent
|
|
546
709
|
|
|
547
710
|
```typescript
|
|
548
711
|
const pass = await sdk.create(
|
|
@@ -563,52 +726,28 @@ for (const payment of scheduledPayments) {
|
|
|
563
726
|
|
|
564
727
|
---
|
|
565
728
|
|
|
566
|
-
### Subscription Manager
|
|
567
|
-
|
|
568
|
-
```typescript
|
|
569
|
-
const pass = await sdk.create(
|
|
570
|
-
EdgePass.fromTemplate('subscription', {
|
|
571
|
-
approvedMerchants: ['netflix.sui', 'spotify.sui', 'github.sui'],
|
|
572
|
-
owner: userAddress,
|
|
573
|
-
}),
|
|
574
|
-
signer
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
async function processRenewals(subscriptions: Subscription[]) {
|
|
578
|
-
for (const sub of subscriptions) {
|
|
579
|
-
if (!sdk.isValid(pass)) {
|
|
580
|
-
pass = await sdk.create(EdgePass.fromTemplate('subscription', { owner: userAddress }), signer);
|
|
581
|
-
}
|
|
582
|
-
await sdk.execute(pass, { merchant: sub.merchant, amount: sub.amount }, signer);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
---
|
|
588
|
-
|
|
589
729
|
## Error Handling
|
|
590
730
|
|
|
591
731
|
```typescript
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
console.error('SDK error:', error);
|
|
732
|
+
const outcome = await sdk.execute(pass, request, signer);
|
|
733
|
+
|
|
734
|
+
switch (outcome.status) {
|
|
735
|
+
case 'approved': // outcome.digest is the Sui tx hash
|
|
736
|
+
case 'escalated': // outcome.reason explains why
|
|
737
|
+
case 'blocked': // outcome.reason explains why
|
|
738
|
+
case 'error': // infrastructure failure — tx was NOT submitted
|
|
739
|
+
// outcome.code for programmatic handling
|
|
601
740
|
}
|
|
602
741
|
```
|
|
603
742
|
|
|
604
|
-
### Common reasons
|
|
743
|
+
### Common blocked reasons
|
|
605
744
|
|
|
606
745
|
| Reason | Cause |
|
|
607
746
|
|--------|-------|
|
|
608
747
|
| `EdgePass is inactive` | Pass was revoked |
|
|
609
|
-
| `EdgePass has expired` | `expiresAt`
|
|
610
|
-
| `Merchant "X" is not approved` |
|
|
611
|
-
| `Insufficient budget` | Remaining
|
|
748
|
+
| `EdgePass has expired` | `expiresAt` passed |
|
|
749
|
+
| `Merchant "X" is not approved` | Not in allowlist |
|
|
750
|
+
| `Insufficient budget` | Remaining < amount |
|
|
612
751
|
| `Amount exceeds per-transaction limit` | Amount > `maxPerTransaction` |
|
|
613
752
|
| `Amount exceeds escalation threshold` | Amount > `escalateThreshold` — escalated not blocked |
|
|
614
753
|
|
|
@@ -629,7 +768,7 @@ Agent calls sdk.execute() — many times, autonomously
|
|
|
629
768
|
│
|
|
630
769
|
├─▶ ExecutionEngine.buildPTB()
|
|
631
770
|
│ Programmable Transaction Block
|
|
632
|
-
│ validate → execute → update spent
|
|
771
|
+
│ validate → execute → update spent
|
|
633
772
|
│ atomic — any failure reverts everything
|
|
634
773
|
│
|
|
635
774
|
└─▶ Walrus audit receipt
|
|
@@ -638,11 +777,11 @@ Agent calls sdk.execute() — many times, autonomously
|
|
|
638
777
|
|
|
639
778
|
### Why PTBs matter
|
|
640
779
|
|
|
641
|
-
|
|
780
|
+
The policy check and spend update happen in one atomic block. If any step fails, everything reverts. No partial state. No race conditions.
|
|
642
781
|
|
|
643
782
|
### Why the object model matters
|
|
644
783
|
|
|
645
|
-
The EdgePass is a first-class owned object in the user's wallet. An agent executes against it without ever taking ownership. No
|
|
784
|
+
The EdgePass is a first-class owned object in the user's wallet. An agent executes against it without ever taking ownership. No admin key, no contract upgrade can change that.
|
|
646
785
|
|
|
647
786
|
---
|
|
648
787
|
|
|
@@ -674,7 +813,7 @@ public entry fun revoke_pass(pass: &mut EdgePass, ctx: &mut TxContext)
|
|
|
674
813
|
|
|
675
814
|
### On-chain enforcement
|
|
676
815
|
|
|
677
|
-
|
|
816
|
+
Five assertions run inside the Move VM on every spend:
|
|
678
817
|
|
|
679
818
|
```move
|
|
680
819
|
assert!(pass.active, EPassInactive);
|
|
@@ -684,72 +823,38 @@ assert!(pass.spent + amount <= pass.budget, EBudgetExceeded);
|
|
|
684
823
|
assert!(amount <= pass.escalate_threshold, EAmountExceedsEscalationThreshold);
|
|
685
824
|
```
|
|
686
825
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
---
|
|
690
|
-
|
|
691
|
-
## Competitive Positioning
|
|
692
|
-
|
|
693
|
-
Edge is the **policy layer** for the agentic economy. It is not a payment rail.
|
|
694
|
-
|
|
695
|
-
| Solution | Layer | Open Source | Sui Native | 3-line SDK |
|
|
696
|
-
|----------|-------|-------------|------------|------------|
|
|
697
|
-
| **Edge Protocol** | Policy enforcement | ✅ | ✅ | ✅ |
|
|
698
|
-
| x402 (Coinbase) | Payment rail | ✅ | ❌ | ❌ |
|
|
699
|
-
| ERC-4337 | Account abstraction | ✅ | ❌ EVM only | ❌ |
|
|
700
|
-
| Trust Wallet Agent Kit | Wallet interactions | ✅ | Partial | ❌ |
|
|
701
|
-
| Cobo Agentic Wallet | Custody | ❌ Enterprise | ❌ | ❌ |
|
|
702
|
-
| Nevermined | Metering + monetization | Partial | ❌ | ❌ |
|
|
703
|
-
| Skyfire | Identity + settlement | ❌ | ❌ | ❌ |
|
|
704
|
-
|
|
705
|
-
**Edge complements x402, it does not compete with it.**
|
|
706
|
-
|
|
707
|
-
x402 answers: *how does money move from agent to merchant?*
|
|
708
|
-
Edge answers: *should this agent be allowed to spend this money at all?*
|
|
709
|
-
|
|
710
|
-
Together they form a complete stack:
|
|
711
|
-
|
|
712
|
-
```
|
|
713
|
-
Edge (policy layer) → x402 (payment rail) → Settlement
|
|
714
|
-
"is this allowed?" "move the money"
|
|
715
|
-
```
|
|
826
|
+
A compromised agent cannot bypass the contract. The chain is the trust boundary.
|
|
716
827
|
|
|
717
828
|
---
|
|
718
829
|
|
|
719
830
|
## Security Model
|
|
720
831
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
### Layer 1 — TypeScript PolicyEngine (pre-flight)
|
|
724
|
-
|
|
725
|
-
Fast, zero network calls, under 1ms. Can be bypassed by a compromised agent runtime. Treat as a UX convenience and performance optimization — not a security boundary.
|
|
726
|
-
|
|
727
|
-
### Layer 2 — Sui Move Contract (source of truth)
|
|
832
|
+
### Layer 1 — TypeScript PolicyEngine
|
|
728
833
|
|
|
729
|
-
|
|
834
|
+
Fast, zero network calls, under 1ms. Can be bypassed by a compromised agent runtime. Treat as a UX convenience — not a security boundary.
|
|
730
835
|
|
|
731
|
-
|
|
836
|
+
### Layer 2 — Sui Move Contract
|
|
732
837
|
|
|
733
|
-
|
|
838
|
+
On-chain enforcement by the Sui VM. Cannot be bypassed.
|
|
734
839
|
|
|
735
840
|
```
|
|
736
841
|
sdk.validate() → TypeScript (instant preview, saves gas on rejections)
|
|
737
842
|
sdk.execute() → TypeScript + Move contract (atomic, tamper-proof, final)
|
|
738
843
|
```
|
|
739
844
|
|
|
740
|
-
### Production
|
|
845
|
+
### Production guidelines
|
|
741
846
|
|
|
742
|
-
-
|
|
743
|
-
- Use on-chain clock (Sui Clock `0x6`) for expiry verification in high-security deployments
|
|
847
|
+
- Always execute via the Move contract — the TypeScript layer is a preview
|
|
744
848
|
- Use verified Sui addresses in `approvedMerchants` rather than display name strings
|
|
745
849
|
- Keep ephemeral zkLogin keys in memory only — never persist to localStorage
|
|
850
|
+
- Fetch EdgePass state from chain before critical operations
|
|
746
851
|
|
|
747
|
-
### Known V2
|
|
852
|
+
### Known V2 security improvements
|
|
748
853
|
|
|
749
854
|
- Rolling time windows — `maxTransactionsPerHour`
|
|
750
|
-
- On-chain policy signatures
|
|
751
|
-
- Merchant address verification
|
|
752
|
-
- Rate limiting
|
|
855
|
+
- On-chain policy signatures
|
|
856
|
+
- Merchant address verification on-chain
|
|
857
|
+
- Rate limiting to prevent rapid budget drain
|
|
753
858
|
|
|
754
859
|
---
|
|
755
860
|
|
|
@@ -760,64 +865,19 @@ cd packages/sdk && pnpm test
|
|
|
760
865
|
```
|
|
761
866
|
|
|
762
867
|
```
|
|
763
|
-
📋 PolicyEngine.validate()
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
✓ blocks unlisted merchant
|
|
769
|
-
✓ blocks when budget exceeded
|
|
770
|
-
✓ blocks when expired
|
|
771
|
-
✓ blocks when inactive
|
|
772
|
-
✓ blocks when maxPerTransaction exceeded
|
|
773
|
-
✓ allows when maxPerTransaction is undefined
|
|
774
|
-
|
|
775
|
-
📋 PolicyEngine helpers
|
|
776
|
-
✓ isValid returns true for active pass
|
|
777
|
-
✓ isValid returns false for expired pass
|
|
778
|
-
✓ isValid returns false for inactive pass
|
|
779
|
-
✓ remainingBudget calculates correctly
|
|
780
|
-
✓ remainingBudget returns full budget when nothing spent
|
|
781
|
-
|
|
782
|
-
📋 EdgePass.fromTemplate()
|
|
783
|
-
✓ festival template has correct defaults
|
|
784
|
-
✓ gaming template has correct expiry
|
|
785
|
-
✓ defi template has correct budget
|
|
786
|
-
✓ enterprise template has correct budget
|
|
787
|
-
✓ fromTemplate allows budget override
|
|
788
|
-
✓ fromTemplate allows merchant override
|
|
789
|
-
✓ fromTemplate preserves owner
|
|
790
|
-
|
|
791
|
-
📋 Constants
|
|
792
|
-
✓ MIST_PER_SUI is 1_000_000_000
|
|
793
|
-
✓ all 5 templates exist
|
|
794
|
-
✓ all templates have required fields
|
|
795
|
-
✓ all templates have autoThreshold < escalateThreshold
|
|
796
|
-
✓ all templates have escalateThreshold < budget
|
|
797
|
-
|
|
798
|
-
📋 Events system
|
|
799
|
-
✓ on() returns sdk instance for chaining
|
|
800
|
-
✓ fires approved event on auto-approve
|
|
801
|
-
✓ fires blocked event on policy rejection
|
|
802
|
-
✓ fires escalated event above threshold
|
|
803
|
-
✓ off() removes listener
|
|
804
|
-
✓ removeAllListeners() clears all events
|
|
805
|
-
✓ multiple listeners fire for same event
|
|
868
|
+
📋 PolicyEngine.validate() 10 tests ✓
|
|
869
|
+
📋 PolicyEngine helpers 5 tests ✓
|
|
870
|
+
📋 EdgePass.fromTemplate() 7 tests ✓
|
|
871
|
+
📋 Constants 5 tests ✓
|
|
872
|
+
📋 Events system 7 tests ✓
|
|
806
873
|
|
|
807
874
|
34 passed · 0 failed ✅
|
|
808
875
|
```
|
|
809
876
|
|
|
810
877
|
---
|
|
811
878
|
|
|
812
|
-
## Links
|
|
813
|
-
|
|
814
|
-
- **npm:** [npmjs.com/package/@edge-protocol/sdk](https://npmjs.com/package/@edge-protocol/sdk)
|
|
815
|
-
- **GitHub:** [github.com/fluturecode/edge](https://github.com/fluturecode/edge)
|
|
816
|
-
- **Live Demo:** [edge-web-cyan.vercel.app](https://edge-web-cyan.vercel.app)
|
|
817
|
-
- **Contract on Mainnet:** [suiscan.xyz/mainnet/object/0x2ad62ac...](https://suiscan.xyz/mainnet/object/0x2ad62ac22e74172cc2e33cbebd7471fb16403831b3bdd1143d51935cefd1bbde)
|
|
818
|
-
|
|
819
|
-
---
|
|
820
|
-
|
|
821
879
|
*The best infrastructure is invisible.*
|
|
822
880
|
|
|
823
|
-
Built for
|
|
881
|
+
Built for Sui Overflow 2026 · MIT License
|
|
882
|
+
|
|
883
|
+
[npm](https://npmjs.com/package/@edge-protocol/sdk) · [GitHub](https://github.com/fluturecode/edge) · [Live Demo](https://edge-web-cyan.vercel.app)
|
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# @edge-protocol/sdk
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.com/package/@edge-protocol/sdk)
|
|
4
|
-
[](https://npmjs.com/package/@edge-protocol/sdk)
|
|
5
|
+
[](https://github.com/fluturecode/edge)
|
|
6
|
+
[](https://sui.io)
|
|
7
|
+
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Give agents your rules, not your keys.
|
|
10
10
|
|
|
11
|
-
[Live Demo](https://edge-web-cyan.vercel.app) · [Full Docs](
|
|
11
|
+
[Live Demo](https://edge-web-cyan.vercel.app) · [Full Docs](DOCS.md) · [GitHub](https://github.com/fluturecode/edge)
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
As autonomous agents begin managing real assets onchain, they need a trust layer that governs how they interact with them. Raw private keys are a security nightmare. Requiring human approval for every transaction defeats the purpose of automation.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
EdgePass is the policy layer — scoped, programmatic spend authority issued directly to agent runtimes, with cryptographic guardrails enforced by the Sui VM. Not a payment rail. Not a wallet. The boundary between what an agent can do and what it cannot.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -38,11 +38,11 @@ Every EdgePass is a native Sui Move object encoding five distinct governance dim
|
|
|
38
38
|
|
|
39
39
|
| Dimension | What it controls |
|
|
40
40
|
|-----------|-----------------|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
41
|
+
| BUDGET | Maximum global spending ceiling |
|
|
42
|
+
| VELOCITY | Auto-approve threshold before escalation fires |
|
|
43
|
+
| SCOPE | Explicit allowlist of approved merchants / contracts |
|
|
44
|
+
| TIME | Hard cryptographic expiration date |
|
|
45
|
+
| ESCALATION | Programmatic fallback when a limit is exceeded |
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -58,7 +58,8 @@ EdgePass.fromTemplate('defi', { owner }) // $10k · auto <$500 · esca
|
|
|
58
58
|
EdgePass.fromTemplate('enterprise', { owner }) // $50k · auto <$1k · escalate >$5k · 30d
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Example — brand licensing agent:
|
|
62
|
+
|
|
62
63
|
```typescript
|
|
63
64
|
EdgePass.fromTemplate('enterprise', {
|
|
64
65
|
approvedMerchants: ['nike-licensing.sui', 'brand-registry.sui'],
|
|
@@ -70,6 +71,54 @@ EdgePass.fromTemplate('enterprise', {
|
|
|
70
71
|
|
|
71
72
|
---
|
|
72
73
|
|
|
74
|
+
## 🔮 Simulate Before You Execute
|
|
75
|
+
|
|
76
|
+
Plan an agent's session without touching the chain. Zero network calls.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const plan = sdk.simulate(pass, [
|
|
80
|
+
{ merchant: 'Shuttle Express', amount: 45n * MIST_PER_SUI },
|
|
81
|
+
{ merchant: 'ShadyTokens.xyz', amount: 1n },
|
|
82
|
+
{ merchant: 'Stage Access VIP', amount: 220n * MIST_PER_SUI },
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
console.log(plan.summary);
|
|
86
|
+
// { approvedCount: 1, blockedCount: 1, escalatedCount: 1, totalDecisions: 3 }
|
|
87
|
+
|
|
88
|
+
console.log(plan.utilizationPct); // projected budget usage after approved decisions
|
|
89
|
+
console.log(plan.remainingBudget); // projected remaining in MIST
|
|
90
|
+
|
|
91
|
+
// Show plan to user, then execute approved decisions
|
|
92
|
+
for (const decision of plan.approved) {
|
|
93
|
+
await sdk.execute(pass, decision.request, signer);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 💰 Budget Intelligence
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const status = sdk.budgetStatus(pass);
|
|
103
|
+
// {
|
|
104
|
+
// budget: 500000000000n,
|
|
105
|
+
// spent: 218000000000n,
|
|
106
|
+
// remaining: 282000000000n,
|
|
107
|
+
// utilizationPct: 43.6,
|
|
108
|
+
// isNearLimit: false, // true when > 80%
|
|
109
|
+
// isExhausted: false,
|
|
110
|
+
// }
|
|
111
|
+
|
|
112
|
+
sdk.utilizationPct(pass) // 43.6
|
|
113
|
+
sdk.isNearLimit(pass) // false (default threshold: 80%)
|
|
114
|
+
sdk.isNearLimit(pass, 0.5) // true if > 50% spent
|
|
115
|
+
sdk.remainingBudget(pass) // 282000000000n MIST
|
|
116
|
+
sdk.timeRemaining(pass) // ms until expiry
|
|
117
|
+
sdk.isExpiringSoon(pass) // true if expires within 1 hour
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
73
122
|
## 🤖 Agent Framework Integration
|
|
74
123
|
|
|
75
124
|
### Vercel AI SDK / Mastra
|
|
@@ -91,14 +140,29 @@ export const autonomousPurchaseTool = tool({
|
|
|
91
140
|
amount: BigInt(Math.floor(amountSUI * 1e9)),
|
|
92
141
|
}, agentSigner);
|
|
93
142
|
|
|
94
|
-
if (outcome.status === 'blocked') return { success: false, error: `Blocked
|
|
95
|
-
if (outcome.status === 'escalated') return { success: false, error: `
|
|
143
|
+
if (outcome.status === 'blocked') return { success: false, error: `Blocked: ${outcome.reason}` };
|
|
144
|
+
if (outcome.status === 'escalated') return { success: false, error: `Escalated: ${outcome.reason}` };
|
|
96
145
|
|
|
97
146
|
return { success: true, digest: outcome.digest };
|
|
98
147
|
}
|
|
99
148
|
});
|
|
100
149
|
```
|
|
101
150
|
|
|
151
|
+
### `withPolicy` — Wrap Any Tool
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const safePurchase = EdgePass.withPolicy(pass, signer, sdk, async (request) => {
|
|
155
|
+
return await purchaseItem(request.merchant, request.amount);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// safePurchase enforces EdgePass policy automatically
|
|
159
|
+
// blocked/escalated never reach your tool logic
|
|
160
|
+
const { outcome, result } = await safePurchase({
|
|
161
|
+
merchant: 'Hydra Bar',
|
|
162
|
+
amount: 32n * MIST_PER_SUI,
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
102
166
|
### Native Agent System Prompt
|
|
103
167
|
|
|
104
168
|
```typescript
|
|
@@ -118,6 +182,41 @@ The transaction was executed on-chain and logged to Walrus.
|
|
|
118
182
|
|
|
119
183
|
---
|
|
120
184
|
|
|
185
|
+
## ⚛️ React Hook
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { useEdgePass } from '@edge-protocol/sdk/react';
|
|
189
|
+
|
|
190
|
+
function AgentDashboard({ passId, signer }) {
|
|
191
|
+
const { pass, execute, simulate, budgetStatus, loading } = useEdgePass({
|
|
192
|
+
passId,
|
|
193
|
+
network: 'mainnet',
|
|
194
|
+
enokiApiKey: process.env.NEXT_PUBLIC_ENOKI_API_KEY!,
|
|
195
|
+
signer,
|
|
196
|
+
autoRefresh: true, // re-fetch after every approved execute
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (loading) return <Spinner />;
|
|
200
|
+
if (!pass) return <div>Pass not found</div>;
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div>
|
|
204
|
+
<progress value={budgetStatus?.utilizationPct} max={100} />
|
|
205
|
+
<button onClick={() => execute({ merchant: 'Hydra Bar', amount: 32n * MIST_PER_SUI })}>
|
|
206
|
+
Purchase
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Three hooks available:
|
|
214
|
+
- `useEdgePass` — full featured: fetch, execute, simulate, budgetStatus, refresh
|
|
215
|
+
- `useBudgetStatus` — lightweight budget display
|
|
216
|
+
- `useSimulate` — reactive simulation when requests change
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
121
220
|
## 📊 Execution Results
|
|
122
221
|
|
|
123
222
|
Every `sdk.execute()` returns a structured outcome — not a flat string:
|
|
@@ -127,6 +226,7 @@ type TransactionOutcome =
|
|
|
127
226
|
| { status: 'approved'; digest: string; auto: true; }
|
|
128
227
|
| { status: 'escalated'; reason: string; auto: false; }
|
|
129
228
|
| { status: 'blocked'; reason: string; auto: false; }
|
|
229
|
+
| { status: 'error'; reason: string; code?: string; auto: false; }
|
|
130
230
|
|
|
131
231
|
// Approved
|
|
132
232
|
{ status: 'approved', digest: '4REcPLezK8gF...', auto: true }
|
|
@@ -164,7 +264,7 @@ await sdk.execute(pass, request, signer);
|
|
|
164
264
|
|
|
165
265
|
## 🔌 Pluggable Escalation Handlers
|
|
166
266
|
|
|
167
|
-
Route escalation alerts to
|
|
267
|
+
Route escalation alerts to Slack, Telegram, or your dashboard:
|
|
168
268
|
|
|
169
269
|
```typescript
|
|
170
270
|
sdk.on('escalated', async ({ outcome, request }) => {
|
|
@@ -179,22 +279,8 @@ sdk.on('escalated', async ({ outcome, request }) => {
|
|
|
179
279
|
|
|
180
280
|
---
|
|
181
281
|
|
|
182
|
-
## 📜 Cryptographic Audit Trail
|
|
183
|
-
|
|
184
|
-
Every execution writes an immutable receipt to Walrus — decentralized, tamper-evident, permanent. No database. No server. Cryptographically committed.
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
const outcome = await sdk.execute(pass, request, signer);
|
|
188
|
-
// audit receipt automatically written to Walrus
|
|
189
|
-
// verifiable at walruscan.com/mainnet/blob/{blobId}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
282
|
## 🔍 Preview Without Executing
|
|
195
283
|
|
|
196
|
-
Zero network calls. Sub-millisecond. Use for UI previews:
|
|
197
|
-
|
|
198
284
|
```typescript
|
|
199
285
|
const preview = sdk.validate(pass, { merchant, amount });
|
|
200
286
|
// { allowed: boolean, requiresEscalation: boolean, reason: string }
|
|
@@ -230,8 +316,6 @@ assert!(amount <= pass.escalate_threshold, EAmountExceedsEscalationThreshold);
|
|
|
230
316
|
|
|
231
317
|
If any assertion fails, the entire transaction reverts. A compromised agent cannot bypass the contract. **The chain is the trust boundary.**
|
|
232
318
|
|
|
233
|
-
For production: always execute via the Move contract. The TypeScript layer is a preview — the chain is the guarantee.
|
|
234
|
-
|
|
235
319
|
---
|
|
236
320
|
|
|
237
321
|
## 🧪 Testing
|
|
@@ -273,6 +357,11 @@ pnpm add @edge-protocol/sdk
|
|
|
273
357
|
yarn add @edge-protocol/sdk
|
|
274
358
|
```
|
|
275
359
|
|
|
360
|
+
React hook (requires React 18+):
|
|
361
|
+
```bash
|
|
362
|
+
import { useEdgePass } from '@edge-protocol/sdk/react';
|
|
363
|
+
```
|
|
364
|
+
|
|
276
365
|
---
|
|
277
366
|
|
|
278
367
|
## Competitive Positioning
|
|
@@ -302,6 +391,6 @@ Edge (policy layer) → x402 (payment rail) → Settlement
|
|
|
302
391
|
|
|
303
392
|
*The best infrastructure is invisible.*
|
|
304
393
|
|
|
305
|
-
Built for
|
|
394
|
+
Built for Sui Overflow 2026 · MIT License
|
|
306
395
|
|
|
307
396
|
[GitHub](https://github.com/fluturecode/edge) · [npm](https://npmjs.com/package/@edge-protocol/sdk)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edge-protocol/sdk",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Programmable trust infrastructure for autonomous AI agents on Sui. Give agents your rules, not your keys.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,4 +44,4 @@
|
|
|
44
44
|
"ts-node": "^10.9.2",
|
|
45
45
|
"typescript": "^5.9.3"
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|