@brainai/satp-client 2.0.0
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 +82 -0
- package/package.json +34 -0
- package/src/constants.js +67 -0
- package/src/index.js +323 -0
- package/src/pda.js +148 -0
- package/src/schema.js +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SATP JavaScript SDK
|
|
2
|
+
|
|
3
|
+
Lightweight SDK for interacting with SATP (Solana Agent Token Protocol) programs on Solana.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd satp-sdk && npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const { SATPSDK } = require('./src');
|
|
15
|
+
|
|
16
|
+
const sdk = new SATPSDK(); // mainnet by default
|
|
17
|
+
// const sdk = new SATPSDK({ rpcUrl: 'https://api.devnet.solana.com' });
|
|
18
|
+
|
|
19
|
+
// Check if an agent is registered
|
|
20
|
+
const verified = await sdk.verifyAgent('SomeWalletPubkey...');
|
|
21
|
+
|
|
22
|
+
// Get identity data
|
|
23
|
+
const identity = await sdk.getIdentity('SomeWalletPubkey...');
|
|
24
|
+
|
|
25
|
+
// Get reputation
|
|
26
|
+
const rep = await sdk.getReputation('SomeWalletPubkey...');
|
|
27
|
+
|
|
28
|
+
// Derive PDAs (offline, no RPC)
|
|
29
|
+
const pdas = sdk.getPDAs('SomeWalletPubkey...');
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Write Operations (require a signer)
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const { Keypair } = require('@solana/web3.js');
|
|
36
|
+
|
|
37
|
+
const signer = Keypair.fromSecretKey(/* your key */);
|
|
38
|
+
|
|
39
|
+
// Register identity
|
|
40
|
+
const sig = await sdk.registerIdentity(signer, 'my-agent', { type: 'ai', version: '1.0' });
|
|
41
|
+
|
|
42
|
+
// Add reputation (endorser signs)
|
|
43
|
+
const sig2 = await sdk.addReputation(endorserKeypair, targetWallet, 100);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Transaction Builders (for wallet adapters / frontends)
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// Get unsigned transaction for frontend signing
|
|
50
|
+
const { transaction, identityPDA } = await sdk.buildRegisterIdentity(
|
|
51
|
+
walletPublicKey, 'agent-name', { metadata: true }
|
|
52
|
+
);
|
|
53
|
+
// Sign with wallet adapter, then send
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
| Method | Type | Description |
|
|
59
|
+
|--------|------|-------------|
|
|
60
|
+
| `getIdentity(wallet)` | Read | Fetch identity data (or null) |
|
|
61
|
+
| `getReputation(wallet)` | Read | Fetch reputation data (or null) |
|
|
62
|
+
| `verifyAgent(wallet)` | Read | Check if wallet has SATP identity |
|
|
63
|
+
| `getPDAs(wallet)` | Offline | Derive identity + reputation PDAs |
|
|
64
|
+
| `registerIdentity(signer, name, metadata)` | Write | Register on-chain identity |
|
|
65
|
+
| `addReputation(endorser, target, score)` | Write | Add reputation score |
|
|
66
|
+
| `buildRegisterIdentity(wallet, name, meta)` | Builder | Unsigned tx for frontends |
|
|
67
|
+
| `buildAddReputation(wallet, score, endorser)` | Builder | Unsigned tx for frontends |
|
|
68
|
+
|
|
69
|
+
## Program IDs (Mainnet)
|
|
70
|
+
|
|
71
|
+
| Program | Address |
|
|
72
|
+
|---------|---------|
|
|
73
|
+
| Identity | `BY4jzmnrui1K5gZ5z5xRQkVfEEMXYHYugtH1Ua867eyr` |
|
|
74
|
+
| Reputation | `TQ4P9R2Y5FRyw1TZfwoWQ2Mf6XeohbGdhYNcDxh6YYh` |
|
|
75
|
+
| Validation | `AdDWFa9oEmZdrTrhu8YTWu4ozbTP7e6qa9rvyqfAvM7N` |
|
|
76
|
+
| Escrow | `STyY8w4ZHws3X1AMoocWuDYBoogVDwvymPy8Wifx5TH` |
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
|
|
80
|
+
- Account schemas are best-guess based on Anchor conventions. Once we have the actual IDLs, we can generate exact schemas.
|
|
81
|
+
- Write operations require SOL for transaction fees + rent.
|
|
82
|
+
- Always test on devnet first: `new SATPSDK({ rpcUrl: 'https://api.devnet.solana.com' })`
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brainai/satp-client",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "SATP v2 Client SDK — Solana Agent Token Protocol (Identity, Reviews, Reputation, Attestations, Validation)",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"solana",
|
|
15
|
+
"satp",
|
|
16
|
+
"agent",
|
|
17
|
+
"identity",
|
|
18
|
+
"reputation",
|
|
19
|
+
"blockchain",
|
|
20
|
+
"web3"
|
|
21
|
+
],
|
|
22
|
+
"author": "brainAI",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/brainai-dev/satp-client"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://agentfolio.bot",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@solana/web3.js": "^1.98.4",
|
|
31
|
+
"borsh": "^2.0.0",
|
|
32
|
+
"bs58": "^6.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const { PublicKey } = require('@solana/web3.js');
|
|
2
|
+
|
|
3
|
+
// SATP v2 Program IDs — Devnet
|
|
4
|
+
const DEVNET_PROGRAM_IDS = {
|
|
5
|
+
IDENTITY: new PublicKey('EJtQh4Gyg88zXvSmFpxYkkeZsPwTsjfm4LvjmPQX1FD3'),
|
|
6
|
+
REVIEWS: new PublicKey('D8HsSpK3JtAN7tVcA1yfgxScju7KcG6skEfaShSKojki'),
|
|
7
|
+
REPUTATION: new PublicKey('4y4W2Mdfpu91C4iVowiDyJTmdKSjo8bmSDQrX2c84WQF'),
|
|
8
|
+
ATTESTATIONS: new PublicKey('9xT3eNcndkmnqZtJqDQ1ggckHK7Dxo5EsAt5mHqsPBhP'),
|
|
9
|
+
VALIDATION: new PublicKey('8jLaqodAzfM7oCxP7aedFeszeNjnJ5ik56dzhDU2HQgc'),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// SATP v2 Program IDs — Mainnet
|
|
13
|
+
const MAINNET_PROGRAM_IDS = {
|
|
14
|
+
IDENTITY: new PublicKey('97yL33fcu6iWT2TdERS5HeqrMSGiUnxuy6nUcTrKieSq'),
|
|
15
|
+
REVIEWS: new PublicKey('Ge1sD2qwmH8QaaKCPZzZERvsFXNVMvKbAgTp2p17yjLK'),
|
|
16
|
+
REPUTATION: new PublicKey('C9ogv8TBrvFy4pLKDoGQg9B73Q5rKPPsQ4kzkcDk6Jd'),
|
|
17
|
+
ATTESTATIONS: new PublicKey('ENvaD19QzwWWMJFu5r5xJ9SmHqWN6GvyzxACRejqbdug'),
|
|
18
|
+
VALIDATION: new PublicKey('9p795d2j3eGqzborG2AncucWBaU6PieKxmhKVroV3LNh'),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
|
|
22
|
+
const DEVNET_RPC = 'https://api.devnet.solana.com';
|
|
23
|
+
|
|
24
|
+
// PDA seeds (must match on-chain programs)
|
|
25
|
+
const IDENTITY_SEED = 'identity';
|
|
26
|
+
const REPUTATION_AUTHORITY_SEED = 'reputation_authority';
|
|
27
|
+
const VALIDATION_AUTHORITY_SEED = 'validation_authority';
|
|
28
|
+
const REVIEW_COUNTER_SEED = 'review_counter';
|
|
29
|
+
const MINT_TRACKER_SEED = 'mint_tracker';
|
|
30
|
+
const REVIEWS_AUTHORITY_SEED = 'reviews_authority';
|
|
31
|
+
const ATTESTATION_SEED = 'attestation';
|
|
32
|
+
const REVIEW_SEED = 'review';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get program IDs for a given network.
|
|
36
|
+
* @param {'mainnet'|'devnet'} network
|
|
37
|
+
* @returns {object} Program ID map
|
|
38
|
+
*/
|
|
39
|
+
function getProgramIds(network = 'devnet') {
|
|
40
|
+
return network === 'mainnet' ? MAINNET_PROGRAM_IDS : DEVNET_PROGRAM_IDS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get RPC URL for a given network.
|
|
45
|
+
* @param {'mainnet'|'devnet'} network
|
|
46
|
+
* @returns {string} RPC URL
|
|
47
|
+
*/
|
|
48
|
+
function getRpcUrl(network = 'devnet') {
|
|
49
|
+
return network === 'mainnet' ? MAINNET_RPC : DEVNET_RPC;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
DEVNET_PROGRAM_IDS,
|
|
54
|
+
MAINNET_PROGRAM_IDS,
|
|
55
|
+
getProgramIds,
|
|
56
|
+
getRpcUrl,
|
|
57
|
+
MAINNET_RPC,
|
|
58
|
+
DEVNET_RPC,
|
|
59
|
+
IDENTITY_SEED,
|
|
60
|
+
REPUTATION_AUTHORITY_SEED,
|
|
61
|
+
VALIDATION_AUTHORITY_SEED,
|
|
62
|
+
REVIEW_COUNTER_SEED,
|
|
63
|
+
MINT_TRACKER_SEED,
|
|
64
|
+
REVIEWS_AUTHORITY_SEED,
|
|
65
|
+
ATTESTATION_SEED,
|
|
66
|
+
REVIEW_SEED,
|
|
67
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
const {
|
|
2
|
+
Connection,
|
|
3
|
+
PublicKey,
|
|
4
|
+
Transaction,
|
|
5
|
+
TransactionInstruction,
|
|
6
|
+
SystemProgram,
|
|
7
|
+
} = require('@solana/web3.js');
|
|
8
|
+
const borsh = require('borsh');
|
|
9
|
+
const { getProgramIds, getRpcUrl } = require('./constants');
|
|
10
|
+
const {
|
|
11
|
+
getIdentityPDA,
|
|
12
|
+
getReputationAuthorityPDA,
|
|
13
|
+
getValidationAuthorityPDA,
|
|
14
|
+
getReviewCounterPDA,
|
|
15
|
+
getMintTrackerPDA,
|
|
16
|
+
getReviewsAuthorityPDA,
|
|
17
|
+
getReviewPDA,
|
|
18
|
+
getReviewAttestationPDA,
|
|
19
|
+
} = require('./pda');
|
|
20
|
+
const {
|
|
21
|
+
IdentityAccount, IDENTITY_SCHEMA,
|
|
22
|
+
ReputationAccount, REPUTATION_SCHEMA,
|
|
23
|
+
} = require('./schema');
|
|
24
|
+
const crypto = require('crypto');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute Anchor instruction discriminator.
|
|
28
|
+
* @param {string} ixName - e.g. "create_identity"
|
|
29
|
+
* @returns {Buffer} 8-byte discriminator
|
|
30
|
+
*/
|
|
31
|
+
function anchorDiscriminator(ixName) {
|
|
32
|
+
return crypto.createHash('sha256')
|
|
33
|
+
.update(`global:${ixName}`)
|
|
34
|
+
.digest()
|
|
35
|
+
.slice(0, 8);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class SATPSDK {
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {'mainnet'|'devnet'} [opts.network='devnet'] - Network selection
|
|
42
|
+
* @param {string} [opts.rpcUrl] - Custom RPC endpoint (overrides network default)
|
|
43
|
+
* @param {string} [opts.commitment='confirmed'] - Commitment level
|
|
44
|
+
*/
|
|
45
|
+
constructor(opts = {}) {
|
|
46
|
+
this.network = opts.network || 'devnet';
|
|
47
|
+
this.rpcUrl = opts.rpcUrl || getRpcUrl(this.network);
|
|
48
|
+
this.commitment = opts.commitment || 'confirmed';
|
|
49
|
+
this.connection = new Connection(this.rpcUrl, this.commitment);
|
|
50
|
+
this.programIds = getProgramIds(this.network);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Identity ──────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a createIdentity transaction.
|
|
57
|
+
* @param {PublicKey|string} wallet - Owner wallet (signer)
|
|
58
|
+
* @param {string} agentName
|
|
59
|
+
* @param {string|object} metadata - JSON string or object
|
|
60
|
+
* @returns {{ transaction: Transaction, identityPDA: PublicKey }}
|
|
61
|
+
*/
|
|
62
|
+
async buildCreateIdentity(wallet, agentName, metadata) {
|
|
63
|
+
const walletKey = new PublicKey(wallet);
|
|
64
|
+
const [identityPDA] = getIdentityPDA(walletKey, this.network);
|
|
65
|
+
const metaStr = typeof metadata === 'object' ? JSON.stringify(metadata) : metadata;
|
|
66
|
+
|
|
67
|
+
const disc = anchorDiscriminator('create_identity');
|
|
68
|
+
const nameBytes = Buffer.from(agentName, 'utf8');
|
|
69
|
+
const metaBytes = Buffer.from(metaStr, 'utf8');
|
|
70
|
+
|
|
71
|
+
const data = Buffer.concat([
|
|
72
|
+
disc,
|
|
73
|
+
Buffer.from(new Uint32Array([nameBytes.length]).buffer),
|
|
74
|
+
nameBytes,
|
|
75
|
+
Buffer.from(new Uint32Array([metaBytes.length]).buffer),
|
|
76
|
+
metaBytes,
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const ix = new TransactionInstruction({
|
|
80
|
+
programId: this.programIds.IDENTITY,
|
|
81
|
+
keys: [
|
|
82
|
+
{ pubkey: walletKey, isSigner: true, isWritable: true },
|
|
83
|
+
{ pubkey: identityPDA, isSigner: false, isWritable: true },
|
|
84
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
85
|
+
],
|
|
86
|
+
data,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const tx = new Transaction().add(ix);
|
|
90
|
+
tx.feePayer = walletKey;
|
|
91
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
92
|
+
|
|
93
|
+
return { transaction: tx, identityPDA };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create an identity on-chain (requires a Keypair signer).
|
|
98
|
+
*/
|
|
99
|
+
async createIdentity(signer, agentName, metadata) {
|
|
100
|
+
const { transaction } = await this.buildCreateIdentity(signer.publicKey, agentName, metadata);
|
|
101
|
+
const sig = await this.connection.sendTransaction(transaction, [signer]);
|
|
102
|
+
await this.connection.confirmTransaction(sig, this.commitment);
|
|
103
|
+
return sig;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Fetch identity data for a wallet. Returns null if not found.
|
|
108
|
+
*/
|
|
109
|
+
async getIdentity(wallet) {
|
|
110
|
+
const [pda] = getIdentityPDA(new PublicKey(wallet), this.network);
|
|
111
|
+
const acct = await this.connection.getAccountInfo(pda);
|
|
112
|
+
if (!acct) return null;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const decoded = borsh.deserialize(IDENTITY_SCHEMA, IdentityAccount, acct.data);
|
|
116
|
+
return {
|
|
117
|
+
owner: new PublicKey(decoded.owner).toBase58(),
|
|
118
|
+
agentName: decoded.agentName,
|
|
119
|
+
metadata: decoded.metadata,
|
|
120
|
+
createdAt: Number(decoded.createdAt),
|
|
121
|
+
updatedAt: Number(decoded.updatedAt),
|
|
122
|
+
pda: pda.toBase58(),
|
|
123
|
+
};
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return { pda: pda.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── Reputation (v2 CPI-based recompute) ───────────────
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build a recomputeReputation transaction.
|
|
133
|
+
* This is permissionless — anyone can call it to recompute an agent's score.
|
|
134
|
+
* The Reputation program CPIs into Identity to update the score.
|
|
135
|
+
*
|
|
136
|
+
* @param {PublicKey|string} agentWallet - Agent whose reputation to recompute
|
|
137
|
+
* @param {PublicKey|string} payer - Transaction fee payer (signer)
|
|
138
|
+
* @returns {{ transaction: Transaction }}
|
|
139
|
+
*/
|
|
140
|
+
async buildRecomputeReputation(agentWallet, payer) {
|
|
141
|
+
const agentKey = new PublicKey(agentWallet);
|
|
142
|
+
const payerKey = new PublicKey(payer);
|
|
143
|
+
const [identityPDA] = getIdentityPDA(agentKey, this.network);
|
|
144
|
+
const [repAuthority] = getReputationAuthorityPDA(this.network);
|
|
145
|
+
const [reviewCounter] = getReviewCounterPDA(agentKey, this.network);
|
|
146
|
+
|
|
147
|
+
const disc = anchorDiscriminator('recompute_reputation');
|
|
148
|
+
|
|
149
|
+
const ix = new TransactionInstruction({
|
|
150
|
+
programId: this.programIds.REPUTATION,
|
|
151
|
+
keys: [
|
|
152
|
+
{ pubkey: payerKey, isSigner: true, isWritable: true },
|
|
153
|
+
{ pubkey: identityPDA, isSigner: false, isWritable: true },
|
|
154
|
+
{ pubkey: reviewCounter, isSigner: false, isWritable: false },
|
|
155
|
+
{ pubkey: repAuthority, isSigner: false, isWritable: false },
|
|
156
|
+
{ pubkey: this.programIds.IDENTITY, isSigner: false, isWritable: false },
|
|
157
|
+
],
|
|
158
|
+
data: disc,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const tx = new Transaction().add(ix);
|
|
162
|
+
tx.feePayer = payerKey;
|
|
163
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
164
|
+
|
|
165
|
+
return { transaction: tx };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Trigger reputation recompute (permissionless).
|
|
170
|
+
*/
|
|
171
|
+
async recomputeReputation(signerKeypair, agentWallet) {
|
|
172
|
+
const { transaction } = await this.buildRecomputeReputation(agentWallet, signerKeypair.publicKey);
|
|
173
|
+
const sig = await this.connection.sendTransaction(transaction, [signerKeypair]);
|
|
174
|
+
await this.connection.confirmTransaction(sig, this.commitment);
|
|
175
|
+
return sig;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Fetch reputation data for a wallet.
|
|
180
|
+
*/
|
|
181
|
+
async getReputation(wallet) {
|
|
182
|
+
const [pda] = getIdentityPDA(new PublicKey(wallet), this.network);
|
|
183
|
+
const acct = await this.connection.getAccountInfo(pda);
|
|
184
|
+
if (!acct) return null;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const decoded = borsh.deserialize(IDENTITY_SCHEMA, IdentityAccount, acct.data);
|
|
188
|
+
return {
|
|
189
|
+
owner: new PublicKey(decoded.owner).toBase58(),
|
|
190
|
+
agentName: decoded.agentName,
|
|
191
|
+
reputationScore: decoded.reputationScore || 0,
|
|
192
|
+
verificationLevel: decoded.verificationLevel || 0,
|
|
193
|
+
pda: pda.toBase58(),
|
|
194
|
+
};
|
|
195
|
+
} catch (e) {
|
|
196
|
+
return { pda: pda.toBase58(), raw: acct.data.toString('hex'), error: e.message };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Validation (v2 CPI-based recompute) ───────────────
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Build a recomputeLevel transaction.
|
|
204
|
+
* Permissionless — recomputes verification level based on attestation count.
|
|
205
|
+
*
|
|
206
|
+
* @param {PublicKey|string} agentWallet - Agent whose level to recompute
|
|
207
|
+
* @param {PublicKey|string} payer - Transaction fee payer
|
|
208
|
+
* @returns {{ transaction: Transaction }}
|
|
209
|
+
*/
|
|
210
|
+
async buildRecomputeLevel(agentWallet, payer) {
|
|
211
|
+
const agentKey = new PublicKey(agentWallet);
|
|
212
|
+
const payerKey = new PublicKey(payer);
|
|
213
|
+
const [identityPDA] = getIdentityPDA(agentKey, this.network);
|
|
214
|
+
const [valAuthority] = getValidationAuthorityPDA(this.network);
|
|
215
|
+
|
|
216
|
+
const disc = anchorDiscriminator('recompute_level');
|
|
217
|
+
|
|
218
|
+
const ix = new TransactionInstruction({
|
|
219
|
+
programId: this.programIds.VALIDATION,
|
|
220
|
+
keys: [
|
|
221
|
+
{ pubkey: payerKey, isSigner: true, isWritable: true },
|
|
222
|
+
{ pubkey: identityPDA, isSigner: false, isWritable: true },
|
|
223
|
+
{ pubkey: valAuthority, isSigner: false, isWritable: false },
|
|
224
|
+
{ pubkey: this.programIds.IDENTITY, isSigner: false, isWritable: false },
|
|
225
|
+
],
|
|
226
|
+
data: disc,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const tx = new Transaction().add(ix);
|
|
230
|
+
tx.feePayer = payerKey;
|
|
231
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
232
|
+
|
|
233
|
+
return { transaction: tx };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Trigger verification level recompute (permissionless).
|
|
238
|
+
*/
|
|
239
|
+
async recomputeLevel(signerKeypair, agentWallet) {
|
|
240
|
+
const { transaction } = await this.buildRecomputeLevel(agentWallet, signerKeypair.publicKey);
|
|
241
|
+
const sig = await this.connection.sendTransaction(transaction, [signerKeypair]);
|
|
242
|
+
await this.connection.confirmTransaction(sig, this.commitment);
|
|
243
|
+
return sig;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ─── MintTracker ───────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Build initMintTracker transaction.
|
|
250
|
+
* @param {PublicKey|string} wallet - Identity owner
|
|
251
|
+
* @returns {{ transaction: Transaction, mintTrackerPDA: PublicKey }}
|
|
252
|
+
*/
|
|
253
|
+
async buildInitMintTracker(wallet) {
|
|
254
|
+
const walletKey = new PublicKey(wallet);
|
|
255
|
+
const [identityPDA] = getIdentityPDA(walletKey, this.network);
|
|
256
|
+
const [mintTrackerPDA] = getMintTrackerPDA(identityPDA, this.network);
|
|
257
|
+
|
|
258
|
+
const disc = anchorDiscriminator('init_mint_tracker');
|
|
259
|
+
|
|
260
|
+
const ix = new TransactionInstruction({
|
|
261
|
+
programId: this.programIds.IDENTITY,
|
|
262
|
+
keys: [
|
|
263
|
+
{ pubkey: walletKey, isSigner: true, isWritable: true },
|
|
264
|
+
{ pubkey: identityPDA, isSigner: false, isWritable: false },
|
|
265
|
+
{ pubkey: mintTrackerPDA, isSigner: false, isWritable: true },
|
|
266
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
267
|
+
],
|
|
268
|
+
data: disc,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const tx = new Transaction().add(ix);
|
|
272
|
+
tx.feePayer = walletKey;
|
|
273
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
274
|
+
|
|
275
|
+
return { transaction: tx, mintTrackerPDA };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ─── Verification ──────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if a wallet has a registered SATP identity.
|
|
282
|
+
*/
|
|
283
|
+
async verifyAgent(wallet) {
|
|
284
|
+
const identity = await this.getIdentity(wallet);
|
|
285
|
+
return identity !== null && !identity.error;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─── Utility ───────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Derive all PDAs for a wallet without RPC calls.
|
|
292
|
+
*/
|
|
293
|
+
getPDAs(wallet) {
|
|
294
|
+
const walletKey = new PublicKey(wallet);
|
|
295
|
+
const [identityPDA] = getIdentityPDA(walletKey, this.network);
|
|
296
|
+
const [reviewCounter] = getReviewCounterPDA(walletKey, this.network);
|
|
297
|
+
const [mintTracker] = getMintTrackerPDA(identityPDA, this.network);
|
|
298
|
+
const [repAuthority] = getReputationAuthorityPDA(this.network);
|
|
299
|
+
const [valAuthority] = getValidationAuthorityPDA(this.network);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
identity: identityPDA.toBase58(),
|
|
303
|
+
reviewCounter: reviewCounter.toBase58(),
|
|
304
|
+
mintTracker: mintTracker.toBase58(),
|
|
305
|
+
reputationAuthority: repAuthority.toBase58(),
|
|
306
|
+
validationAuthority: valAuthority.toBase58(),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
SATPSDK,
|
|
313
|
+
getProgramIds,
|
|
314
|
+
getIdentityPDA,
|
|
315
|
+
getReputationAuthorityPDA,
|
|
316
|
+
getValidationAuthorityPDA,
|
|
317
|
+
getReviewCounterPDA,
|
|
318
|
+
getMintTrackerPDA,
|
|
319
|
+
getReviewsAuthorityPDA,
|
|
320
|
+
getReviewPDA,
|
|
321
|
+
getReviewAttestationPDA,
|
|
322
|
+
anchorDiscriminator,
|
|
323
|
+
};
|
package/src/pda.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const { PublicKey } = require('@solana/web3.js');
|
|
2
|
+
const {
|
|
3
|
+
getProgramIds,
|
|
4
|
+
IDENTITY_SEED,
|
|
5
|
+
REPUTATION_AUTHORITY_SEED,
|
|
6
|
+
VALIDATION_AUTHORITY_SEED,
|
|
7
|
+
REVIEW_COUNTER_SEED,
|
|
8
|
+
MINT_TRACKER_SEED,
|
|
9
|
+
REVIEWS_AUTHORITY_SEED,
|
|
10
|
+
ATTESTATION_SEED,
|
|
11
|
+
REVIEW_SEED,
|
|
12
|
+
} = require('./constants');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Derive the Identity PDA for a wallet.
|
|
16
|
+
* Seeds: ["identity", wallet_pubkey]
|
|
17
|
+
* @param {PublicKey|string} wallet
|
|
18
|
+
* @param {'mainnet'|'devnet'} network
|
|
19
|
+
*/
|
|
20
|
+
function getIdentityPDA(wallet, network = 'devnet') {
|
|
21
|
+
const walletKey = new PublicKey(wallet);
|
|
22
|
+
const programIds = getProgramIds(network);
|
|
23
|
+
return PublicKey.findProgramAddressSync(
|
|
24
|
+
[Buffer.from(IDENTITY_SEED), walletKey.toBuffer()],
|
|
25
|
+
programIds.IDENTITY
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Derive the Reputation Authority PDA (program signer for CPI).
|
|
31
|
+
* Seeds: ["reputation_authority"]
|
|
32
|
+
* @param {'mainnet'|'devnet'} network
|
|
33
|
+
*/
|
|
34
|
+
function getReputationAuthorityPDA(network = 'devnet') {
|
|
35
|
+
const programIds = getProgramIds(network);
|
|
36
|
+
return PublicKey.findProgramAddressSync(
|
|
37
|
+
[Buffer.from(REPUTATION_AUTHORITY_SEED)],
|
|
38
|
+
programIds.REPUTATION
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Derive the Validation Authority PDA (program signer for CPI).
|
|
44
|
+
* Seeds: ["validation_authority"]
|
|
45
|
+
* @param {'mainnet'|'devnet'} network
|
|
46
|
+
*/
|
|
47
|
+
function getValidationAuthorityPDA(network = 'devnet') {
|
|
48
|
+
const programIds = getProgramIds(network);
|
|
49
|
+
return PublicKey.findProgramAddressSync(
|
|
50
|
+
[Buffer.from(VALIDATION_AUTHORITY_SEED)],
|
|
51
|
+
programIds.VALIDATION
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Derive the Review Counter PDA for an agent.
|
|
57
|
+
* Seeds: ["review_counter", agent_id]
|
|
58
|
+
* @param {PublicKey|string} agentId
|
|
59
|
+
* @param {'mainnet'|'devnet'} network
|
|
60
|
+
*/
|
|
61
|
+
function getReviewCounterPDA(agentId, network = 'devnet') {
|
|
62
|
+
const agentKey = new PublicKey(agentId);
|
|
63
|
+
const programIds = getProgramIds(network);
|
|
64
|
+
return PublicKey.findProgramAddressSync(
|
|
65
|
+
[Buffer.from(REVIEW_COUNTER_SEED), agentKey.toBuffer()],
|
|
66
|
+
programIds.REVIEWS
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Derive the MintTracker PDA for an identity.
|
|
72
|
+
* Seeds: ["mint_tracker", identity_pda]
|
|
73
|
+
* @param {PublicKey|string} identityPDA
|
|
74
|
+
* @param {'mainnet'|'devnet'} network
|
|
75
|
+
*/
|
|
76
|
+
function getMintTrackerPDA(identityPDA, network = 'devnet') {
|
|
77
|
+
const identityKey = new PublicKey(identityPDA);
|
|
78
|
+
const programIds = getProgramIds(network);
|
|
79
|
+
return PublicKey.findProgramAddressSync(
|
|
80
|
+
[Buffer.from(MINT_TRACKER_SEED), identityKey.toBuffer()],
|
|
81
|
+
programIds.IDENTITY
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Derive the Reviews Authority PDA (program signer for CPI into Attestations).
|
|
87
|
+
* Seeds: ["reviews_authority"]
|
|
88
|
+
* @param {'mainnet'|'devnet'} network
|
|
89
|
+
*/
|
|
90
|
+
function getReviewsAuthorityPDA(network = 'devnet') {
|
|
91
|
+
const programIds = getProgramIds(network);
|
|
92
|
+
return PublicKey.findProgramAddressSync(
|
|
93
|
+
[Buffer.from(REVIEWS_AUTHORITY_SEED)],
|
|
94
|
+
programIds.REVIEWS
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Derive the Review PDA for a specific reviewer + agent pair.
|
|
100
|
+
* Seeds: ["review", agent_id, reviewer]
|
|
101
|
+
* @param {PublicKey|string} agentId
|
|
102
|
+
* @param {PublicKey|string} reviewer
|
|
103
|
+
* @param {'mainnet'|'devnet'} network
|
|
104
|
+
*/
|
|
105
|
+
function getReviewPDA(agentId, reviewer, network = 'devnet') {
|
|
106
|
+
const agentKey = new PublicKey(agentId);
|
|
107
|
+
const reviewerKey = new PublicKey(reviewer);
|
|
108
|
+
const programIds = getProgramIds(network);
|
|
109
|
+
return PublicKey.findProgramAddressSync(
|
|
110
|
+
[Buffer.from(REVIEW_SEED), agentKey.toBuffer(), reviewerKey.toBuffer()],
|
|
111
|
+
programIds.REVIEWS
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Derive the Review Attestation PDA (auto-created via CPI when a review is submitted).
|
|
117
|
+
* Seeds: ["attestation", agent_id, reviews_authority, "review", reviewer]
|
|
118
|
+
* @param {PublicKey|string} agentId
|
|
119
|
+
* @param {PublicKey} reviewsAuthority - Use getReviewsAuthorityPDA() to derive
|
|
120
|
+
* @param {PublicKey|string} reviewer
|
|
121
|
+
* @param {'mainnet'|'devnet'} network
|
|
122
|
+
*/
|
|
123
|
+
function getReviewAttestationPDA(agentId, reviewsAuthority, reviewer, network = 'devnet') {
|
|
124
|
+
const agentKey = new PublicKey(agentId);
|
|
125
|
+
const reviewerKey = new PublicKey(reviewer);
|
|
126
|
+
const programIds = getProgramIds(network);
|
|
127
|
+
return PublicKey.findProgramAddressSync(
|
|
128
|
+
[
|
|
129
|
+
Buffer.from(ATTESTATION_SEED),
|
|
130
|
+
agentKey.toBuffer(),
|
|
131
|
+
reviewsAuthority.toBuffer(),
|
|
132
|
+
Buffer.from(REVIEW_SEED),
|
|
133
|
+
reviewerKey.toBuffer(),
|
|
134
|
+
],
|
|
135
|
+
programIds.ATTESTATIONS
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
getIdentityPDA,
|
|
141
|
+
getReputationAuthorityPDA,
|
|
142
|
+
getValidationAuthorityPDA,
|
|
143
|
+
getReviewCounterPDA,
|
|
144
|
+
getMintTrackerPDA,
|
|
145
|
+
getReviewsAuthorityPDA,
|
|
146
|
+
getReviewPDA,
|
|
147
|
+
getReviewAttestationPDA,
|
|
148
|
+
};
|
package/src/schema.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const borsh = require('borsh');
|
|
2
|
+
|
|
3
|
+
// ---- Identity Account Schema ----
|
|
4
|
+
class IdentityAccount {
|
|
5
|
+
constructor(fields) {
|
|
6
|
+
this.discriminator = fields.discriminator; // 8 bytes (Anchor)
|
|
7
|
+
this.owner = fields.owner; // 32 bytes pubkey
|
|
8
|
+
this.agentName = fields.agentName; // string
|
|
9
|
+
this.metadata = fields.metadata; // string (JSON)
|
|
10
|
+
this.createdAt = fields.createdAt; // i64 timestamp
|
|
11
|
+
this.updatedAt = fields.updatedAt; // i64 timestamp
|
|
12
|
+
this.bump = fields.bump; // u8
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const IDENTITY_SCHEMA = new Map([
|
|
17
|
+
[IdentityAccount, {
|
|
18
|
+
kind: 'struct',
|
|
19
|
+
fields: [
|
|
20
|
+
['discriminator', [8]],
|
|
21
|
+
['owner', [32]],
|
|
22
|
+
['agentName', 'string'],
|
|
23
|
+
['metadata', 'string'],
|
|
24
|
+
['createdAt', 'u64'],
|
|
25
|
+
['updatedAt', 'u64'],
|
|
26
|
+
['bump', 'u8'],
|
|
27
|
+
],
|
|
28
|
+
}],
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// ---- Reputation Account Schema ----
|
|
32
|
+
class ReputationAccount {
|
|
33
|
+
constructor(fields) {
|
|
34
|
+
this.discriminator = fields.discriminator;
|
|
35
|
+
this.owner = fields.owner;
|
|
36
|
+
this.score = fields.score; // u64
|
|
37
|
+
this.endorsements = fields.endorsements; // u32 count
|
|
38
|
+
this.lastEndorser = fields.lastEndorser; // 32 bytes pubkey
|
|
39
|
+
this.updatedAt = fields.updatedAt;
|
|
40
|
+
this.bump = fields.bump;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const REPUTATION_SCHEMA = new Map([
|
|
45
|
+
[ReputationAccount, {
|
|
46
|
+
kind: 'struct',
|
|
47
|
+
fields: [
|
|
48
|
+
['discriminator', [8]],
|
|
49
|
+
['owner', [32]],
|
|
50
|
+
['score', 'u64'],
|
|
51
|
+
['endorsements', 'u32'],
|
|
52
|
+
['lastEndorser', [32]],
|
|
53
|
+
['updatedAt', 'u64'],
|
|
54
|
+
['bump', 'u8'],
|
|
55
|
+
],
|
|
56
|
+
}],
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
module.exports = { IdentityAccount, IDENTITY_SCHEMA, ReputationAccount, REPUTATION_SCHEMA };
|