@edge-protocol/sdk 0.9.0 → 0.9.2

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 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: $300 · auto-approve: <$50 · escalate: >$100 · merchants: [...] · expiry: 48h
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 three outcomes:
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
- ### Requirements
90
-
91
- - Node.js 18+
92
- - TypeScript 5.0+ (recommended)
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: 300n * MIST_PER_SUI, // total spend limit
124
- autoThreshold: 50n * MIST_PER_SUI, // auto-approve below this
125
- escalateThreshold: 100n * MIST_PER_SUI, // escalate above this
126
- maxPerTransaction: 200n * MIST_PER_SUI, // optional hard cap per tx
127
- approvedMerchants: ['Shuttle Express', 'Hydra Bar'],
128
- expiryMs: 48 * 60 * 60 * 1000, // 48 hours
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 — verifiable on Suiscan
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 milliseconds |
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
- // Use a template as-is
158
- const config = EdgePass.fromTemplate('festival', { owner: userAddress });
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. Policy is validated before touching the chainblocked and escalated transactions never reach Sui.
157
+ Execute a transaction against an EdgePass. Blocked and escalated decisions are validated locallythey never touch the chain.
175
158
 
176
159
  ```typescript
177
160
  const outcome = await sdk.execute(pass, {
178
161
  merchant: 'Shuttle Express',
179
- amount: 18_500_000_000n, // 18.5 SUI in MIST
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 the outcome without executing. Zero network calls. Sub-millisecond.
185
+ Preview a single transaction outcome. Zero network calls. Sub-millisecond.
200
186
 
201
187
  ```typescript
202
- const preview = sdk.validate(pass, {
203
- merchant: 'Shuttle Express',
204
- amount: 18_500_000_000n,
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
- if (!preview.allowed) {
208
- showBlockedUI(preview.reason);
209
- return;
210
- }
194
+ ---
211
195
 
212
- if (preview.requiresEscalation) {
213
- showEscalationUI(preview.reason);
214
- return;
215
- }
196
+ ### `sdk.fetch(objectId)` → `Promise<EdgePassObject | null>`
216
197
 
217
- const outcome = await sdk.execute(pass, request, signer);
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. All future `execute()` calls return `blocked` immediately.
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.fetch(objectId)` → `Promise<EdgePassObject | null>`
234
-
235
- Fetch a live EdgePass from the Sui network.
217
+ ### `sdk.isValid(pass)` → `boolean`
236
218
 
237
- ```typescript
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
- ### `sdk.remainingBudget(pass)` → `bigint`
223
+ ## Simulation
246
224
 
247
- Returns remaining budget in MIST.
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 remaining = sdk.remainingBudget(pass);
251
- const remainingSUI = Number(remaining) / Number(MIST_PER_SUI);
252
- console.log(`$${remainingSUI.toFixed(2)} remaining`);
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
- ### `sdk.isValid(pass)` → `boolean`
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
- Returns `true` if the pass is active and not expired.
275
+ **`SimulatedDecision`:**
260
276
 
261
277
  ```typescript
262
- if (!sdk.isValid(pass)) {
263
- pass = await sdk.create(config, signer);
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
- ### `sdk.on(event, listener)` → `this`
289
+ ## Budget Intelligence
270
290
 
271
- Subscribe to transaction outcomes. Fires automatically after every `sdk.execute()` call. Returns the SDK instance for chaining.
291
+ ### `sdk.budgetStatus(pass, nearLimitThreshold?)` `BudgetStatus`
272
292
 
273
293
  ```typescript
274
- sdk
275
- .on('approved', ({ outcome, pass, request }) => {
276
- console.log('executed:', outcome.digest);
277
- updateBudgetUI(pass);
278
- })
279
- .on('escalated', ({ outcome, request }) => {
280
- notifyUser(`Approve $${request.amount} at ${request.merchant}?`);
281
- })
282
- .on('blocked', ({ outcome, request }) => {
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
- await sdk.execute(pass, request, signer);
304
+ if (status.isExhausted) stopAgent();
305
+ if (status.isNearLimit) warnUser(`${status.utilizationPct.toFixed(1)}% of budget used`);
287
306
  ```
288
307
 
289
- **Event payload:**
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
- { type: 'approved', outcome: { status: 'approved', digest: string, auto: true }, pass, request }
293
- { type: 'escalated', outcome: { status: 'escalated', reason: string, auto: false }, pass, request }
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
- ### `sdk.off(event, listener)` → `this`
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
- Remove a specific listener.
339
+ ### `EdgePass.withPolicy(pass, signer, sdk, fn)`
302
340
 
303
341
  ```typescript
304
- const onApproved = ({ outcome }) => console.log(outcome.digest);
305
- sdk.on('approved', onApproved);
306
- sdk.off('approved', onApproved);
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
- ### `sdk.removeAllListeners(event?)` `this`
352
+ // outcome.status === 'approved' | 'blocked' | 'escalated' | 'error'
353
+ // result is undefined if blocked/escalated/error
354
+ ```
312
355
 
313
- Remove all listeners for an event, or all events if none specified.
356
+ Perfect for wrapping Vercel AI SDK tools:
314
357
 
315
358
  ```typescript
316
- sdk.removeAllListeners('approved'); // remove all approved listeners
317
- sdk.removeAllListeners(); // remove all listeners
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
- ## Templates
378
+ ## React Hooks
323
379
 
324
- Pre-configured trust boundaries for common use cases. Every template is a starting point — override any field.
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
- import { EdgePass, EDGE_TEMPLATES } from '@edge-protocol/sdk';
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
- EdgePass.fromTemplate('festival', { owner }) // $300 / 48h
330
- EdgePass.fromTemplate('gaming', { owner }) // $50 / 4h session
331
- EdgePass.fromTemplate('subscription', { owner }) // $200 / 30 days
332
- EdgePass.fromTemplate('defi', { owner }) // $10k / 7 days
333
- EdgePass.fromTemplate('enterprise', { owner }) // $50k / 30 days
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
- ### Template defaults
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
- ## PolicyEngine
450
+ ## Events System
349
451
 
350
- Access the policy engine directly for custom validation flows.
452
+ Subscribe to transaction outcomes without polling. Chain-able.
351
453
 
352
454
  ```typescript
353
- import { PolicyEngine } from '@edge-protocol/sdk';
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
- ### `PolicyEngine.validate(pass, request)` `PolicyValidation`
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
- const validation = PolicyEngine.validate(pass, {
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
- **Validation rules (in order):**
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.isValid(pass)` → `boolean`
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
- ### `PolicyValidation`
575
+ ### `SimulationResult`
446
576
 
447
577
  ```typescript
448
- interface PolicyValidation {
449
- allowed: boolean;
450
- requiresEscalation: boolean;
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
- ### `Network`
607
+ ### `BudgetStatus`
456
608
 
457
609
  ```typescript
458
- type Network = 'mainnet' | 'testnet' | 'devnet';
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
- ### `EdgePassTemplate`
620
+ ### `PolicyValidation`
462
621
 
463
622
  ```typescript
464
- type EdgePassTemplate = 'festival' | 'gaming' | 'subscription' | 'defi' | 'enterprise';
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 EdgePass
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
- async function agentLoop(scenario: string) {
503
- const response = await claude.messages.create({
504
- model: 'claude-sonnet-4-6',
505
- max_tokens: 500,
506
- messages: [{ role: 'user', content: `Festival scenario: "${scenario}". Return JSON: { merchant: string, amount: number }` }]
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
- const { merchant, amount } = JSON.parse(response.content[0].text);
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
- if (outcome.status === 'escalated') await notifyUser(`Approve $${amount} at ${merchant}?`);
516
- return outcome;
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
- try {
593
- const outcome = await sdk.execute(pass, request, signer);
594
- switch (outcome.status) {
595
- case 'approved': break; // outcome.digest is the Sui tx hash
596
- case 'escalated': break; // outcome.reason explains why
597
- case 'blocked': break; // outcome.reason explains why
598
- }
599
- } catch (error) {
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` timestamp passed |
610
- | `Merchant "X" is not approved` | Merchant not in allowlist |
611
- | `Insufficient budget` | Remaining budget < amount |
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 → emit event
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
- PTBs 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.
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 contract upgrade, no admin key, no reentrancy attack can change that.
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
- `execute_transaction` validates every spend at the protocol level before recording it. Five assertions run inside the Move VM if any fails, the entire transaction reverts:
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
- This means policy enforcement is not client-side. A compromised agent cannot bypass the contract. The chain is the trust boundary.
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
- Edge has two enforcement layers:
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
- On-chain enforcement by the Sui VM. Cannot be bypassed. The EdgePass object validates budget, expiry, and merchant allowlist independently at the protocol level.
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
- **For production deployments:** Always execute via the Move contract. The TypeScript layer is a preview — the chain is the guarantee.
836
+ ### Layer 2 Sui Move Contract
732
837
 
733
- ### The Two-Layer Pattern
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 Guidelines
845
+ ### Production guidelines
741
846
 
742
- - Fetch EdgePass state from chain before executing never trust locally cached config
743
- - Use on-chain clock (Sui Clock `0x6`) for expiry verification in high-security deployments
847
+ - Always execute via the Move contractthe 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 Security Improvements
852
+ ### Known V2 security improvements
748
853
 
749
854
  - Rolling time windows — `maxTransactionsPerHour`
750
- - On-chain policy signatures — cryptographic commitment prevents client-side tampering
751
- - Merchant address verification — verified Sui addresses on-chain
752
- - Rate limiting prevent rapid budget drain attacks
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
- auto-approves under $50
765
- auto-approves at exactly $50
766
- escalates above $100
767
- escalates at exactly $101
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 [Sui Overflow 2026](https://overflow.sui.io) · MIT License
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)