@brainai/satp-client 2.0.0 → 2.0.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/README.md +529 -51
- package/examples/integration-patterns.js +495 -0
- package/examples/runtime-policy-adapter.js +75 -0
- package/examples/x402-discovery-evidence-lookup.js +66 -0
- package/package.json +42 -5
- package/src/attestation-request.js +149 -0
- package/src/borsh-reader.d.ts +213 -0
- package/src/borsh-reader.js +587 -0
- package/src/constants.js +10 -0
- package/src/index.d.ts +507 -0
- package/src/index.js +672 -0
- package/src/pda.js +43 -0
- package/src/runtime-policy-adapter.js +206 -0
- package/src/trust-packet.js +148 -0
- package/src/v3-pda.d.ts +72 -0
- package/src/v3-pda.js +281 -0
- package/src/v3-sdk.d.ts +508 -0
- package/src/v3-sdk.js +1800 -0
- package/src/wallet-control-challenge.js +351 -0
- package/src/x402-discovery.js +180 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SATP V3 SDK - Integration Patterns & App-Agnostic Examples
|
|
5
|
+
*
|
|
6
|
+
* Practical patterns for integrating SATP V3 into applications.
|
|
7
|
+
* Each example is self-contained and can be run against devnet.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node examples/integration-patterns.js [example-name]
|
|
11
|
+
*
|
|
12
|
+
* Examples: lookup, register, review-flow, reputation-flow,
|
|
13
|
+
* attestation-flow, migration, multi-wallet, batch-lookup
|
|
14
|
+
*
|
|
15
|
+
* @author brainChain — brainAI
|
|
16
|
+
* @version 3.0.0
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { Connection, PublicKey, Keypair, sendAndConfirmTransaction } = require('@solana/web3.js');
|
|
20
|
+
const {
|
|
21
|
+
SATPV3SDK,
|
|
22
|
+
hashAgentId,
|
|
23
|
+
getGenesisPDA,
|
|
24
|
+
getV3ReviewPDA,
|
|
25
|
+
getV3AttestationPDA,
|
|
26
|
+
getNameRegistryPDA,
|
|
27
|
+
getLinkedWalletPDA,
|
|
28
|
+
} = require('../src');
|
|
29
|
+
|
|
30
|
+
// ═══════════════════════════════════════════════════════
|
|
31
|
+
// PATTERN 1: Agent Identity Lookup (Read-Only)
|
|
32
|
+
// Use case: downstream profile pages and app-owned listings
|
|
33
|
+
// ═══════════════════════════════════════════════════════
|
|
34
|
+
|
|
35
|
+
async function lookupAgent(agentId = 'brainChain') {
|
|
36
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
37
|
+
|
|
38
|
+
// Quick existence check (1 RPC call)
|
|
39
|
+
const exists = await sdk.hasIdentity(agentId);
|
|
40
|
+
if (!exists) {
|
|
41
|
+
console.log(`Agent "${agentId}" not found on-chain.`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Full profile read (1 RPC call)
|
|
46
|
+
const record = await sdk.getGenesisRecord(agentId);
|
|
47
|
+
|
|
48
|
+
console.log('=== Agent Profile ===');
|
|
49
|
+
console.log(`Name: ${record.agentName}`);
|
|
50
|
+
console.log(`Category: ${record.category}`);
|
|
51
|
+
console.log(`Description: ${record.description}`);
|
|
52
|
+
console.log(`Capabilities: ${record.capabilities.join(', ')}`);
|
|
53
|
+
console.log(`Active: ${record.isActive}`);
|
|
54
|
+
console.log(`Born: ${record.isBorn}`);
|
|
55
|
+
console.log(`Rep Score: ${record.reputationScore} (${(record.reputationScore / 10000).toFixed(2)}%)`);
|
|
56
|
+
console.log(`Verify Level: L${record.verificationLevel}`);
|
|
57
|
+
console.log(`Authority: ${record.authority}`);
|
|
58
|
+
console.log(`PDA: ${record.pda}`);
|
|
59
|
+
console.log(`Created: ${new Date(record.createdAt * 1000).toISOString()}`);
|
|
60
|
+
|
|
61
|
+
if (record.faceMint) {
|
|
62
|
+
console.log(`Face Mint: ${record.faceMint}`);
|
|
63
|
+
console.log(`Face Image: ${record.faceImage}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return record;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ═══════════════════════════════════════════════════════
|
|
70
|
+
// PATTERN 2: Agent Registration (Full Flow)
|
|
71
|
+
// Use case: New agent onboarding, self-registration
|
|
72
|
+
// ═══════════════════════════════════════════════════════
|
|
73
|
+
|
|
74
|
+
async function registerAgent(keypairPath) {
|
|
75
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
76
|
+
|
|
77
|
+
// In production, load from wallet adapter or server keypair
|
|
78
|
+
// const wallet = Keypair.fromSecretKey(Buffer.from(JSON.parse(fs.readFileSync(keypairPath))));
|
|
79
|
+
// For demo, generate ephemeral
|
|
80
|
+
const wallet = Keypair.generate();
|
|
81
|
+
const agentId = `agent-${Date.now()}`;
|
|
82
|
+
|
|
83
|
+
console.log('=== Step 1: Create Identity ===');
|
|
84
|
+
const { transaction: createTx, genesisPDA } = await sdk.buildCreateIdentity(
|
|
85
|
+
wallet.publicKey,
|
|
86
|
+
agentId,
|
|
87
|
+
{
|
|
88
|
+
name: 'My Agent',
|
|
89
|
+
description: 'An AI agent specialized in data analysis',
|
|
90
|
+
category: 'analytics',
|
|
91
|
+
capabilities: ['data-analysis', 'reporting', 'visualization'],
|
|
92
|
+
metadataUri: 'https://arweave.net/agent-metadata.json',
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
console.log(`Genesis PDA: ${genesisPDA.toBase58()}`);
|
|
96
|
+
// await sendAndConfirmTransaction(sdk.connection, createTx, [wallet]);
|
|
97
|
+
|
|
98
|
+
console.log('\n=== Step 2: Register Unique Name ===');
|
|
99
|
+
const { transaction: nameTx, nameRegistryPDA } = await sdk.buildRegisterName(
|
|
100
|
+
wallet.publicKey,
|
|
101
|
+
agentId,
|
|
102
|
+
'My Agent'
|
|
103
|
+
);
|
|
104
|
+
console.log(`Name Registry PDA: ${nameRegistryPDA.toBase58()}`);
|
|
105
|
+
// await sendAndConfirmTransaction(sdk.connection, nameTx, [wallet]);
|
|
106
|
+
|
|
107
|
+
console.log('\n=== Step 3: Link Wallets ===');
|
|
108
|
+
const hotWallet = Keypair.generate().publicKey;
|
|
109
|
+
const { transaction: linkTx, linkedWalletPDA } = await sdk.buildLinkWallet(
|
|
110
|
+
wallet.publicKey,
|
|
111
|
+
agentId,
|
|
112
|
+
hotWallet,
|
|
113
|
+
'solana', // chain
|
|
114
|
+
'hot-wallet' // label
|
|
115
|
+
);
|
|
116
|
+
console.log(`Linked Wallet PDA: ${linkedWalletPDA.toBase58()}`);
|
|
117
|
+
// await sendAndConfirmTransaction(sdk.connection, linkTx, [wallet]);
|
|
118
|
+
|
|
119
|
+
console.log('\n=== Step 4: Init Mint Tracker ===');
|
|
120
|
+
const { transaction: trackerTx, mintTrackerPDA } = await sdk.buildInitMintTracker(
|
|
121
|
+
wallet.publicKey,
|
|
122
|
+
agentId
|
|
123
|
+
);
|
|
124
|
+
console.log(`Mint Tracker PDA: ${mintTrackerPDA.toBase58()}`);
|
|
125
|
+
// await sendAndConfirmTransaction(sdk.connection, trackerTx, [wallet]);
|
|
126
|
+
|
|
127
|
+
console.log('\n✅ Full registration flow built (4 transactions)');
|
|
128
|
+
console.log('Uncomment sendAndConfirmTransaction calls to execute on-chain.');
|
|
129
|
+
|
|
130
|
+
return { agentId, genesisPDA };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ═══════════════════════════════════════════════════════
|
|
134
|
+
// PATTERN 3: Review + Reputation Flow
|
|
135
|
+
// Use case: post-engagement review and consumer-owned feedback
|
|
136
|
+
// ═══════════════════════════════════════════════════════
|
|
137
|
+
|
|
138
|
+
async function reviewAndReputationFlow() {
|
|
139
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
140
|
+
|
|
141
|
+
const reviewer = Keypair.generate();
|
|
142
|
+
const agentId = 'brainChain';
|
|
143
|
+
const jobPDA = Keypair.generate().publicKey; // In practice, derive from escrow
|
|
144
|
+
|
|
145
|
+
console.log('=== Review + Reputation Flow ===');
|
|
146
|
+
console.log('This demonstrates the full review → reputation recompute pipeline.\n');
|
|
147
|
+
|
|
148
|
+
// Step 1: Submit a review (requires Reviews program)
|
|
149
|
+
// Note: Review submission uses the Reviews V3 program directly.
|
|
150
|
+
// The SDK provides PDA derivation for reviews:
|
|
151
|
+
const [reviewPDA] = getV3ReviewPDA(jobPDA, reviewer.publicKey, 'devnet');
|
|
152
|
+
console.log(`Review PDA (derived): ${reviewPDA.toBase58()}`);
|
|
153
|
+
console.log('→ Reviews are submitted via the Reviews program (5-star scale).\n');
|
|
154
|
+
|
|
155
|
+
// Step 2: Gather all review accounts for the agent
|
|
156
|
+
// In a real application, you'd query reviews using getProgramAccounts:
|
|
157
|
+
console.log('Querying existing reviews...');
|
|
158
|
+
const connection = new Connection('https://api.devnet.solana.com');
|
|
159
|
+
// Filter reviews by agent — in practice, filter by account data
|
|
160
|
+
// const reviews = await connection.getProgramAccounts(REVIEWS_PROGRAM_ID, { filters: [...] });
|
|
161
|
+
|
|
162
|
+
// Step 3: Trigger reputation recompute (permissionless — anyone can call)
|
|
163
|
+
const caller = Keypair.generate();
|
|
164
|
+
const mockReviewAccounts = [reviewPDA]; // Would be real review PDAs
|
|
165
|
+
|
|
166
|
+
const { transaction: repTx } = await sdk.buildRecomputeReputation(
|
|
167
|
+
caller.publicKey,
|
|
168
|
+
agentId,
|
|
169
|
+
mockReviewAccounts
|
|
170
|
+
);
|
|
171
|
+
console.log('Reputation recompute transaction built.');
|
|
172
|
+
console.log(`Includes ${mockReviewAccounts.length} review account(s) as remaining_accounts.`);
|
|
173
|
+
console.log('→ Score = time-decay weighted average × 200,000 (5★ = 1,000,000)');
|
|
174
|
+
console.log('→ Base score (no reviews) = 500,000\n');
|
|
175
|
+
|
|
176
|
+
// Step 4: Read updated reputation
|
|
177
|
+
// After submitting the recompute tx, read the updated Genesis Record:
|
|
178
|
+
// const updated = await sdk.getGenesisRecord(agentId);
|
|
179
|
+
// console.log(`New reputation score: ${updated.reputationScore}`);
|
|
180
|
+
|
|
181
|
+
console.log('✅ Review → Reputation pipeline demonstrated');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ═══════════════════════════════════════════════════════
|
|
185
|
+
// PATTERN 4: Attestation + Validation Flow
|
|
186
|
+
// Use case: Identity verification, skill certification
|
|
187
|
+
// ═══════════════════════════════════════════════════════
|
|
188
|
+
|
|
189
|
+
async function attestationAndValidationFlow() {
|
|
190
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
191
|
+
|
|
192
|
+
const issuer = Keypair.generate(); // Verification authority
|
|
193
|
+
const agentId = 'brainChain';
|
|
194
|
+
|
|
195
|
+
console.log('=== Attestation + Validation Flow ===');
|
|
196
|
+
console.log('Demonstrates: create → verify → recompute level.\n');
|
|
197
|
+
|
|
198
|
+
// Step 1: Create attestation
|
|
199
|
+
const attestationType = 'kyc-identity';
|
|
200
|
+
const proofData = JSON.stringify({
|
|
201
|
+
method: 'document-verification',
|
|
202
|
+
provider: 'brainAI-verify',
|
|
203
|
+
timestamp: Date.now(),
|
|
204
|
+
confidence: 0.98,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const { transaction: attTx, attestationPDA } = await sdk.buildCreateAttestation(
|
|
208
|
+
issuer.publicKey,
|
|
209
|
+
agentId,
|
|
210
|
+
attestationType,
|
|
211
|
+
proofData,
|
|
212
|
+
null // No expiry (permanent)
|
|
213
|
+
);
|
|
214
|
+
console.log(`Attestation PDA: ${attestationPDA.toBase58()}`);
|
|
215
|
+
console.log(`Type: ${attestationType}`);
|
|
216
|
+
// await sendAndConfirmTransaction(sdk.connection, attTx, [issuer]);
|
|
217
|
+
|
|
218
|
+
// Step 2: Verify the attestation (issuer confirms)
|
|
219
|
+
const { transaction: verifyTx } = await sdk.buildVerifyAttestation(
|
|
220
|
+
issuer.publicKey,
|
|
221
|
+
attestationPDA
|
|
222
|
+
);
|
|
223
|
+
console.log('Verification transaction built.');
|
|
224
|
+
// await sendAndConfirmTransaction(sdk.connection, verifyTx, [issuer]);
|
|
225
|
+
|
|
226
|
+
// Step 3: Recompute validation level (permissionless)
|
|
227
|
+
const caller = Keypair.generate();
|
|
228
|
+
const { transaction: valTx } = await sdk.buildRecomputeLevel(
|
|
229
|
+
caller.publicKey,
|
|
230
|
+
agentId,
|
|
231
|
+
[attestationPDA]
|
|
232
|
+
);
|
|
233
|
+
console.log('Validation level recompute transaction built.\n');
|
|
234
|
+
// await sendAndConfirmTransaction(sdk.connection, valTx, [caller]);
|
|
235
|
+
|
|
236
|
+
// Validation Level Map:
|
|
237
|
+
console.log('Verification Levels:');
|
|
238
|
+
console.log(' L0 = Unverified (0 unique types)');
|
|
239
|
+
console.log(' L1 = Basic (1 unique type)');
|
|
240
|
+
console.log(' L2 = Verified (2 unique types)');
|
|
241
|
+
console.log(' L3 = Trusted (3 unique types)');
|
|
242
|
+
console.log(' L4 = Certified (4 unique types)');
|
|
243
|
+
console.log(' L5 = Sovereign (5+ unique types)\n');
|
|
244
|
+
|
|
245
|
+
// Attestation types that count toward level:
|
|
246
|
+
console.log('Example attestation types:');
|
|
247
|
+
console.log(' - kyc-identity: Government ID verification');
|
|
248
|
+
console.log(' - code-audit: Code security audit passed');
|
|
249
|
+
console.log(' - performance: Performance benchmark attestation');
|
|
250
|
+
console.log(' - domain-expert: Domain expertise certification');
|
|
251
|
+
console.log(' - community: Community vouching/endorsement');
|
|
252
|
+
|
|
253
|
+
console.log('\n✅ Attestation → Validation pipeline demonstrated');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ═══════════════════════════════════════════════════════
|
|
257
|
+
// PATTERN 5: V2 → V3 Migration
|
|
258
|
+
// Use case: Existing agents upgrading to Genesis Records
|
|
259
|
+
// ═══════════════════════════════════════════════════════
|
|
260
|
+
|
|
261
|
+
async function migrationFlow() {
|
|
262
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
263
|
+
const v2Authority = Keypair.generate();
|
|
264
|
+
const agentId = 'legacy-agent';
|
|
265
|
+
|
|
266
|
+
console.log('=== V2 → V3 Migration ===');
|
|
267
|
+
console.log('Migrates existing V2 identity to V3 Genesis Record.\n');
|
|
268
|
+
|
|
269
|
+
// The migration instruction:
|
|
270
|
+
// 1. Verifies the caller is the V2 authority
|
|
271
|
+
// 2. Creates a new V3 Genesis Record with provided metadata
|
|
272
|
+
// 3. Sets the V2 authority as the V3 authority
|
|
273
|
+
// 4. Does NOT modify the V2 account (non-destructive)
|
|
274
|
+
|
|
275
|
+
const { transaction: migrateTx, genesisPDA } = await sdk.buildMigrateV2ToV3(
|
|
276
|
+
v2Authority.publicKey,
|
|
277
|
+
agentId,
|
|
278
|
+
{
|
|
279
|
+
name: 'Legacy Agent (V3)',
|
|
280
|
+
description: 'Migrated from V2 identity',
|
|
281
|
+
category: 'general',
|
|
282
|
+
capabilities: ['chat', 'search'],
|
|
283
|
+
metadataUri: '',
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
console.log(`New V3 Genesis PDA: ${genesisPDA.toBase58()}`);
|
|
288
|
+
console.log('Migration preserves:');
|
|
289
|
+
console.log(' ✓ Authority (same signer)');
|
|
290
|
+
console.log(' ✓ Agent ID hash (deterministic)');
|
|
291
|
+
console.log(' ✓ V2 account (untouched)');
|
|
292
|
+
console.log('Migration adds:');
|
|
293
|
+
console.log(' + Genesis Record with face fields');
|
|
294
|
+
console.log(' + Name Registry support');
|
|
295
|
+
console.log(' + Multi-wallet linking');
|
|
296
|
+
console.log(' + CPI-based reputation/validation');
|
|
297
|
+
console.log(' + Mint tracking (cap: 3)');
|
|
298
|
+
|
|
299
|
+
console.log('\n✅ Migration flow demonstrated');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ═══════════════════════════════════════════════════════
|
|
303
|
+
// PATTERN 6: Batch Agent Lookup (Read-Only)
|
|
304
|
+
// Use case: Marketplace search results, leaderboard
|
|
305
|
+
// ═══════════════════════════════════════════════════════
|
|
306
|
+
|
|
307
|
+
async function batchLookup(agentIds = ['brainChain', 'brainForge', 'brainGrowth']) {
|
|
308
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
309
|
+
|
|
310
|
+
console.log('=== Batch Agent Lookup ===');
|
|
311
|
+
console.log(`Looking up ${agentIds.length} agents...\n`);
|
|
312
|
+
|
|
313
|
+
// Method 1: Sequential (simple, rate-limit safe)
|
|
314
|
+
const results = [];
|
|
315
|
+
for (const id of agentIds) {
|
|
316
|
+
const record = await sdk.getGenesisRecord(id);
|
|
317
|
+
results.push({ agentId: id, record });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Method 2: Parallel with getMultipleAccountsInfo (more efficient)
|
|
321
|
+
// Derive all PDAs first (no RPC needed), then batch fetch:
|
|
322
|
+
const pdas = agentIds.map(id => getGenesisPDA(id, 'devnet')[0]);
|
|
323
|
+
const accounts = await sdk.connection.getMultipleAccountsInfo(pdas);
|
|
324
|
+
|
|
325
|
+
console.log('Results:');
|
|
326
|
+
for (let i = 0; i < agentIds.length; i++) {
|
|
327
|
+
const record = results[i].record;
|
|
328
|
+
if (record && !record.error) {
|
|
329
|
+
console.log(` ${record.agentName}: Rep=${record.reputationScore}, L${record.verificationLevel}, Active=${record.isActive}`);
|
|
330
|
+
} else {
|
|
331
|
+
console.log(` ${agentIds[i]}: not found`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Sorting example (for leaderboard):
|
|
336
|
+
const sorted = results
|
|
337
|
+
.filter(r => r.record && !r.record.error)
|
|
338
|
+
.sort((a, b) => b.record.reputationScore - a.record.reputationScore);
|
|
339
|
+
|
|
340
|
+
console.log('\nLeaderboard (by reputation):');
|
|
341
|
+
sorted.forEach((r, i) => {
|
|
342
|
+
console.log(` ${i + 1}. ${r.record.agentName} — ${r.record.reputationScore}`);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
console.log('\n✅ Batch lookup demonstrated');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ═══════════════════════════════════════════════════════
|
|
349
|
+
// PATTERN 7: Authority Rotation (2-Step)
|
|
350
|
+
// Use case: Key rotation, team handoff, security recovery
|
|
351
|
+
// ═══════════════════════════════════════════════════════
|
|
352
|
+
|
|
353
|
+
async function authorityRotation() {
|
|
354
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
355
|
+
|
|
356
|
+
const currentAuth = Keypair.generate();
|
|
357
|
+
const newAuth = Keypair.generate();
|
|
358
|
+
const agentId = 'my-agent';
|
|
359
|
+
|
|
360
|
+
console.log('=== 2-Step Authority Rotation ===');
|
|
361
|
+
console.log('Secure key rotation with propose → accept pattern.\n');
|
|
362
|
+
|
|
363
|
+
// Step 1: Current authority proposes new authority
|
|
364
|
+
const { transaction: proposeTx } = await sdk.buildProposeAuthority(
|
|
365
|
+
currentAuth.publicKey,
|
|
366
|
+
agentId,
|
|
367
|
+
newAuth.publicKey
|
|
368
|
+
);
|
|
369
|
+
console.log(`Step 1: Propose ${newAuth.publicKey.toBase58().slice(0, 8)}... as new authority`);
|
|
370
|
+
// await sendAndConfirmTransaction(sdk.connection, proposeTx, [currentAuth]);
|
|
371
|
+
|
|
372
|
+
// Step 2: New authority accepts
|
|
373
|
+
const { transaction: acceptTx } = await sdk.buildAcceptAuthority(
|
|
374
|
+
newAuth.publicKey,
|
|
375
|
+
agentId
|
|
376
|
+
);
|
|
377
|
+
console.log(`Step 2: New authority accepts control`);
|
|
378
|
+
// await sendAndConfirmTransaction(sdk.connection, acceptTx, [newAuth]);
|
|
379
|
+
|
|
380
|
+
// Cancel option (current auth can cancel before acceptance):
|
|
381
|
+
const { transaction: cancelTx } = await sdk.buildCancelAuthorityTransfer(
|
|
382
|
+
currentAuth.publicKey,
|
|
383
|
+
agentId
|
|
384
|
+
);
|
|
385
|
+
console.log(`(Optional: Cancel transfer before acceptance)\n`);
|
|
386
|
+
|
|
387
|
+
console.log('Security properties:');
|
|
388
|
+
console.log(' ✓ Current authority must initiate (prevents unauthorized rotation)');
|
|
389
|
+
console.log(' ✓ New authority must confirm (prevents accidental rotation)');
|
|
390
|
+
console.log(' ✓ Current auth can cancel anytime before acceptance');
|
|
391
|
+
console.log(' ✓ After acceptance, old authority has NO access');
|
|
392
|
+
|
|
393
|
+
console.log('\n✅ Authority rotation demonstrated');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ═══════════════════════════════════════════════════════
|
|
397
|
+
// PATTERN 8: PDA Derivation (Offline / No RPC)
|
|
398
|
+
// Use case: Pre-computing addresses, caching, indexers
|
|
399
|
+
// ═══════════════════════════════════════════════════════
|
|
400
|
+
|
|
401
|
+
function offlinePDADerivation() {
|
|
402
|
+
console.log('=== Offline PDA Derivation ===');
|
|
403
|
+
console.log('All PDAs can be derived without any RPC calls.\n');
|
|
404
|
+
|
|
405
|
+
const agentId = 'brainChain';
|
|
406
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
407
|
+
|
|
408
|
+
// All at once
|
|
409
|
+
const pdas = sdk.getV3PDAs(agentId);
|
|
410
|
+
console.log('Agent PDAs:');
|
|
411
|
+
console.log(` Genesis: ${pdas.genesis}`);
|
|
412
|
+
console.log(` Mint Tracker: ${pdas.mintTracker}`);
|
|
413
|
+
console.log(` Rep Authority: ${pdas.reputationAuthority}`);
|
|
414
|
+
console.log(` Val Authority: ${pdas.validationAuthority}`);
|
|
415
|
+
console.log(` Agent ID Hash: ${pdas.agentIdHash.slice(0, 16)}...`);
|
|
416
|
+
|
|
417
|
+
// Individual derivations
|
|
418
|
+
const wallet = Keypair.generate().publicKey;
|
|
419
|
+
const [genesisPDA] = getGenesisPDA(agentId, 'devnet');
|
|
420
|
+
const [linkedPDA] = getLinkedWalletPDA(genesisPDA, wallet, 'devnet');
|
|
421
|
+
const [namePDA] = getNameRegistryPDA('brainChain', 'devnet');
|
|
422
|
+
console.log(`\n Name Registry: ${namePDA.toBase58()}`);
|
|
423
|
+
console.log(` Linked Wallet: ${linkedPDA.toBase58()}`);
|
|
424
|
+
|
|
425
|
+
// Hash functions (deterministic, no RPC)
|
|
426
|
+
const hash1 = hashAgentId('brainChain');
|
|
427
|
+
const hash2 = hashAgentId('brainChain');
|
|
428
|
+
console.log(`\n Hash stable: ${hash1.equals(hash2) ? 'YES ✓' : 'NO ✗'}`);
|
|
429
|
+
console.log(` Hash hex: ${hash1.toString('hex').slice(0, 32)}...`);
|
|
430
|
+
|
|
431
|
+
console.log('\n✅ All derivations are deterministic and offline');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ═══════════════════════════════════════════════════════
|
|
435
|
+
// PATTERN 9: Name Availability Check
|
|
436
|
+
// Use case: Registration form, name suggestions
|
|
437
|
+
// ═══════════════════════════════════════════════════════
|
|
438
|
+
|
|
439
|
+
async function nameAvailability() {
|
|
440
|
+
const sdk = new SATPV3SDK({ network: 'devnet' });
|
|
441
|
+
|
|
442
|
+
console.log('=== Name Availability Check ===\n');
|
|
443
|
+
|
|
444
|
+
const names = ['brainChain', 'available-name-12345', 'BrainChain']; // Note: case-insensitive
|
|
445
|
+
|
|
446
|
+
for (const name of names) {
|
|
447
|
+
const available = await sdk.isNameAvailable(name);
|
|
448
|
+
const [pda] = getNameRegistryPDA(name, 'devnet');
|
|
449
|
+
console.log(` "${name}" → ${available ? '✅ Available' : '❌ Taken'} (PDA: ${pda.toBase58().slice(0, 12)}...)`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log('\nNote: Names are case-insensitive.');
|
|
453
|
+
console.log('"brainChain" and "BrainChain" hash to the same PDA.');
|
|
454
|
+
|
|
455
|
+
console.log('\n✅ Name check demonstrated');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ═══════════════════════════════════════════════════════
|
|
459
|
+
// CLI Runner
|
|
460
|
+
// ═══════════════════════════════════════════════════════
|
|
461
|
+
|
|
462
|
+
const examples = {
|
|
463
|
+
'lookup': () => lookupAgent(process.argv[3] || 'brainChain'),
|
|
464
|
+
'register': () => registerAgent(),
|
|
465
|
+
'review-flow': () => reviewAndReputationFlow(),
|
|
466
|
+
'reputation-flow': () => reviewAndReputationFlow(),
|
|
467
|
+
'attestation-flow': () => attestationAndValidationFlow(),
|
|
468
|
+
'migration': () => migrationFlow(),
|
|
469
|
+
'batch-lookup': () => batchLookup(),
|
|
470
|
+
'authority-rotation': () => authorityRotation(),
|
|
471
|
+
'pda-derivation': () => offlinePDADerivation(),
|
|
472
|
+
'name-check': () => nameAvailability(),
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
async function main() {
|
|
476
|
+
const example = process.argv[2];
|
|
477
|
+
|
|
478
|
+
if (!example || !examples[example]) {
|
|
479
|
+
console.log('SATP V3 SDK — Integration Patterns\n');
|
|
480
|
+
console.log('Usage: node integration-patterns.js <example>\n');
|
|
481
|
+
console.log('Available examples:');
|
|
482
|
+
Object.keys(examples).forEach(name => console.log(` ${name}`));
|
|
483
|
+
console.log('\nExample: node integration-patterns.js lookup brainChain');
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
await examples[example]();
|
|
489
|
+
} catch (err) {
|
|
490
|
+
console.error(`Error: ${err.message}`);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
main();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { evaluateRuntimePolicy } = require('..');
|
|
5
|
+
|
|
6
|
+
const trustedIdentity = {
|
|
7
|
+
agentId: 'brainchain-demo',
|
|
8
|
+
active: true,
|
|
9
|
+
satpVerified: true,
|
|
10
|
+
trustScore: 88,
|
|
11
|
+
capabilities: ['mcp:deploy-readiness', 'satp:trust-read'],
|
|
12
|
+
evidenceUpdatedAt: '2026-05-21T00:00:00Z',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const examples = [
|
|
16
|
+
{
|
|
17
|
+
name: 'MCP protected tool',
|
|
18
|
+
identity: trustedIdentity,
|
|
19
|
+
action: {
|
|
20
|
+
type: 'mcp_protected_tool',
|
|
21
|
+
resource: 'mcp://protected/deploy-readiness',
|
|
22
|
+
operation: 'read',
|
|
23
|
+
requiresCapability: 'mcp:deploy-readiness',
|
|
24
|
+
requiresFreshEvidence: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'x402 paid endpoint',
|
|
29
|
+
identity: trustedIdentity,
|
|
30
|
+
action: {
|
|
31
|
+
type: 'x402_endpoint',
|
|
32
|
+
resource: 'https://api.example.test/reputation',
|
|
33
|
+
operation: 'lookup',
|
|
34
|
+
costUsd: 0.05,
|
|
35
|
+
},
|
|
36
|
+
options: {
|
|
37
|
+
actionPaymentPreapproved: true,
|
|
38
|
+
policy: { maxAutoSpendUsd: 0.01 },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Host trust-score degrade',
|
|
43
|
+
identity: { ...trustedIdentity, trustScore: 62 },
|
|
44
|
+
action: {
|
|
45
|
+
type: 'host_trust_gate',
|
|
46
|
+
resource: 'satp://trust-score',
|
|
47
|
+
operation: 'gate',
|
|
48
|
+
minimumTrustScore: 80,
|
|
49
|
+
allowDegraded: true,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Optional paid x402 evidence lookup',
|
|
54
|
+
identity: { ...trustedIdentity, evidenceUpdatedAt: '2026-04-01T00:00:00Z' },
|
|
55
|
+
action: {
|
|
56
|
+
type: 'mcp_protected_tool',
|
|
57
|
+
resource: 'mcp://protected/high-risk-action',
|
|
58
|
+
operation: 'prepare',
|
|
59
|
+
requiresFreshEvidence: true,
|
|
60
|
+
evidenceLookup: {
|
|
61
|
+
type: 'x402',
|
|
62
|
+
endpoint: 'https://api.example.test/evidence',
|
|
63
|
+
maxCostUsd: 0.05,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
options: {
|
|
67
|
+
now: '2026-05-21T00:00:00Z',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const item of examples) {
|
|
73
|
+
const result = evaluateRuntimePolicy(item.identity, item.action, item.options);
|
|
74
|
+
console.log(JSON.stringify({ name: item.name, ...result }, null, 2));
|
|
75
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const {
|
|
6
|
+
X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION,
|
|
7
|
+
parseX402DiscoveryMetadata,
|
|
8
|
+
buildX402EvidenceLookup,
|
|
9
|
+
buildRuntimePolicyActionDescriptorFromX402Discovery,
|
|
10
|
+
} = require('../src');
|
|
11
|
+
|
|
12
|
+
const discoveryMetadata = {
|
|
13
|
+
action: 'satp.resolveIdentity',
|
|
14
|
+
endpoint: 'https://example.invalid/.well-known/x402/satp',
|
|
15
|
+
accepts: [
|
|
16
|
+
{
|
|
17
|
+
scheme: 'exact',
|
|
18
|
+
network: 'base',
|
|
19
|
+
asset: 'USDC',
|
|
20
|
+
amountRequired: '1',
|
|
21
|
+
resource: 'satp://identity/brainChain',
|
|
22
|
+
description: 'Read-only SATP evidence lookup',
|
|
23
|
+
mimeType: 'application/json',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const discovery = parseX402DiscoveryMetadata(discoveryMetadata);
|
|
29
|
+
const evidenceLookup = buildX402EvidenceLookup(discoveryMetadata, {
|
|
30
|
+
sourceKind: 'well-known-x402',
|
|
31
|
+
maxCostUsd: 0.01,
|
|
32
|
+
});
|
|
33
|
+
const descriptor = buildRuntimePolicyActionDescriptorFromX402Discovery(discoveryMetadata, {
|
|
34
|
+
sourceKind: 'well-known-x402',
|
|
35
|
+
maxCostUsd: 0.01,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
assert.equal(discovery.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
|
|
39
|
+
assert.equal(evidenceLookup.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
|
|
40
|
+
assert.equal(evidenceLookup.paymentAuthorization, false);
|
|
41
|
+
assert.equal(evidenceLookup.actionAuthorization, false);
|
|
42
|
+
assert.equal(evidenceLookup.spendAuthorized, false);
|
|
43
|
+
assert.equal(evidenceLookup.livePaymentRequired, false);
|
|
44
|
+
assert.equal(descriptor.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
|
|
45
|
+
assert.equal(descriptor.paymentAuthorization, false);
|
|
46
|
+
assert.equal(descriptor.actionAuthorization, false);
|
|
47
|
+
assert.equal(descriptor.spendAuthorized, false);
|
|
48
|
+
assert.equal(descriptor.livePaymentRequired, false);
|
|
49
|
+
assert.deepEqual(descriptor.evidenceLookup, evidenceLookup);
|
|
50
|
+
|
|
51
|
+
console.log(JSON.stringify(
|
|
52
|
+
{
|
|
53
|
+
resource: discovery.resource,
|
|
54
|
+
endpoint: discovery.endpoint,
|
|
55
|
+
evidenceLookupType: evidenceLookup.type,
|
|
56
|
+
operation: descriptor.operation,
|
|
57
|
+
guardrail: descriptor.guardrail,
|
|
58
|
+
paymentAuthorization: descriptor.paymentAuthorization,
|
|
59
|
+
actionAuthorization: descriptor.actionAuthorization,
|
|
60
|
+
spendAuthorized: descriptor.spendAuthorized,
|
|
61
|
+
livePaymentRequired: descriptor.livePaymentRequired,
|
|
62
|
+
warning: 'x402 payment metadata is discovery/evidence lookup only; it is not action authorization.',
|
|
63
|
+
},
|
|
64
|
+
null,
|
|
65
|
+
2
|
|
66
|
+
));
|