@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.
- package/DOCS.md +173 -143
- package/README.md +21 -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.
|
|
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:
|
|
386
|
-
autoThreshold:
|
|
387
|
-
escalateThreshold:
|
|
388
|
-
maxPerTransaction?: bigint;
|
|
389
|
-
approvedMerchants:
|
|
390
|
-
expiryMs:
|
|
391
|
-
owner:
|
|
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;
|
|
415
|
+
id: string;
|
|
400
416
|
config: EdgePassConfig;
|
|
401
|
-
spent: bigint;
|
|
417
|
+
spent: bigint;
|
|
402
418
|
active: boolean;
|
|
403
|
-
createdAt: number;
|
|
404
|
-
expiresAt: number;
|
|
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;
|
|
413
|
-
amount: bigint;
|
|
414
|
-
metadata?: Record<string, string>;
|
|
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:
|
|
432
|
-
requiresEscalation:
|
|
433
|
-
reason:
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
619
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
-
|
|
694
|
-
|
|
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
|
-
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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