@edge-protocol/sdk 0.5.0 → 0.5.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.
Files changed (3) hide show
  1. package/DOCS.md +173 -143
  2. package/README.md +21 -3
  3. package/package.json +1 -1
package/DOCS.md CHANGED
@@ -22,6 +22,10 @@ pnpm add @edge-protocol/sdk
22
22
  - [Error Handling](#error-handling)
23
23
  - [Architecture](#architecture)
24
24
  - [Move Contract](#move-contract)
25
+ - [Competitive Positioning](#competitive-positioning)
26
+ - [Security Model](#security-model)
27
+ - [Testing](#testing)
28
+ - [Links](#links)
25
29
 
26
30
  ---
27
31
 
@@ -178,17 +182,11 @@ const outcome = await sdk.execute(pass, {
178
182
  switch (outcome.status) {
179
183
  case 'approved':
180
184
  console.log('tx digest:', outcome.digest);
181
- // audit receipt written to Walrus automatically
182
185
  break;
183
-
184
186
  case 'escalated':
185
- // notify user — outcome.reason explains why
186
187
  await sendPushNotification(outcome.reason);
187
- // re-execute after user approves
188
188
  break;
189
-
190
189
  case 'blocked':
191
- // policy rejected — outcome.reason explains why
192
190
  console.log('blocked:', outcome.reason);
193
191
  break;
194
192
  }
@@ -198,7 +196,7 @@ switch (outcome.status) {
198
196
 
199
197
  ### `sdk.validate(pass, request)` → `PolicyValidation`
200
198
 
201
- Preview the outcome without executing. Zero network calls. Sub-millisecond. Use for UI previews before execution.
199
+ Preview the outcome without executing. Zero network calls. Sub-millisecond.
202
200
 
203
201
  ```typescript
204
202
  const preview = sdk.validate(pass, {
@@ -206,10 +204,6 @@ const preview = sdk.validate(pass, {
206
204
  amount: 18_500_000_000n,
207
205
  });
208
206
 
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
207
  if (!preview.allowed) {
214
208
  showBlockedUI(preview.reason);
215
209
  return;
@@ -220,7 +214,6 @@ if (preview.requiresEscalation) {
220
214
  return;
221
215
  }
222
216
 
223
- // Safe to execute
224
217
  const outcome = await sdk.execute(pass, request, signer);
225
218
  ```
226
219
 
@@ -243,14 +236,8 @@ Fetch a live EdgePass from the Sui network.
243
236
 
244
237
  ```typescript
245
238
  const pass = await sdk.fetch('0x4e2f...8b91');
246
-
247
- if (!pass) {
248
- console.log('EdgePass not found');
249
- return;
250
- }
251
-
239
+ if (!pass) { console.log('EdgePass not found'); return; }
252
240
  const remaining = sdk.remainingBudget(pass);
253
- console.log('remaining:', remaining);
254
241
  ```
255
242
 
256
243
  ---
@@ -273,13 +260,65 @@ Returns `true` if the pass is active and not expired.
273
260
 
274
261
  ```typescript
275
262
  if (!sdk.isValid(pass)) {
276
- // create a new pass
277
263
  pass = await sdk.create(config, signer);
278
264
  }
279
265
  ```
280
266
 
281
267
  ---
282
268
 
269
+ ### `sdk.on(event, listener)` → `this`
270
+
271
+ Subscribe to transaction outcomes. Fires automatically after every `sdk.execute()` call. Returns the SDK instance for chaining.
272
+
273
+ ```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
+ });
285
+
286
+ await sdk.execute(pass, request, signer);
287
+ ```
288
+
289
+ **Event payload:**
290
+
291
+ ```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 }
295
+ ```
296
+
297
+ ---
298
+
299
+ ### `sdk.off(event, listener)` → `this`
300
+
301
+ Remove a specific listener.
302
+
303
+ ```typescript
304
+ const onApproved = ({ outcome }) => console.log(outcome.digest);
305
+ sdk.on('approved', onApproved);
306
+ sdk.off('approved', onApproved);
307
+ ```
308
+
309
+ ---
310
+
311
+ ### `sdk.removeAllListeners(event?)` → `this`
312
+
313
+ Remove all listeners for an event, or all events if none specified.
314
+
315
+ ```typescript
316
+ sdk.removeAllListeners('approved'); // remove all approved listeners
317
+ sdk.removeAllListeners(); // remove all listeners
318
+ ```
319
+
320
+ ---
321
+
283
322
  ## Templates
284
323
 
285
324
  Pre-configured trust boundaries for common use cases. Every template is a starting point — override any field.
@@ -287,7 +326,6 @@ Pre-configured trust boundaries for common use cases. Every template is a starti
287
326
  ```typescript
288
327
  import { EdgePass, EDGE_TEMPLATES } from '@edge-protocol/sdk';
289
328
 
290
- // Available templates
291
329
  EdgePass.fromTemplate('festival', { owner }) // $300 / 48h
292
330
  EdgePass.fromTemplate('gaming', { owner }) // $50 / 4h session
293
331
  EdgePass.fromTemplate('subscription', { owner }) // $200 / 30 days
@@ -305,15 +343,6 @@ EdgePass.fromTemplate('enterprise', { owner }) // $50k / 30 days
305
343
  | `defi` | 10,000 SUI | 500 SUI | 1,000 SUI | 2,000 SUI | 7d |
306
344
  | `enterprise` | 50,000 SUI | 1,000 SUI | 5,000 SUI | 10,000 SUI | 30d |
307
345
 
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
346
  ---
318
347
 
319
348
  ## PolicyEngine
@@ -326,17 +355,12 @@ import { PolicyEngine } from '@edge-protocol/sdk';
326
355
 
327
356
  ### `PolicyEngine.validate(pass, request)` → `PolicyValidation`
328
357
 
329
- Validates a transaction request. Same logic used internally by `sdk.execute()`.
330
-
331
358
  ```typescript
332
359
  const validation = PolicyEngine.validate(pass, {
333
360
  merchant: 'Shuttle Express',
334
361
  amount: 18_500_000_000n,
335
362
  });
336
-
337
- // validation.allowed → boolean
338
- // validation.requiresEscalation → boolean
339
- // validation.reason → string
363
+ // validation.allowed · validation.requiresEscalation · validation.reason
340
364
  ```
341
365
 
342
366
  **Validation rules (in order):**
@@ -351,16 +375,8 @@ const validation = PolicyEngine.validate(pass, {
351
375
 
352
376
  ### `PolicyEngine.isValid(pass)` → `boolean`
353
377
 
354
- ```typescript
355
- const valid = PolicyEngine.isValid(pass);
356
- ```
357
-
358
378
  ### `PolicyEngine.remainingBudget(pass)` → `bigint`
359
379
 
360
- ```typescript
361
- const remaining = PolicyEngine.remainingBudget(pass);
362
- ```
363
-
364
380
  ---
365
381
 
366
382
  ## Types
@@ -382,13 +398,13 @@ import type {
382
398
 
383
399
  ```typescript
384
400
  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
401
+ budget: bigint;
402
+ autoThreshold: bigint;
403
+ escalateThreshold: bigint;
404
+ maxPerTransaction?: bigint;
405
+ approvedMerchants: string[];
406
+ expiryMs: number;
407
+ owner: string;
392
408
  }
393
409
  ```
394
410
 
@@ -396,12 +412,12 @@ interface EdgePassConfig {
396
412
 
397
413
  ```typescript
398
414
  interface EdgePassObject {
399
- id: string; // Sui object ID
415
+ id: string;
400
416
  config: EdgePassConfig;
401
- spent: bigint; // total spent so far in MIST
417
+ spent: bigint;
402
418
  active: boolean;
403
- createdAt: number; // Unix timestamp
404
- expiresAt: number; // Unix timestamp
419
+ createdAt: number;
420
+ expiresAt: number;
405
421
  }
406
422
  ```
407
423
 
@@ -409,9 +425,9 @@ interface EdgePassObject {
409
425
 
410
426
  ```typescript
411
427
  interface TransactionRequest {
412
- merchant: string; // merchant identifier
413
- amount: bigint; // amount in MIST
414
- metadata?: Record<string, string>; // optional metadata
428
+ merchant: string;
429
+ amount: bigint;
430
+ metadata?: Record<string, string>;
415
431
  }
416
432
  ```
417
433
 
@@ -428,9 +444,9 @@ type TransactionOutcome =
428
444
 
429
445
  ```typescript
430
446
  interface PolicyValidation {
431
- allowed: boolean;
432
- requiresEscalation: boolean;
433
- reason: string;
447
+ allowed: boolean;
448
+ requiresEscalation: boolean;
449
+ reason: string;
434
450
  }
435
451
  ```
436
452
 
@@ -466,8 +482,6 @@ import {
466
482
 
467
483
  ### AI Agent with EdgePass
468
484
 
469
- A Claude LLM making autonomous festival purchases within an EdgePass.
470
-
471
485
  ```typescript
472
486
  import { EdgePass, MIST_PER_SUI } from '@edge-protocol/sdk';
473
487
  import Anthropic from '@anthropic-ai/sdk';
@@ -475,7 +489,6 @@ import Anthropic from '@anthropic-ai/sdk';
475
489
  const sdk = new EdgePass({ network: 'mainnet', enokiApiKey: KEY });
476
490
  const claude = new Anthropic();
477
491
 
478
- // 1. User creates EdgePass once
479
492
  const pass = await sdk.create(
480
493
  EdgePass.fromTemplate('festival', {
481
494
  approvedMerchants: ['Shuttle Express', 'Hydra Bar', 'Stage Access VIP'],
@@ -484,30 +497,20 @@ const pass = await sdk.create(
484
497
  signer
485
498
  );
486
499
 
487
- // 2. Agent loop — runs autonomously
488
500
  async function agentLoop(scenario: string) {
489
501
  const response = await claude.messages.create({
490
502
  model: 'claude-sonnet-4-6',
491
503
  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
- }]
504
+ messages: [{ role: 'user', content: `Festival scenario: "${scenario}". Return JSON: { merchant: string, amount: number }` }]
498
505
  });
499
506
 
500
507
  const { merchant, amount } = JSON.parse(response.content[0].text);
501
-
502
508
  const outcome = await sdk.execute(pass, {
503
509
  merchant,
504
510
  amount: BigInt(Math.floor(amount * 1e9)),
505
511
  }, signer);
506
512
 
507
- if (outcome.status === 'escalated') {
508
- await notifyUser(`Approve $${amount} at ${merchant}?`);
509
- }
510
-
513
+ if (outcome.status === 'escalated') await notifyUser(`Approve $${amount} at ${merchant}?`);
511
514
  return outcome;
512
515
  }
513
516
  ```
@@ -527,21 +530,10 @@ const pass = await sdk.create(
527
530
  );
528
531
 
529
532
  async function executeTrade(dex: string, amount: bigint) {
530
- // validate before network call
531
533
  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
-
534
+ if (!preview.allowed) { logger.warn(`blocked: ${preview.reason}`); return; }
535
+ if (preview.requiresEscalation) { await riskTeam.requestApproval({ dex, amount }); return; }
543
536
  const outcome = await sdk.execute(pass, { merchant: dex, amount }, signer);
544
- // audit log written to Walrus automatically
545
537
  logger.info(`Trade executed: ${outcome.digest}`);
546
538
  }
547
539
  ```
@@ -555,22 +547,15 @@ const pass = await sdk.create(
555
547
  EdgePass.fromTemplate('enterprise', {
556
548
  approvedMerchants: ['vendor-a.sui', 'vendor-b.sui'],
557
549
  budget: 100_000n * MIST_PER_SUI,
558
- escalateThreshold: 10_000n * MIST_PER_SUI,
550
+ escalateThreshold: 10_000n * MIST_PER_SUI,
559
551
  owner: cfoAddress,
560
552
  }),
561
553
  signer
562
554
  );
563
555
 
564
556
  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
557
+ const outcome = await sdk.execute(pass, { merchant: payment.vendor, amount: payment.amount }, signer);
558
+ if (outcome.status === 'escalated') await cfo.requestApproval(payment);
574
559
  }
575
560
  ```
576
561
 
@@ -587,20 +572,12 @@ const pass = await sdk.create(
587
572
  signer
588
573
  );
589
574
 
590
- // Runs monthly — agent handles all renewals
591
575
  async function processRenewals(subscriptions: Subscription[]) {
592
576
  for (const sub of subscriptions) {
593
577
  if (!sdk.isValid(pass)) {
594
- pass = await sdk.create(
595
- EdgePass.fromTemplate('subscription', { owner: userAddress }),
596
- signer
597
- );
578
+ pass = await sdk.create(EdgePass.fromTemplate('subscription', { owner: userAddress }), signer);
598
579
  }
599
-
600
- await sdk.execute(pass, {
601
- merchant: sub.merchant,
602
- amount: sub.amount,
603
- }, signer);
580
+ await sdk.execute(pass, { merchant: sub.merchant, amount: sub.amount }, signer);
604
581
  }
605
582
  }
606
583
  ```
@@ -612,20 +589,12 @@ async function processRenewals(subscriptions: Subscription[]) {
612
589
  ```typescript
613
590
  try {
614
591
  const outcome = await sdk.execute(pass, request, signer);
615
-
616
592
  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;
593
+ case 'approved': break; // outcome.digest is the Sui tx hash
594
+ case 'escalated': break; // outcome.reason explains why
595
+ case 'blocked': break; // outcome.reason explains why
626
596
  }
627
597
  } catch (error) {
628
- // network error, signing failure, expired credentials, etc.
629
598
  console.error('SDK error:', error);
630
599
  }
631
600
  ```
@@ -639,7 +608,7 @@ try {
639
608
  | `Merchant "X" is not approved` | Merchant not in allowlist |
640
609
  | `Insufficient budget` | Remaining budget < amount |
641
610
  | `Amount exceeds per-transaction limit` | Amount > `maxPerTransaction` |
642
- | `Amount exceeds escalation threshold` | Amount > `escalateThreshold` (not blocked — escalated) |
611
+ | `Amount exceeds escalation threshold` | Amount > `escalateThreshold` — escalated not blocked |
643
612
 
644
613
  ---
645
614
 
@@ -667,11 +636,11 @@ Agent calls sdk.execute() — many times, autonomously
667
636
 
668
637
  ### Why PTBs matter
669
638
 
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.
639
+ 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.
671
640
 
672
641
  ### Why the object model matters
673
642
 
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.
643
+ 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.
675
644
 
676
645
  ---
677
646
 
@@ -687,35 +656,87 @@ Network: Sui Testnet (Mainnet coming)
687
656
  ### Contract functions
688
657
 
689
658
  ```move
690
- // Create a new EdgePass
691
659
  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,
660
+ budget: u64, auto_threshold: u64, escalate_threshold: u64,
661
+ expiry_ms: u64, approved_merchants: vector<String>,
662
+ clock: &Clock, ctx: &mut TxContext,
699
663
  )
700
664
 
701
- // Execute a transaction against an EdgePass
702
665
  public entry fun execute_transaction(
703
- pass: &mut EdgePass,
704
- amount: u64,
705
- merchant: String,
706
- clock: &Clock,
707
- ctx: &mut TxContext,
666
+ pass: &mut EdgePass, amount: u64, merchant: String,
667
+ clock: &Clock, ctx: &mut TxContext,
708
668
  )
709
669
 
710
- // Revoke an EdgePass
711
- public entry fun revoke_pass(
712
- pass: &mut EdgePass,
713
- ctx: &mut TxContext,
714
- )
670
+ public entry fun revoke_pass(pass: &mut EdgePass, ctx: &mut TxContext)
671
+ ```
672
+
673
+ ---
674
+
675
+ ## Competitive Positioning
676
+
677
+ Edge is the **policy layer** for the agentic economy. It is not a payment rail.
678
+
679
+ | Solution | Layer | Open Source | Sui Native | 3-line SDK |
680
+ |----------|-------|-------------|------------|------------|
681
+ | **Edge Protocol** | Policy enforcement | ✅ | ✅ | ✅ |
682
+ | x402 (Coinbase) | Payment rail | ✅ | ❌ | ❌ |
683
+ | ERC-4337 | Account abstraction | ✅ | ❌ EVM only | ❌ |
684
+ | Trust Wallet Agent Kit | Wallet interactions | ✅ | Partial | ❌ |
685
+ | Cobo Agentic Wallet | Custody | ❌ Enterprise | ❌ | ❌ |
686
+ | Nevermined | Metering + monetization | Partial | ❌ | ❌ |
687
+ | Skyfire | Identity + settlement | ❌ | ❌ | ❌ |
688
+
689
+ **Edge complements x402, it does not compete with it.**
690
+
691
+ x402 answers: *how does money move from agent to merchant?*
692
+ Edge answers: *should this agent be allowed to spend this money at all?*
693
+
694
+ Together they form a complete stack:
695
+
696
+ ```
697
+ Edge (policy layer) → x402 (payment rail) → Settlement
698
+ "is this allowed?" "move the money"
715
699
  ```
716
700
 
717
701
  ---
718
702
 
703
+ ## Security Model
704
+
705
+ Edge has two enforcement layers:
706
+
707
+ ### Layer 1 — TypeScript PolicyEngine (pre-flight)
708
+
709
+ 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.
710
+
711
+ ### Layer 2 — Sui Move Contract (source of truth)
712
+
713
+ On-chain enforcement by the Sui VM. Cannot be bypassed. The EdgePass object validates budget, expiry, and merchant allowlist independently at the protocol level.
714
+
715
+ **For production deployments:** Always execute via the Move contract. The TypeScript layer is a preview — the chain is the guarantee.
716
+
717
+ ### The Two-Layer Pattern
718
+
719
+ ```
720
+ sdk.validate() → TypeScript (instant preview, saves gas on rejections)
721
+ sdk.execute() → TypeScript + Move contract (atomic, tamper-proof, final)
722
+ ```
723
+
724
+ ### Production Guidelines
725
+
726
+ - Fetch EdgePass state from chain before executing — never trust locally cached config
727
+ - Use on-chain clock (Sui Clock `0x6`) for expiry verification in high-security deployments
728
+ - Use verified Sui addresses in `approvedMerchants` rather than display name strings
729
+ - Keep ephemeral zkLogin keys in memory only — never persist to localStorage
730
+
731
+ ### Known V2 Security Improvements
732
+
733
+ - Rolling time windows — `maxTransactionsPerHour`
734
+ - On-chain policy signatures — cryptographic commitment prevents client-side tampering
735
+ - Merchant address verification — verified Sui addresses on-chain
736
+ - Rate limiting — prevent rapid budget drain attacks
737
+
738
+ ---
739
+
719
740
  ## Testing
720
741
 
721
742
  ```bash
@@ -758,7 +779,16 @@ cd packages/sdk && pnpm test
758
779
  ✓ all templates have autoThreshold < escalateThreshold
759
780
  ✓ all templates have escalateThreshold < budget
760
781
 
761
- 27 passed · 0 failed ✅
782
+ 📋 Events system
783
+ ✓ on() returns sdk instance for chaining
784
+ ✓ fires approved event on auto-approve
785
+ ✓ fires blocked event on policy rejection
786
+ ✓ fires escalated event above threshold
787
+ ✓ off() removes listener
788
+ ✓ removeAllListeners() clears all events
789
+ ✓ multiple listeners fire for same event
790
+
791
+ 34 passed · 0 failed ✅
762
792
  ```
763
793
 
764
794
  ---
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  **Give agents your rules, not your keys.**
14
14
 
15
- [Live Demo](https://edge-web-cyan.vercel.app) · [Full Docs](https://github.com/fluturecode/edge/blob/main/packages/sdk/DOCS.md) · [GitHub](https://github.com/fluturecode/edge)
15
+ [Live Demo](https://edge-web-git-main-fluturecodes-projects.vercel.app) · [Full Docs](https://github.com/fluturecode/edge/blob/main/packages/sdk/DOCS.md) · [GitHub](https://github.com/fluturecode/edge)
16
16
 
17
17
  </div>
18
18
 
@@ -213,7 +213,7 @@ Festival Mode: Claude autonomously manages purchases within an EdgePass.
213
213
  3 transactions · $54.50 spent · 0 wallet popups
214
214
  ```
215
215
 
216
- [See it live →](https://edge-web-cyan.vercel.app)
216
+ [See it live →](https://edge-web-git-main-fluturecodes-projects.vercel.app)
217
217
 
218
218
  ---
219
219
 
@@ -230,9 +230,27 @@ pnpm test
230
230
  ✓ blocks when budget exceeded
231
231
  ✓ blocks when expired
232
232
  ✓ blocks when inactive
233
- 6/6 passing ✅
233
+ 34/34 passing ✅
234
234
  ```
235
235
 
236
+
237
+ ---
238
+
239
+ ## Security Model
240
+
241
+ Edge has two enforcement layers:
242
+
243
+ **Layer 1 — TypeScript PolicyEngine** — pre-flight validation, zero network calls, under 1ms. Can be bypassed by a malicious agent runtime. Use as a UX convenience, not a security boundary.
244
+
245
+ **Layer 2 — Sui Move Contract** — on-chain enforcement by the Sui VM. Cannot be bypassed. The EdgePass object validates budget, expiry, and merchant allowlist independently. This is the source of truth.
246
+
247
+ ```
248
+ sdk.validate() → TypeScript check (fast preview, no gas)
249
+ sdk.execute() → TypeScript check + Move contract check (atomic, final)
250
+ ```
251
+
252
+ For production: always execute via the Move contract. The TypeScript layer is a preview — the chain is the guarantee.
253
+
236
254
  ---
237
255
 
238
256
  ## Move Contract
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edge-protocol/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",