@brainai/satp-client 2.0.1 → 2.0.3
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 +114 -20
- 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 +20 -4
- package/src/index.d.ts +241 -0
- package/src/index.js +30 -0
- package/src/runtime-policy-adapter.js +206 -0
- package/src/v3-pda.js +1 -1
- package/src/wallet-control-challenge.js +351 -0
- package/src/x402-discovery.js +180 -0
package/README.md
CHANGED
|
@@ -1,18 +1,53 @@
|
|
|
1
|
-
# SATP V3 SDK
|
|
1
|
+
# SATP V3 SDK - `@brainai/satp-client`
|
|
2
2
|
|
|
3
|
-
**Solana Agent Token Protocol**
|
|
3
|
+
**Solana Agent Token Protocol** - JavaScript/TypeScript SDK for interacting with the SATP V3 devnet programs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Current stable npm package: **@brainai/satp-client@2.0.1** | reviewed rc artifact: **@brainai/satp-client@2.0.2-rc.0** | Programs: **6**
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
+
Choose stable, rc, or Git based on what the consumer needs to prove:
|
|
10
|
+
|
|
11
|
+
| Channel | Use when | Command |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Stable npm | Default production-style consumption of the stable public package. | `npm install @brainai/satp-client@2.0.1` |
|
|
14
|
+
| Release candidate npm | Validating the reviewed rc package before promotion or producing reproducible rc manifests. | `npm install @brainai/satp-client@2.0.2-rc.0` |
|
|
15
|
+
| Release candidate tag | Quick rc opt-in where a moving dist-tag is acceptable. | `npm install @brainai/satp-client@rc` |
|
|
16
|
+
| Reviewed Git commit | PR coordination or source-review installs tied to an exact SATP commit. | `npm install git+https://github.com/brainAI-bot/satp.git#<SATP_COMMIT>` |
|
|
17
|
+
|
|
18
|
+
The npm `latest` tag still resolves to `@brainai/satp-client@2.0.1`.
|
|
19
|
+
Historical rc-tag readback may still show the older `0.1.0-rc.0` package until
|
|
20
|
+
the rc channel is promoted. The reviewed RC-S6 artifact in this source tree is
|
|
21
|
+
`@brainai/satp-client@2.0.2-rc.0`; downstream apps that need reproducible
|
|
22
|
+
manifests should pin that exact version after promotion instead of relying on
|
|
23
|
+
the moving `@rc` tag.
|
|
24
|
+
|
|
25
|
+
For stable consumer installs, pin the current published npm package:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @brainai/satp-client@2.0.1
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For exact rc validation:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @brainai/satp-client@2.0.2-rc.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For short-lived rc opt-in:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @brainai/satp-client@rc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For branch-only development or PR review, pin an explicit SATP Git commit:
|
|
44
|
+
|
|
9
45
|
```bash
|
|
10
46
|
npm install git+https://github.com/brainAI-bot/satp.git#<SATP_COMMIT>
|
|
11
47
|
```
|
|
12
48
|
|
|
13
|
-
The
|
|
14
|
-
|
|
15
|
-
until the release packet passes.
|
|
49
|
+
The old `0.0.0-extraction` label was extraction-branch metadata and is not the
|
|
50
|
+
current consumer package. Do not treat branch-only Git installs as npm latest.
|
|
16
51
|
|
|
17
52
|
Mainnet program IDs are intentionally not enabled in this release candidate.
|
|
18
53
|
Constructors and helpers fail closed for `network: 'mainnet'` until an approved
|
|
@@ -72,7 +107,7 @@ const tx = await sdk.buildCreateIdentity(creatorPubkey, 'myAgent', {
|
|
|
72
107
|
| `attestations_v3` | `6Xd1dAQJPvQRJ4Ntr6LtPTjDjPUZ8nfnmYLZaZ2DtrdD` | Third-party attestations & proofs |
|
|
73
108
|
| `reputation_v3` | `2Lz7KzMvKdrGeAuS8WPHu7jK2yScrnKVgacpYVEuDjkJ` | Weighted reputation scoring (CPI → identity) |
|
|
74
109
|
| `validation_v3` | `6rYRiCYidJYV7QvKrzKGgNu4oMh6BAvynked69R7xMbV` | Validation level computation (CPI → identity) |
|
|
75
|
-
| `escrow_v3` | `
|
|
110
|
+
| `escrow_v3` | `B1Se8SPx7GLUisa4LYeXY1tDZy5TviJrsV2yMLgqUXmg` | SOL escrow for agent jobs |
|
|
76
111
|
|
|
77
112
|
## API Reference
|
|
78
113
|
|
|
@@ -111,6 +146,69 @@ if (!validation.ok) throw new Error(validation.errors.join('; '));
|
|
|
111
146
|
re-derives the expected packet so tampered PDA, program, request, or hash fields
|
|
112
147
|
surface as explicit errors.
|
|
113
148
|
|
|
149
|
+
### x402 Discovery Evidence Lookup Helpers
|
|
150
|
+
|
|
151
|
+
`parseX402DiscoveryMetadata(input)`, `buildX402EvidenceLookup(input, opts)`,
|
|
152
|
+
and `buildRuntimePolicyActionDescriptorFromX402Discovery(input, opts)` map x402
|
|
153
|
+
discovery metadata into SATP runtime policy evidence lookup data. The helpers are
|
|
154
|
+
read-only: x402 payment metadata can identify where evidence may be fetched, but
|
|
155
|
+
it is discovery/evidence lookup only and never authorizes SATP action execution,
|
|
156
|
+
spending, live payment, signing, transactions, or host policy bypass.
|
|
157
|
+
|
|
158
|
+
Run the offline example:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
node packages/satp-client/examples/x402-discovery-evidence-lookup.js
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The example parses discovery metadata, builds an evidence lookup descriptor, and
|
|
165
|
+
builds a runtime policy action descriptor. It asserts
|
|
166
|
+
`X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION` plus
|
|
167
|
+
`paymentAuthorization: false`, `actionAuthorization: false`,
|
|
168
|
+
`spendAuthorized: false`, and `livePaymentRequired: false`.
|
|
169
|
+
|
|
170
|
+
### Wallet-Control Challenge Helpers
|
|
171
|
+
|
|
172
|
+
`buildWalletControlChallenge(opts)` creates a canonical, offline challenge that
|
|
173
|
+
binds an agent ID to a Solana wallet. It derives the SATP V3 Genesis PDA and
|
|
174
|
+
linked-wallet PDA from `agentId`, `wallet`, and `network`, includes a nonce and
|
|
175
|
+
expiry, and returns plain JSON. It does not connect to RPC, read keypairs,
|
|
176
|
+
create transactions, sign, send, deploy, or mutate chain state.
|
|
177
|
+
|
|
178
|
+
`canonicalWalletControlChallenge(challenge)` returns the exact UTF-8 message a
|
|
179
|
+
wallet signs. `verifyWalletControlChallengeSignature(opts)` verifies a 64-byte
|
|
180
|
+
Ed25519 signature against the challenge wallet and fails closed for mismatched
|
|
181
|
+
wallets, signatures, agent IDs, PDAs, domain, audience, expiry, and replayed
|
|
182
|
+
nonces supplied by your replay cache.
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const {
|
|
186
|
+
buildWalletControlChallenge,
|
|
187
|
+
canonicalWalletControlChallenge,
|
|
188
|
+
verifyWalletControlChallengeSignature,
|
|
189
|
+
} = require('@brainai/satp-client');
|
|
190
|
+
|
|
191
|
+
const challenge = buildWalletControlChallenge({
|
|
192
|
+
agentId: 'brainChain',
|
|
193
|
+
wallet: walletPublicKey,
|
|
194
|
+
audience: 'my-service',
|
|
195
|
+
nonce: crypto.randomBytes(16).toString('hex'),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Ask the wallet to sign this exact canonical string.
|
|
199
|
+
const message = canonicalWalletControlChallenge(challenge);
|
|
200
|
+
|
|
201
|
+
const verification = verifyWalletControlChallengeSignature({
|
|
202
|
+
challenge,
|
|
203
|
+
signature,
|
|
204
|
+
expectedWallet: walletPublicKey,
|
|
205
|
+
expectedAgentId: 'brainChain',
|
|
206
|
+
expectedAudience: 'my-service',
|
|
207
|
+
usedNonces: replayCache,
|
|
208
|
+
});
|
|
209
|
+
if (!verification.ok) throw new Error(verification.errors.join('; '));
|
|
210
|
+
```
|
|
211
|
+
|
|
114
212
|
### Constructor
|
|
115
213
|
|
|
116
214
|
```javascript
|
|
@@ -233,7 +331,9 @@ These use Cross-Program Invocation to update fields directly on the Genesis Reco
|
|
|
233
331
|
|
|
234
332
|
### Escrow Methods (10)
|
|
235
333
|
|
|
236
|
-
|
|
334
|
+
App-agnostic escrow builders for downstream applications that need unsigned
|
|
335
|
+
SATP escrow transactions. Product-specific marketplace records, fees, job
|
|
336
|
+
workflow, moderation, and display copy stay in the consuming application.
|
|
237
337
|
|
|
238
338
|
| Method | Description |
|
|
239
339
|
|--------|-------------|
|
|
@@ -261,13 +361,13 @@ Cancelled Disputed Closed (rent reclaimed)
|
|
|
261
361
|
```
|
|
262
362
|
|
|
263
363
|
```javascript
|
|
264
|
-
// Create an escrow (0.5 SOL for a
|
|
364
|
+
// Create an escrow (0.5 SOL for a generic work agreement)
|
|
265
365
|
const tx = await sdk.buildCreateEscrow(
|
|
266
366
|
clientPubkey,
|
|
267
367
|
agentWallet,
|
|
268
368
|
'brainChain',
|
|
269
369
|
0.5 * 1e9, // lamports
|
|
270
|
-
'
|
|
370
|
+
'Complete agreed work',
|
|
271
371
|
Math.floor(Date.now()/1000) + 86400 * 7, // 7 day deadline
|
|
272
372
|
0, // nonce (for multiple escrows with same description)
|
|
273
373
|
{ arbiter: arbiterPubkey }
|
|
@@ -328,17 +428,11 @@ const sig = await connection.sendRawTransaction(tx.serialize());
|
|
|
328
428
|
await connection.confirmTransaction(sig);
|
|
329
429
|
```
|
|
330
430
|
|
|
331
|
-
##
|
|
332
|
-
|
|
333
|
-
The SATP V3 API is available at `https://agentfolio.bot/api/v3/`:
|
|
431
|
+
## Consumer APIs
|
|
334
432
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
| `GET /api/v3/escrow/by-client/:wallet` | Escrows by client wallet |
|
|
339
|
-
| `GET /api/v3/escrow/by-agent/:wallet` | Escrows by agent wallet |
|
|
340
|
-
| `GET /api/v3/escrow/by-agent-id/:agentId` | Escrows by SATP agent_id |
|
|
341
|
-
| + 18 more | See OpenAPI spec in `docs/` |
|
|
433
|
+
SATP core does not define or host an HTTP API. Downstream applications may wrap
|
|
434
|
+
the SDK with their own read APIs, but those routes are consumer-owned adapters
|
|
435
|
+
and must not be treated as SATP protocol authority.
|
|
342
436
|
|
|
343
437
|
## Testing
|
|
344
438
|
|
|
@@ -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();
|