@edge-protocol/sdk 0.5.1 → 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.
- package/DOCS.md +112 -152
- package/README.md +18 -0
- 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.
|
|
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,7 +260,6 @@ 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
|
```
|
|
@@ -297,21 +283,15 @@ sdk
|
|
|
297
283
|
logger.warn(`blocked: ${outcome.reason}`);
|
|
298
284
|
});
|
|
299
285
|
|
|
300
|
-
// events fire automatically on execute
|
|
301
286
|
await sdk.execute(pass, request, signer);
|
|
302
287
|
```
|
|
303
288
|
|
|
304
289
|
**Event payload:**
|
|
305
290
|
|
|
306
291
|
```typescript
|
|
307
|
-
|
|
308
|
-
{ type: 'approved', outcome: { status: 'approved', digest: string, auto: true }, pass, request }
|
|
309
|
-
|
|
310
|
-
// escalated
|
|
292
|
+
{ type: 'approved', outcome: { status: 'approved', digest: string, auto: true }, pass, request }
|
|
311
293
|
{ type: 'escalated', outcome: { status: 'escalated', reason: string, auto: false }, pass, request }
|
|
312
|
-
|
|
313
|
-
// blocked
|
|
314
|
-
{ type: 'blocked', outcome: { status: 'blocked', reason: string, auto: false }, pass, request }
|
|
294
|
+
{ type: 'blocked', outcome: { status: 'blocked', reason: string, auto: false }, pass, request }
|
|
315
295
|
```
|
|
316
296
|
|
|
317
297
|
---
|
|
@@ -322,9 +302,7 @@ Remove a specific listener.
|
|
|
322
302
|
|
|
323
303
|
```typescript
|
|
324
304
|
const onApproved = ({ outcome }) => console.log(outcome.digest);
|
|
325
|
-
|
|
326
305
|
sdk.on('approved', onApproved);
|
|
327
|
-
// later...
|
|
328
306
|
sdk.off('approved', onApproved);
|
|
329
307
|
```
|
|
330
308
|
|
|
@@ -348,7 +326,6 @@ Pre-configured trust boundaries for common use cases. Every template is a starti
|
|
|
348
326
|
```typescript
|
|
349
327
|
import { EdgePass, EDGE_TEMPLATES } from '@edge-protocol/sdk';
|
|
350
328
|
|
|
351
|
-
// Available templates
|
|
352
329
|
EdgePass.fromTemplate('festival', { owner }) // $300 / 48h
|
|
353
330
|
EdgePass.fromTemplate('gaming', { owner }) // $50 / 4h session
|
|
354
331
|
EdgePass.fromTemplate('subscription', { owner }) // $200 / 30 days
|
|
@@ -366,15 +343,6 @@ EdgePass.fromTemplate('enterprise', { owner }) // $50k / 30 days
|
|
|
366
343
|
| `defi` | 10,000 SUI | 500 SUI | 1,000 SUI | 2,000 SUI | 7d |
|
|
367
344
|
| `enterprise` | 50,000 SUI | 1,000 SUI | 5,000 SUI | 10,000 SUI | 30d |
|
|
368
345
|
|
|
369
|
-
### Accessing raw template values
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
import { EDGE_TEMPLATES } from '@edge-protocol/sdk';
|
|
373
|
-
|
|
374
|
-
console.log(EDGE_TEMPLATES.festival.budget); // 300_000_000_000n
|
|
375
|
-
console.log(EDGE_TEMPLATES.defi.escalateThreshold); // 1_000_000_000_000n
|
|
376
|
-
```
|
|
377
|
-
|
|
378
346
|
---
|
|
379
347
|
|
|
380
348
|
## PolicyEngine
|
|
@@ -387,17 +355,12 @@ import { PolicyEngine } from '@edge-protocol/sdk';
|
|
|
387
355
|
|
|
388
356
|
### `PolicyEngine.validate(pass, request)` → `PolicyValidation`
|
|
389
357
|
|
|
390
|
-
Validates a transaction request. Same logic used internally by `sdk.execute()`.
|
|
391
|
-
|
|
392
358
|
```typescript
|
|
393
359
|
const validation = PolicyEngine.validate(pass, {
|
|
394
360
|
merchant: 'Shuttle Express',
|
|
395
361
|
amount: 18_500_000_000n,
|
|
396
362
|
});
|
|
397
|
-
|
|
398
|
-
// validation.allowed → boolean
|
|
399
|
-
// validation.requiresEscalation → boolean
|
|
400
|
-
// validation.reason → string
|
|
363
|
+
// validation.allowed · validation.requiresEscalation · validation.reason
|
|
401
364
|
```
|
|
402
365
|
|
|
403
366
|
**Validation rules (in order):**
|
|
@@ -412,16 +375,8 @@ const validation = PolicyEngine.validate(pass, {
|
|
|
412
375
|
|
|
413
376
|
### `PolicyEngine.isValid(pass)` → `boolean`
|
|
414
377
|
|
|
415
|
-
```typescript
|
|
416
|
-
const valid = PolicyEngine.isValid(pass);
|
|
417
|
-
```
|
|
418
|
-
|
|
419
378
|
### `PolicyEngine.remainingBudget(pass)` → `bigint`
|
|
420
379
|
|
|
421
|
-
```typescript
|
|
422
|
-
const remaining = PolicyEngine.remainingBudget(pass);
|
|
423
|
-
```
|
|
424
|
-
|
|
425
380
|
---
|
|
426
381
|
|
|
427
382
|
## Types
|
|
@@ -443,13 +398,13 @@ import type {
|
|
|
443
398
|
|
|
444
399
|
```typescript
|
|
445
400
|
interface EdgePassConfig {
|
|
446
|
-
budget:
|
|
447
|
-
autoThreshold:
|
|
448
|
-
escalateThreshold:
|
|
449
|
-
maxPerTransaction?: bigint;
|
|
450
|
-
approvedMerchants:
|
|
451
|
-
expiryMs:
|
|
452
|
-
owner:
|
|
401
|
+
budget: bigint;
|
|
402
|
+
autoThreshold: bigint;
|
|
403
|
+
escalateThreshold: bigint;
|
|
404
|
+
maxPerTransaction?: bigint;
|
|
405
|
+
approvedMerchants: string[];
|
|
406
|
+
expiryMs: number;
|
|
407
|
+
owner: string;
|
|
453
408
|
}
|
|
454
409
|
```
|
|
455
410
|
|
|
@@ -457,12 +412,12 @@ interface EdgePassConfig {
|
|
|
457
412
|
|
|
458
413
|
```typescript
|
|
459
414
|
interface EdgePassObject {
|
|
460
|
-
id: string;
|
|
415
|
+
id: string;
|
|
461
416
|
config: EdgePassConfig;
|
|
462
|
-
spent: bigint;
|
|
417
|
+
spent: bigint;
|
|
463
418
|
active: boolean;
|
|
464
|
-
createdAt: number;
|
|
465
|
-
expiresAt: number;
|
|
419
|
+
createdAt: number;
|
|
420
|
+
expiresAt: number;
|
|
466
421
|
}
|
|
467
422
|
```
|
|
468
423
|
|
|
@@ -470,9 +425,9 @@ interface EdgePassObject {
|
|
|
470
425
|
|
|
471
426
|
```typescript
|
|
472
427
|
interface TransactionRequest {
|
|
473
|
-
merchant: string;
|
|
474
|
-
amount: bigint;
|
|
475
|
-
metadata?: Record<string, string>;
|
|
428
|
+
merchant: string;
|
|
429
|
+
amount: bigint;
|
|
430
|
+
metadata?: Record<string, string>;
|
|
476
431
|
}
|
|
477
432
|
```
|
|
478
433
|
|
|
@@ -489,9 +444,9 @@ type TransactionOutcome =
|
|
|
489
444
|
|
|
490
445
|
```typescript
|
|
491
446
|
interface PolicyValidation {
|
|
492
|
-
allowed:
|
|
493
|
-
requiresEscalation:
|
|
494
|
-
reason:
|
|
447
|
+
allowed: boolean;
|
|
448
|
+
requiresEscalation: boolean;
|
|
449
|
+
reason: string;
|
|
495
450
|
}
|
|
496
451
|
```
|
|
497
452
|
|
|
@@ -527,8 +482,6 @@ import {
|
|
|
527
482
|
|
|
528
483
|
### AI Agent with EdgePass
|
|
529
484
|
|
|
530
|
-
A Claude LLM making autonomous festival purchases within an EdgePass.
|
|
531
|
-
|
|
532
485
|
```typescript
|
|
533
486
|
import { EdgePass, MIST_PER_SUI } from '@edge-protocol/sdk';
|
|
534
487
|
import Anthropic from '@anthropic-ai/sdk';
|
|
@@ -536,7 +489,6 @@ import Anthropic from '@anthropic-ai/sdk';
|
|
|
536
489
|
const sdk = new EdgePass({ network: 'mainnet', enokiApiKey: KEY });
|
|
537
490
|
const claude = new Anthropic();
|
|
538
491
|
|
|
539
|
-
// 1. User creates EdgePass once
|
|
540
492
|
const pass = await sdk.create(
|
|
541
493
|
EdgePass.fromTemplate('festival', {
|
|
542
494
|
approvedMerchants: ['Shuttle Express', 'Hydra Bar', 'Stage Access VIP'],
|
|
@@ -545,30 +497,20 @@ const pass = await sdk.create(
|
|
|
545
497
|
signer
|
|
546
498
|
);
|
|
547
499
|
|
|
548
|
-
// 2. Agent loop — runs autonomously
|
|
549
500
|
async function agentLoop(scenario: string) {
|
|
550
501
|
const response = await claude.messages.create({
|
|
551
502
|
model: 'claude-sonnet-4-6',
|
|
552
503
|
max_tokens: 500,
|
|
553
|
-
messages: [{
|
|
554
|
-
role: 'user',
|
|
555
|
-
content: `Festival scenario: "${scenario}".
|
|
556
|
-
Decide what to purchase. Return JSON:
|
|
557
|
-
{ merchant: string, amount: number }`
|
|
558
|
-
}]
|
|
504
|
+
messages: [{ role: 'user', content: `Festival scenario: "${scenario}". Return JSON: { merchant: string, amount: number }` }]
|
|
559
505
|
});
|
|
560
506
|
|
|
561
507
|
const { merchant, amount } = JSON.parse(response.content[0].text);
|
|
562
|
-
|
|
563
508
|
const outcome = await sdk.execute(pass, {
|
|
564
509
|
merchant,
|
|
565
510
|
amount: BigInt(Math.floor(amount * 1e9)),
|
|
566
511
|
}, signer);
|
|
567
512
|
|
|
568
|
-
if (outcome.status === 'escalated') {
|
|
569
|
-
await notifyUser(`Approve $${amount} at ${merchant}?`);
|
|
570
|
-
}
|
|
571
|
-
|
|
513
|
+
if (outcome.status === 'escalated') await notifyUser(`Approve $${amount} at ${merchant}?`);
|
|
572
514
|
return outcome;
|
|
573
515
|
}
|
|
574
516
|
```
|
|
@@ -588,21 +530,10 @@ const pass = await sdk.create(
|
|
|
588
530
|
);
|
|
589
531
|
|
|
590
532
|
async function executeTrade(dex: string, amount: bigint) {
|
|
591
|
-
// validate before network call
|
|
592
533
|
const preview = sdk.validate(pass, { merchant: dex, amount });
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
logger.warn(`Trade blocked: ${preview.reason}`);
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (preview.requiresEscalation) {
|
|
600
|
-
await riskTeam.requestApproval({ dex, amount, reason: preview.reason });
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
|
|
534
|
+
if (!preview.allowed) { logger.warn(`blocked: ${preview.reason}`); return; }
|
|
535
|
+
if (preview.requiresEscalation) { await riskTeam.requestApproval({ dex, amount }); return; }
|
|
604
536
|
const outcome = await sdk.execute(pass, { merchant: dex, amount }, signer);
|
|
605
|
-
// audit log written to Walrus automatically
|
|
606
537
|
logger.info(`Trade executed: ${outcome.digest}`);
|
|
607
538
|
}
|
|
608
539
|
```
|
|
@@ -616,22 +547,15 @@ const pass = await sdk.create(
|
|
|
616
547
|
EdgePass.fromTemplate('enterprise', {
|
|
617
548
|
approvedMerchants: ['vendor-a.sui', 'vendor-b.sui'],
|
|
618
549
|
budget: 100_000n * MIST_PER_SUI,
|
|
619
|
-
escalateThreshold:
|
|
550
|
+
escalateThreshold: 10_000n * MIST_PER_SUI,
|
|
620
551
|
owner: cfoAddress,
|
|
621
552
|
}),
|
|
622
553
|
signer
|
|
623
554
|
);
|
|
624
555
|
|
|
625
556
|
for (const payment of scheduledPayments) {
|
|
626
|
-
const outcome = await sdk.execute(pass, {
|
|
627
|
-
|
|
628
|
-
amount: payment.amount,
|
|
629
|
-
}, signer);
|
|
630
|
-
|
|
631
|
-
if (outcome.status === 'escalated') {
|
|
632
|
-
await cfo.requestApproval(payment); // new vendor or large amount
|
|
633
|
-
}
|
|
634
|
-
// 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);
|
|
635
559
|
}
|
|
636
560
|
```
|
|
637
561
|
|
|
@@ -648,20 +572,12 @@ const pass = await sdk.create(
|
|
|
648
572
|
signer
|
|
649
573
|
);
|
|
650
574
|
|
|
651
|
-
// Runs monthly — agent handles all renewals
|
|
652
575
|
async function processRenewals(subscriptions: Subscription[]) {
|
|
653
576
|
for (const sub of subscriptions) {
|
|
654
577
|
if (!sdk.isValid(pass)) {
|
|
655
|
-
pass = await sdk.create(
|
|
656
|
-
EdgePass.fromTemplate('subscription', { owner: userAddress }),
|
|
657
|
-
signer
|
|
658
|
-
);
|
|
578
|
+
pass = await sdk.create(EdgePass.fromTemplate('subscription', { owner: userAddress }), signer);
|
|
659
579
|
}
|
|
660
|
-
|
|
661
|
-
await sdk.execute(pass, {
|
|
662
|
-
merchant: sub.merchant,
|
|
663
|
-
amount: sub.amount,
|
|
664
|
-
}, signer);
|
|
580
|
+
await sdk.execute(pass, { merchant: sub.merchant, amount: sub.amount }, signer);
|
|
665
581
|
}
|
|
666
582
|
}
|
|
667
583
|
```
|
|
@@ -673,20 +589,12 @@ async function processRenewals(subscriptions: Subscription[]) {
|
|
|
673
589
|
```typescript
|
|
674
590
|
try {
|
|
675
591
|
const outcome = await sdk.execute(pass, request, signer);
|
|
676
|
-
|
|
677
592
|
switch (outcome.status) {
|
|
678
|
-
case 'approved':
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
case 'escalated':
|
|
682
|
-
// needs human review — outcome.reason explains why
|
|
683
|
-
break;
|
|
684
|
-
case 'blocked':
|
|
685
|
-
// policy rejected — outcome.reason explains why
|
|
686
|
-
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
|
|
687
596
|
}
|
|
688
597
|
} catch (error) {
|
|
689
|
-
// network error, signing failure, expired credentials, etc.
|
|
690
598
|
console.error('SDK error:', error);
|
|
691
599
|
}
|
|
692
600
|
```
|
|
@@ -700,7 +608,7 @@ try {
|
|
|
700
608
|
| `Merchant "X" is not approved` | Merchant not in allowlist |
|
|
701
609
|
| `Insufficient budget` | Remaining budget < amount |
|
|
702
610
|
| `Amount exceeds per-transaction limit` | Amount > `maxPerTransaction` |
|
|
703
|
-
| `Amount exceeds escalation threshold` | Amount > `escalateThreshold`
|
|
611
|
+
| `Amount exceeds escalation threshold` | Amount > `escalateThreshold` — escalated not blocked |
|
|
704
612
|
|
|
705
613
|
---
|
|
706
614
|
|
|
@@ -728,11 +636,11 @@ Agent calls sdk.execute() — many times, autonomously
|
|
|
728
636
|
|
|
729
637
|
### Why PTBs matter
|
|
730
638
|
|
|
731
|
-
PTBs
|
|
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.
|
|
732
640
|
|
|
733
641
|
### Why the object model matters
|
|
734
642
|
|
|
735
|
-
The EdgePass is a first-class owned object in the user's wallet. An agent
|
|
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.
|
|
736
644
|
|
|
737
645
|
---
|
|
738
646
|
|
|
@@ -748,35 +656,87 @@ Network: Sui Testnet (Mainnet coming)
|
|
|
748
656
|
### Contract functions
|
|
749
657
|
|
|
750
658
|
```move
|
|
751
|
-
// Create a new EdgePass
|
|
752
659
|
public entry fun create_pass(
|
|
753
|
-
budget: u64,
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
expiry_ms: u64,
|
|
757
|
-
approved_merchants: vector<String>,
|
|
758
|
-
clock: &Clock,
|
|
759
|
-
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,
|
|
760
663
|
)
|
|
761
664
|
|
|
762
|
-
// Execute a transaction against an EdgePass
|
|
763
665
|
public entry fun execute_transaction(
|
|
764
|
-
pass: &mut EdgePass,
|
|
765
|
-
|
|
766
|
-
merchant: String,
|
|
767
|
-
clock: &Clock,
|
|
768
|
-
ctx: &mut TxContext,
|
|
666
|
+
pass: &mut EdgePass, amount: u64, merchant: String,
|
|
667
|
+
clock: &Clock, ctx: &mut TxContext,
|
|
769
668
|
)
|
|
770
669
|
|
|
771
|
-
|
|
772
|
-
public entry fun revoke_pass(
|
|
773
|
-
pass: &mut EdgePass,
|
|
774
|
-
ctx: &mut TxContext,
|
|
775
|
-
)
|
|
670
|
+
public entry fun revoke_pass(pass: &mut EdgePass, ctx: &mut TxContext)
|
|
776
671
|
```
|
|
777
672
|
|
|
778
673
|
---
|
|
779
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"
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
---
|
|
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
|
+
|
|
780
740
|
## Testing
|
|
781
741
|
|
|
782
742
|
```bash
|
package/README.md
CHANGED
|
@@ -233,6 +233,24 @@ pnpm test
|
|
|
233
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