@brainai/satp-client 2.0.4 → 2.0.6

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  **Solana Agent Trust Protocol** - JavaScript/TypeScript SDK for reading and
4
4
  building reviewed SATP V3 program interactions.
5
5
 
6
- Current stable npm package: **@brainai/satp-client@2.0.3** | rc dist-tag: **@brainai/satp-client@2.0.2** | Programs: **6**
6
+ Current stable npm package: **@brainai/satp-client@2.0.5** | unpublished source candidate: **@brainai/satp-client@2.0.6** | rc dist-tag: **@brainai/satp-client@2.0.2** | Programs: **6**
7
7
 
8
8
  ## Installation
9
9
 
@@ -11,20 +11,25 @@ Choose stable, rc, or Git based on what the consumer needs to prove:
11
11
 
12
12
  | Channel | Use when | Command |
13
13
  | --- | --- | --- |
14
- | Stable npm | Default production-style consumption of the stable public package. | `npm install @brainai/satp-client@2.0.3` |
14
+ | Stable npm | Default production-style consumption of the stable public package. | `npm install @brainai/satp-client@2.0.5` |
15
15
  | Historical rc exact version | Explicit HQ-assigned reproduction or lockfile evidence for the historical pre-stable artifact. | `npm install @brainai/satp-client@2.0.2` |
16
16
  | Release candidate tag | Explicit HQ-assigned rc validation where a moving dist-tag is acceptable and the task names the tag as the target. | `npm install @brainai/satp-client@rc` |
17
17
  | 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>` |
18
18
 
19
- Registry readback on 2026-07-26 shows npm `latest` resolves to
20
- `@brainai/satp-client@2.0.3` and the `rc` dist-tag resolves to `2.0.2`.
21
- Stable consumers should use `latest`/`2.0.3` unless HQ assigns an explicit
19
+ Registry readback on 2026-07-31 shows npm `latest` resolves to
20
+ `@brainai/satp-client@2.0.5` and the `rc` dist-tag resolves to `2.0.2`.
21
+ Stable consumers should use `latest`/`2.0.5` unless HQ assigns an explicit
22
22
  release-candidate validation task.
23
23
 
24
+ Source release-prep on 2026-08-02 advances package metadata to `2.0.6` as the
25
+ next unpublished stable semver candidate because npm `latest` is already
26
+ `2.0.5` and npm package versions are immutable. This does not publish the
27
+ package or move any npm dist-tag.
28
+
24
29
  For stable consumer installs, pin the current published npm package:
25
30
 
26
31
  ```bash
27
- npm install @brainai/satp-client@2.0.3
32
+ npm install @brainai/satp-client@2.0.5
28
33
  ```
29
34
 
30
35
  For rc validation:
@@ -56,6 +61,10 @@ a separate HQ approval and the relevant owner-gated runbook. See
56
61
  `docs/mainnet-authority-decision-packet-6c8a5545.md` for the public authority
57
62
  decision packet.
58
63
 
64
+ Legacy `SATPSDK` is V2-only compatibility. It defaults to devnet and rejects
65
+ `network: 'mainnet'` unless `allowLegacyV2Mainnet: true` is passed explicitly.
66
+ New integrations should prefer `SATPV3SDK` or `createSATPClient`.
67
+
59
68
  RC-S6 semantic uncertainty outcomes are covered by the offline conformance gate
60
69
  merged in `93db1b3` (PR #53, `[#43394290]`) and runnable with
61
70
  `npm run test:conformance:rc-s6` from the repository root. Treat positive
@@ -73,6 +82,13 @@ For the root consumer quickstart covering stable npm, rc validation, Git-review
73
82
  pins, read-only trust packets, conformance checks, and network boundaries, see
74
83
  [`docs/quickstart.md`](../../docs/quickstart.md).
75
84
 
85
+ For copy-paste MCP, A2A/agent-runtime, and x402 paid-endpoint examples, see
86
+ [`docs/adoption-quickstarts.md`](../../docs/adoption-quickstarts.md) or run:
87
+
88
+ ```bash
89
+ node packages/satp-client/examples/adoption-quickstarts.js
90
+ ```
91
+
76
92
  ```javascript
77
93
  const { SATPV3SDK } = require('@brainai/satp-client');
78
94
 
@@ -116,14 +132,14 @@ const tx = await sdk.buildCreateIdentity(creatorPubkey, 'myAgent', {
116
132
 
117
133
  ## Programs & Program IDs
118
134
 
119
- | Program | Devnet | Description |
135
+ | Program | Mainnet | Description |
120
136
  |---------|--------|-------------|
121
137
  | `identity_v3` | `GTppU4E44BqXTQgbqMZ68ozFzhP1TLty3EGnzzjtNZfG` | Agent identity, names, wallets, face/birth |
122
138
  | `reviews_v3` | `r9XX4frcqxxAZ6Au9V5PA3EAxs1zoNckqLLmoSRcNr4` | Peer reviews with 1-5 star ratings |
123
139
  | `attestations_v3` | `6Xd1dAQJPvQRJ4Ntr6LtPTjDjPUZ8nfnmYLZaZ2DtrdD` | Third-party attestations & proofs |
124
140
  | `reputation_v3` | `2Lz7KzMvKdrGeAuS8WPHu7jK2yScrnKVgacpYVEuDjkJ` | Weighted reputation scoring (CPI → identity) |
125
141
  | `validation_v3` | `6rYRiCYidJYV7QvKrzKGgNu4oMh6BAvynked69R7xMbV` | Validation level computation (CPI → identity) |
126
- | `escrow_v3` | `B1Se8SPx7GLUisa4LYeXY1tDZy5TviJrsV2yMLgqUXmg` | SOL escrow for agent jobs |
142
+ | `escrow_v3` | `HXCUWKR2NvRcZ7rNAJHwPcH6QAAWaLR4bRFbfyuDND6C` | SOL escrow for agent jobs |
127
143
 
128
144
  ## API Reference
129
145
 
@@ -183,6 +199,15 @@ builds a runtime policy action descriptor. It asserts
183
199
  `paymentAuthorization: false`, `actionAuthorization: false`,
184
200
  `spendAuthorized: false`, and `livePaymentRequired: false`.
185
201
 
202
+ The proposed issue #14 Track C endpoint spec for third-party SATP reputation
203
+ and evidence consumers lives in
204
+ [`docs/x402-reputation-evidence-lookup-api.md`](../../docs/x402-reputation-evidence-lookup-api.md).
205
+ Its companion offline caller example is runnable with:
206
+
207
+ ```bash
208
+ node packages/satp-client/examples/x402-reputation-evidence-lookup-client.js
209
+ ```
210
+
186
211
  ### Runtime Policy Adapter Helper
187
212
 
188
213
  `createRuntimePolicyAdapter(opts)` provides a host-oriented wrapper around the
@@ -541,6 +566,10 @@ const genesis = deserializeGenesisRecord(acct.data);
541
566
  console.log(genesis.agentName, genesis.reputationScore, genesis.isBorn);
542
567
  ```
543
568
 
569
+ Genesis parsing is layout-aware for RC-S6 review. Parsed records include
570
+ `layout` and `hasIsActiveField`; `isActive` is `null` when the account bytes do
571
+ not carry the historical `is_active` field.
572
+
544
573
  ### Usage: Auto-detect Account Type
545
574
 
546
575
  ```js
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const {
5
+ buildRuntimePolicyActionDescriptorFromX402Discovery,
6
+ buildSatpTrustPacket,
7
+ createRuntimePolicyAdapter,
8
+ getV3ProgramIds,
9
+ validateSatpTrustPacket,
10
+ } = require('../src');
11
+
12
+ const METADATA_HASH = '93d122f8879fe87c186c10a00db8fbc80a73cecd2ede44b9ffa6410be3c2b805';
13
+
14
+ async function mcpToolServerQuickstart() {
15
+ function createSatpMcpTools() {
16
+ return {
17
+ 'satp.getPrograms': async ({ network = 'devnet' } = {}) => ({
18
+ network,
19
+ programs: getV3ProgramIds(network),
20
+ guardrails: {
21
+ readOnly: true,
22
+ writesSolanaState: false,
23
+ usesKeypairs: false,
24
+ },
25
+ }),
26
+
27
+ 'satp.prepareAttestationRequest': async ({
28
+ subjectWallet,
29
+ agentId,
30
+ claimType = 'identity',
31
+ metadataHash,
32
+ network = 'devnet',
33
+ }) => {
34
+ const packet = buildSatpTrustPacket({
35
+ subjectWallet,
36
+ agentId,
37
+ claimType,
38
+ metadataHash,
39
+ network,
40
+ });
41
+ const validation = validateSatpTrustPacket(packet);
42
+ if (!validation.ok) throw new Error(validation.errors.join('; '));
43
+
44
+ return {
45
+ packet,
46
+ guardrails: {
47
+ readOnly: true,
48
+ livePaymentRequired: false,
49
+ transaction: null,
50
+ },
51
+ };
52
+ },
53
+ };
54
+ }
55
+
56
+ const tools = createSatpMcpTools();
57
+ const prepared = await tools['satp.prepareAttestationRequest']({
58
+ subjectWallet: '11111111111111111111111111111111',
59
+ agentId: 'example-mcp-agent',
60
+ metadataHash: METADATA_HASH,
61
+ });
62
+ return {
63
+ audience: 'mcp-tool-server-builders',
64
+ mode: prepared.packet.mode,
65
+ guardrails: prepared.guardrails,
66
+ };
67
+ }
68
+
69
+ function agentRuntimeQuickstart() {
70
+ const now = new Date().toISOString();
71
+ const adapter = createRuntimePolicyAdapter({
72
+ policy: {
73
+ minimumTrustScore: 70,
74
+ denyTrustScoreBelow: 25,
75
+ requireVerifiedIdentity: true,
76
+ maxAutoSpendUsd: 0,
77
+ },
78
+ });
79
+ const identity = {
80
+ agentId: 'partner-runtime-agent',
81
+ active: true,
82
+ satpVerified: true,
83
+ trustScore: 82,
84
+ capabilities: ['a2a:task.delegate'],
85
+ evidenceUpdatedAt: now,
86
+ };
87
+ const action = adapter.action({
88
+ type: 'a2a_agent_runtime',
89
+ resource: 'a2a://task/delegate',
90
+ operation: 'delegate',
91
+ capability: 'a2a:task.delegate',
92
+ requiresFreshEvidence: true,
93
+ operatorApprovalRequired: false,
94
+ });
95
+ const result = adapter.evaluate(identity, action, { now });
96
+ const auditTrace = adapter.auditTrace(identity, action, { result, now });
97
+
98
+ return {
99
+ audience: 'a2a-agent-runtime-builders',
100
+ decision: result.decision,
101
+ reasonCodes: result.reasonCodes,
102
+ localDecisionOnly: auditTrace.guardrails.localDecisionOnly,
103
+ };
104
+ }
105
+
106
+ function x402PaidEndpointQuickstart() {
107
+ const discovery = {
108
+ endpoint: 'https://api.example.test/satp/reputation',
109
+ action: 'lookup',
110
+ accepts: [{
111
+ scheme: 'exact',
112
+ network: 'solana-mainnet',
113
+ asset: 'USDC',
114
+ maxAmountRequired: '10000',
115
+ resource: 'https://api.example.test/satp/reputation',
116
+ description: 'SATP-backed reputation evidence lookup',
117
+ }],
118
+ };
119
+ const lookupAction = buildRuntimePolicyActionDescriptorFromX402Discovery(
120
+ discovery,
121
+ { maxCostUsd: 0.01 }
122
+ );
123
+ const adapter = createRuntimePolicyAdapter({
124
+ policy: { maxAutoSpendUsd: 0, minimumTrustScore: 70 },
125
+ });
126
+ const identity = {
127
+ agentId: 'x402-consumer-agent',
128
+ active: true,
129
+ satpVerified: true,
130
+ trustScore: 90,
131
+ capabilities: ['satp:evidence.lookup'],
132
+ evidenceUpdatedAt: '2026-05-21T00:00:00Z',
133
+ };
134
+ const result = adapter.evaluate(identity, lookupAction, {
135
+ evidenceLookupPaymentPreapproved: false,
136
+ now: '2026-07-31T00:00:00Z',
137
+ });
138
+
139
+ return {
140
+ audience: 'x402-paid-endpoint-builders',
141
+ decision: result.decision,
142
+ reasonCodes: result.reasonCodes,
143
+ guardrail: lookupAction.evidenceLookup.guardrail,
144
+ paymentBoundary: {
145
+ satpPackageUseIsFreeOpen: true,
146
+ x402OnlyForHostedReputationEvidenceLookup: true,
147
+ paymentIsNotActionAuthorization: true,
148
+ livePaymentRequired: false,
149
+ },
150
+ };
151
+ }
152
+
153
+ async function run() {
154
+ const examples = [
155
+ await mcpToolServerQuickstart(),
156
+ agentRuntimeQuickstart(),
157
+ x402PaidEndpointQuickstart(),
158
+ ];
159
+ console.log(JSON.stringify({ examples }, null, 2));
160
+ }
161
+
162
+ if (require.main === module) {
163
+ run().catch((err) => {
164
+ console.error(err);
165
+ process.exitCode = 1;
166
+ });
167
+ }
168
+
169
+ module.exports = {
170
+ agentRuntimeQuickstart,
171
+ mcpToolServerQuickstart,
172
+ x402PaidEndpointQuickstart,
173
+ };
@@ -44,7 +44,7 @@ const examples = [
44
44
  },
45
45
  },
46
46
  {
47
- name: 'Host trust-score degrade',
47
+ name: 'AgentFolio trust-score degrade',
48
48
  identity: { ...trustedIdentity, trustScore: 62 },
49
49
  action: buildRuntimePolicyActionDescriptor({
50
50
  type: 'agentfolio_trust_gate',
@@ -55,6 +55,18 @@ const examples = [
55
55
  now: '2026-05-21T00:00:00Z',
56
56
  },
57
57
  },
58
+ {
59
+ name: 'Host trust-score gate alias',
60
+ identity: { ...trustedIdentity, trustScore: 74 },
61
+ action: buildRuntimePolicyActionDescriptor({
62
+ type: 'host_trust_gate',
63
+ profileId: 'brainchain-demo',
64
+ minimumTrustScore: 80,
65
+ }),
66
+ options: {
67
+ now: '2026-05-21T00:00:00Z',
68
+ },
69
+ },
58
70
  {
59
71
  name: 'Optional paid x402 evidence lookup',
60
72
  identity: { ...trustedIdentity, evidenceUpdatedAt: '2026-04-01T00:00:00Z' },
@@ -0,0 +1,182 @@
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
+ buildRuntimePolicyActionDescriptorFromX402Discovery,
8
+ } = require('../src');
9
+
10
+ const LOOKUP_SCHEMA_VERSION = 'satp.x402ReputationEvidenceLookup.v1';
11
+
12
+ const discoveryMetadata = {
13
+ schemaVersion: 'satp.x402DiscoveryMetadata.v1',
14
+ protocol: 'x402',
15
+ action: 'satp.reputationEvidence.lookup',
16
+ endpoint: 'https://satp-provider.example/v1/satp/evidence/reputation',
17
+ resource: 'satp://evidence/reputation',
18
+ paymentRequired: true,
19
+ accepts: [
20
+ {
21
+ scheme: 'exact',
22
+ network: '<provider-selected-payment-network>',
23
+ asset: '<provider-selected-asset>',
24
+ payTo: '<provider-controlled-recipient>',
25
+ maxAmountRequired: '<provider-disclosed-maximum>',
26
+ resource: 'https://satp-provider.example/v1/satp/evidence/reputation',
27
+ description: 'Read-only SATP reputation evidence lookup',
28
+ mimeType: 'application/json',
29
+ maxTimeoutSeconds: 60,
30
+ },
31
+ ],
32
+ guardrail: X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION,
33
+ };
34
+
35
+ function buildLookupUrl(endpoint, request) {
36
+ const url = new URL(endpoint);
37
+ if (request.agentId) url.searchParams.set('agentId', request.agentId);
38
+ if (request.wallet) url.searchParams.set('wallet', request.wallet);
39
+ if (request.network) url.searchParams.set('network', request.network);
40
+ if (request.include) url.searchParams.set('include', request.include.join(','));
41
+ if (request.minEvidenceUpdatedAt) url.searchParams.set('minEvidenceUpdatedAt', request.minEvidenceUpdatedAt);
42
+ if (request.trace) url.searchParams.set('trace', request.trace);
43
+ return url;
44
+ }
45
+
46
+ function assertLookupBoundary(body) {
47
+ assert.equal(body.schemaVersion, LOOKUP_SCHEMA_VERSION);
48
+ assert.equal(body.policyBoundary.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
49
+ assert.equal(body.policyBoundary.paymentAuthorization, false);
50
+ assert.equal(body.policyBoundary.actionAuthorization, false);
51
+ assert.equal(body.policyBoundary.spendAuthorized, false);
52
+ }
53
+
54
+ async function fetchSatpReputationEvidence({
55
+ fetchImpl,
56
+ endpoint = discoveryMetadata.endpoint,
57
+ request,
58
+ x402PaymentHeader,
59
+ }) {
60
+ if (typeof fetchImpl !== 'function') throw new Error('fetchImpl is required for this offline example');
61
+ const url = buildLookupUrl(endpoint, request);
62
+ const response = await fetchImpl(url, {
63
+ method: 'GET',
64
+ headers: {
65
+ Accept: 'application/json',
66
+ 'X-PAYMENT': x402PaymentHeader,
67
+ },
68
+ });
69
+ const body = await response.json();
70
+ assertLookupBoundary(body);
71
+ if (!response.ok) {
72
+ throw new Error(body.error && body.error.code ? body.error.code : 'satp_lookup_failed');
73
+ }
74
+ return body;
75
+ }
76
+
77
+ function createMockFetch() {
78
+ return async function mockFetch(url, options = {}) {
79
+ assert.equal(url.pathname, '/v1/satp/evidence/reputation');
80
+ assert.equal(url.searchParams.get('agentId'), 'brainChain');
81
+ assert.equal(options.headers.Accept, 'application/json');
82
+ assert.equal(options.headers['X-PAYMENT'], 'fixture-x402-payment-proof');
83
+
84
+ return {
85
+ ok: true,
86
+ async json() {
87
+ return {
88
+ schemaVersion: LOOKUP_SCHEMA_VERSION,
89
+ lookup: {
90
+ lookupId: 'lkp_fixture_brainchain_devnet_01',
91
+ agentId: 'brainChain',
92
+ wallet: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgBNG',
93
+ network: 'devnet',
94
+ queriedAt: '2026-07-28T03:18:00Z',
95
+ },
96
+ reputation: {
97
+ score: 88,
98
+ tier: 'trusted',
99
+ sourceProgram: 'reputation_v3',
100
+ computedAt: '2026-07-28T03:10:00Z',
101
+ },
102
+ evidence: {
103
+ attestations: [
104
+ {
105
+ claimType: 'github_verified',
106
+ issuer: 'satp-attestation-authority',
107
+ status: 'valid',
108
+ },
109
+ ],
110
+ },
111
+ proof: {
112
+ source: 'satp',
113
+ programSet: 'v3',
114
+ trace: 'redacted',
115
+ evidenceUpdatedAt: '2026-07-28T03:10:00Z',
116
+ },
117
+ policyBoundary: {
118
+ paymentAuthorization: false,
119
+ actionAuthorization: false,
120
+ spendAuthorized: false,
121
+ livePaymentRequiredByClient: false,
122
+ guardrail: X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION,
123
+ message: 'x402 payment grants lookup access only and does not authorize SATP agent actions.',
124
+ },
125
+ };
126
+ },
127
+ };
128
+ };
129
+ }
130
+
131
+ async function runExample() {
132
+ const descriptor = buildRuntimePolicyActionDescriptorFromX402Discovery(discoveryMetadata, {
133
+ sourceKind: 'well-known-x402',
134
+ maxCostUsd: 0,
135
+ });
136
+ assert.equal(descriptor.operation, 'satp.reputationEvidence.lookup');
137
+ assert.equal(descriptor.paymentAuthorization, false);
138
+ assert.equal(descriptor.actionAuthorization, false);
139
+ assert.equal(descriptor.spendAuthorized, false);
140
+ assert.equal(descriptor.livePaymentRequired, false);
141
+
142
+ const body = await fetchSatpReputationEvidence({
143
+ fetchImpl: createMockFetch(),
144
+ request: {
145
+ agentId: 'brainChain',
146
+ network: 'devnet',
147
+ include: ['identity', 'reputation', 'attestations', 'policy'],
148
+ trace: 'redacted',
149
+ },
150
+ x402PaymentHeader: 'fixture-x402-payment-proof',
151
+ });
152
+
153
+ return {
154
+ endpoint: discoveryMetadata.endpoint,
155
+ lookupId: body.lookup.lookupId,
156
+ score: body.reputation.score,
157
+ guardrail: body.policyBoundary.guardrail,
158
+ paymentAuthorization: body.policyBoundary.paymentAuthorization,
159
+ actionAuthorization: body.policyBoundary.actionAuthorization,
160
+ spendAuthorized: body.policyBoundary.spendAuthorized,
161
+ livePaymentRequired: descriptor.livePaymentRequired,
162
+ };
163
+ }
164
+
165
+ if (require.main === module) {
166
+ runExample()
167
+ .then((result) => console.log(JSON.stringify(result, null, 2)))
168
+ .catch((err) => {
169
+ console.error(err.message);
170
+ process.exitCode = 1;
171
+ });
172
+ }
173
+
174
+ module.exports = {
175
+ LOOKUP_SCHEMA_VERSION,
176
+ discoveryMetadata,
177
+ buildLookupUrl,
178
+ assertLookupBoundary,
179
+ fetchSatpReputationEvidence,
180
+ createMockFetch,
181
+ runExample,
182
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainai/satp-client",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "SATP client SDK surface prepared for release-candidate review; publish only after the release packet passes.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -26,6 +26,7 @@
26
26
  "README.md"
27
27
  ],
28
28
  "scripts": {
29
+ "check:package-health": "node ../../scripts/check-satp-client-package-health.js",
29
30
  "test": "node test.js",
30
31
  "test:runtime-policy": "node --test test-runtime-policy-adapter.js",
31
32
  "runtime-policy:example": "node examples/runtime-policy-adapter.js",
@@ -45,7 +45,7 @@ export interface ParsedGenesisRecord {
45
45
  faceBurnTx: string | null;
46
46
  genesisRecord: number;
47
47
  isBorn: boolean;
48
- isActive: boolean;
48
+ isActive: boolean | null;
49
49
  authority: string;
50
50
  pendingAuthority: string | null;
51
51
  reputationScore: number;
@@ -55,6 +55,8 @@ export interface ParsedGenesisRecord {
55
55
  createdAt: number;
56
56
  updatedAt: number;
57
57
  bump: number;
58
+ layout: 'idl-with-is-active' | 'deployed-no-is-active';
59
+ hasIsActiveField: boolean;
58
60
  }
59
61
 
60
62
  export interface ParsedLinkedWallet {
@@ -64,8 +64,9 @@ class BorshReader {
64
64
 
65
65
  /** Read u64 LE → number (safe for values < 2^53) */
66
66
  readU64() {
67
- return Number(this.buf.readBigUInt64LE(this.offset));
68
- // Note: offset advanced after return won't work — fix:
67
+ const val = Number(this.buf.readBigUInt64LE(this.offset));
68
+ this.offset += 8;
69
+ return val;
69
70
  }
70
71
 
71
72
  /** Read u64 LE → number, advances offset */
@@ -185,7 +186,7 @@ class BorshReader {
185
186
  * @param {Buffer} data - Raw account data (with 8-byte discriminator)
186
187
  * @returns {object} Parsed GenesisRecord
187
188
  */
188
- function deserializeGenesisRecord(data) {
189
+ function parseGenesisRecordLayout(data, includeIsActive) {
189
190
  const r = new BorshReader(data).skipDiscriminator();
190
191
 
191
192
  const agentIdHash = r.readFixedBytes32();
@@ -198,7 +199,7 @@ function deserializeGenesisRecord(data) {
198
199
  const faceMint = r.readPubkey();
199
200
  const faceBurnTx = r.readString();
200
201
  const genesisRecord = r.readI64();
201
- const isActive = r.readBool();
202
+ const isActive = includeIsActive ? r.readBool() : null;
202
203
  const authority = r.readPubkeyBase58();
203
204
  const pendingAuthority = r.readOptionPubkey();
204
205
  const reputationScore = r.readU64Num();
@@ -209,6 +210,10 @@ function deserializeGenesisRecord(data) {
209
210
  const updatedAt = r.readI64();
210
211
  const bump = r.readU8();
211
212
 
213
+ if (r.remaining() !== 0) {
214
+ throw new Error(`GenesisRecord layout left ${r.remaining()} unread bytes`);
215
+ }
216
+
212
217
  return {
213
218
  agentIdHash: agentIdHash.toString('hex'),
214
219
  agentName,
@@ -231,9 +236,23 @@ function deserializeGenesisRecord(data) {
231
236
  createdAt,
232
237
  updatedAt,
233
238
  bump,
239
+ layout: includeIsActive ? 'idl-with-is-active' : 'deployed-no-is-active',
240
+ hasIsActiveField: includeIsActive,
234
241
  };
235
242
  }
236
243
 
244
+ function deserializeGenesisRecord(data) {
245
+ const errors = [];
246
+ for (const includeIsActive of [true, false]) {
247
+ try {
248
+ return parseGenesisRecordLayout(data, includeIsActive);
249
+ } catch (e) {
250
+ errors.push(e.message);
251
+ }
252
+ }
253
+ throw new Error(`Unable to deserialize GenesisRecord with known layouts: ${errors.join('; ')}`);
254
+ }
255
+
237
256
  /**
238
257
  * Deserialize a LinkedWallet from raw account data.
239
258
  * @param {Buffer} data - Raw account data (with 8-byte discriminator)
package/src/constants.js CHANGED
@@ -10,8 +10,10 @@ const DEVNET_PROGRAM_IDS = {
10
10
  ESCROW: new PublicKey('UpJ7jmUzHkQ7EdBKiBv3zq8Dr1fVh6GVWKa7nYtwQ22'),
11
11
  };
12
12
 
13
- // SATP v2 Program IDs Mainnet
14
- const MAINNET_PROGRAM_IDS = {
13
+ const LEGACY_V2_MAINNET_FENCE_MESSAGE = 'Legacy SATP V2 mainnet program IDs are fenced; use getV3ProgramIds("mainnet") for V3 or pass allowLegacyV2Mainnet: true for explicit read-only legacy V2 access';
14
+
15
+ // SATP v2 Program IDs — Mainnet. Keep internal so direct public imports fail closed.
16
+ const LEGACY_V2_MAINNET_PROGRAM_IDS = {
15
17
  IDENTITY: new PublicKey('97yL33fcu6iWT2TdERS5HeqrMSGiUnxuy6nUcTrKieSq'),
16
18
  REVIEWS: new PublicKey('Ge1sD2qwmH8QaaKCPZzZERvsFXNVMvKbAgTp2p17yjLK'),
17
19
  REPUTATION: new PublicKey('C9ogv8TBrvFy4pLKDoGQg9B73Q5rKPPsQ4kzkcDk6Jd'),
@@ -20,6 +22,18 @@ const MAINNET_PROGRAM_IDS = {
20
22
  ESCROW: null,
21
23
  };
22
24
 
25
+ const MAINNET_PROGRAM_IDS = new Proxy({}, {
26
+ get() {
27
+ throw new Error(LEGACY_V2_MAINNET_FENCE_MESSAGE);
28
+ },
29
+ ownKeys() {
30
+ throw new Error(LEGACY_V2_MAINNET_FENCE_MESSAGE);
31
+ },
32
+ getOwnPropertyDescriptor() {
33
+ throw new Error(LEGACY_V2_MAINNET_FENCE_MESSAGE);
34
+ },
35
+ });
36
+
23
37
  const MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
24
38
  const DEVNET_RPC = 'https://api.devnet.solana.com';
25
39
 
@@ -37,13 +51,21 @@ const ESCROW_SEED = 'escrow';
37
51
  /**
38
52
  * Get program IDs for a given network.
39
53
  * @param {'mainnet'|'devnet'} network
54
+ * @param {object} [opts]
55
+ * @param {boolean} [opts.allowLegacyV2Mainnet=false]
40
56
  * @returns {object} Program ID map
41
57
  */
42
- function getProgramIds(network = 'devnet') {
58
+ function getProgramIds(network = 'devnet', opts = {}) {
43
59
  if (network !== 'devnet' && network !== 'mainnet') {
44
60
  throw new Error('Invalid network: expected devnet or mainnet');
45
61
  }
46
- return network === 'mainnet' ? MAINNET_PROGRAM_IDS : DEVNET_PROGRAM_IDS;
62
+ if (network === 'mainnet') {
63
+ if (opts.allowLegacyV2Mainnet !== true) {
64
+ throw new Error(LEGACY_V2_MAINNET_FENCE_MESSAGE);
65
+ }
66
+ return LEGACY_V2_MAINNET_PROGRAM_IDS;
67
+ }
68
+ return DEVNET_PROGRAM_IDS;
47
69
  }
48
70
 
49
71
  /**
@@ -61,6 +83,7 @@ function getRpcUrl(network = 'devnet') {
61
83
  module.exports = {
62
84
  DEVNET_PROGRAM_IDS,
63
85
  MAINNET_PROGRAM_IDS,
86
+ LEGACY_V2_MAINNET_FENCE_MESSAGE,
64
87
  getProgramIds,
65
88
  getRpcUrl,
66
89
  MAINNET_RPC,
package/src/index.d.ts CHANGED
@@ -467,6 +467,7 @@ export interface SATSDKOptions {
467
467
  network?: Network;
468
468
  rpcUrl?: string;
469
469
  commitment?: 'processed' | 'confirmed' | 'finalized';
470
+ allowLegacyV2Mainnet?: boolean;
470
471
  }
471
472
 
472
473
  export interface V2Identity {
package/src/index.js CHANGED
@@ -46,10 +46,15 @@ class SATPSDK {
46
46
  */
47
47
  constructor(opts = {}) {
48
48
  this.network = opts.network || 'devnet';
49
+ if (this.network === 'mainnet' && opts.allowLegacyV2Mainnet !== true) {
50
+ throw new Error('Legacy SATPSDK V2 mainnet access is fenced; use SATPV3SDK for V3 or pass allowLegacyV2Mainnet: true for explicit read-only legacy V2 access');
51
+ }
49
52
  this.rpcUrl = opts.rpcUrl || getRpcUrl(this.network);
50
53
  this.commitment = opts.commitment || 'confirmed';
51
54
  this.connection = new Connection(this.rpcUrl, this.commitment);
52
- this.programIds = getProgramIds(this.network);
55
+ this.programIds = getProgramIds(this.network, {
56
+ allowLegacyV2Mainnet: opts.allowLegacyV2Mainnet === true,
57
+ });
53
58
  }
54
59
 
55
60
  // ─── Identity ──────────────────────────────────────────
@@ -822,71 +827,6 @@ function createSATPClient(opts = {}) {
822
827
  const borshReader = require('./borsh-reader');
823
828
 
824
829
 
825
- // Fixed genesis deserializer — matches actual on-chain struct (no isActive field)
826
- function _deserializeGenesisFixed(data) {
827
- if (!data || data.length < 8) return null;
828
- try {
829
- const { PublicKey } = require('@solana/web3.js');
830
- let offset = 8; // skip discriminator
831
- const agentIdHashBytes = data.slice(offset, offset + 32); offset += 32;
832
- const readString = () => {
833
- const len = data.readUInt32LE(offset); offset += 4;
834
- const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
835
- return str;
836
- };
837
- const readVecString = () => {
838
- const count = data.readUInt32LE(offset); offset += 4;
839
- const arr = [];
840
- for (let i = 0; i < count; i++) arr.push(readString());
841
- return arr;
842
- };
843
- const agentName = readString();
844
- const description = readString();
845
- const category = readString();
846
- const capabilities = readVecString();
847
- const metadataUri = readString();
848
- const faceImage = readString();
849
- const faceMint = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
850
- const faceBurnTx = readString();
851
- const genesisRecord = Number(data.readBigInt64LE(offset)); offset += 8;
852
- // NOTE: No isActive field in deployed program (SDK bug — has phantom isActive)
853
- const authority = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
854
- const hasPending = data[offset]; offset += 1;
855
- let pendingAuthority = null;
856
- if (hasPending === 1) {
857
- pendingAuthority = new PublicKey(data.slice(offset, offset + 32)).toBase58();
858
- offset += 32;
859
- }
860
- const reputationScore = Number(data.readBigUInt64LE(offset)); offset += 8;
861
- const verificationLevel = data[offset]; offset += 1;
862
- const reputationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
863
- const verificationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
864
- const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
865
- const updatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
866
- const bump = data[offset]; offset += 1;
867
- return {
868
- agentIdHash: Array.from(agentIdHashBytes),
869
- agentName, description, category, capabilities, metadataUri, faceImage,
870
- faceMint: faceMint.toBase58(),
871
- faceBurnTx,
872
- genesisRecord,
873
- isBorn: genesisRecord > 0,
874
- authority: authority.toBase58(),
875
- pendingAuthority,
876
- reputationScore,
877
- verificationLevel,
878
- verificationLabel: ['Unverified','Registered','Verified','Established','Trusted','Sovereign'][verificationLevel] || 'Unknown',
879
- reputationPct: (reputationScore / 10000).toFixed(2),
880
- reputationUpdatedAt, verificationUpdatedAt,
881
- createdAt: createdAt > 0 ? new Date(createdAt * 1000).toISOString() : null,
882
- updatedAt: updatedAt > 0 ? new Date(updatedAt * 1000).toISOString() : null,
883
- bump,
884
- };
885
- } catch (e) {
886
- return { error: e.message, raw: data.toString('hex').slice(0, 200) };
887
- }
888
- }
889
-
890
830
  module.exports = {
891
831
  // V2 SDK (backward compatible — legacy, kept for escrow V2 / old paths)
892
832
  SATPSDK,
@@ -981,10 +921,8 @@ module.exports = {
981
921
  evaluateRuntimePolicy,
982
922
 
983
923
  // V3 Deserialization (local extracted scaffold)
984
- // NOTE: v3sdk.deserializeGenesis has isActive field mismatch with deployed program
985
- // Using corrected manual parser until SDK v3.6+ fixes struct alignment
986
- deserializeGenesis: _deserializeGenesisFixed,
987
- deserializeGenesisRecord: _deserializeGenesisFixed, // alias for old name
924
+ deserializeGenesis: v3Borsh.deserializeGenesisRecord,
925
+ deserializeGenesisRecord: v3Borsh.deserializeGenesisRecord,
988
926
  deserializeLinkedWallet: v3Borsh.deserializeLinkedWallet,
989
927
  deserializeMintTracker: v3Borsh.deserializeMintTracker,
990
928
  deserializeNameRegistry: v3Borsh.deserializeNameRegistry,
@@ -36,6 +36,7 @@ const DEFAULT_POLICY = Object.freeze({
36
36
 
37
37
  const RUNTIME_POLICY_AUDIT_TRACE_SCHEMA_VERSION = 'satp.runtimePolicyAuditTrace.v1';
38
38
  const RUNTIME_POLICY_HOST_ACTION_DESCRIPTOR_SCHEMA_VERSION = 'satp.runtimePolicyHostActionDescriptor.v1';
39
+ const TRUST_SCORE_GATE_TYPES = new Set(['agentfolio_trust_gate', 'host_trust_gate']);
39
40
 
40
41
  function createRuntimePolicyAdapter(config = {}) {
41
42
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
@@ -361,7 +362,7 @@ function firstDefined(...values) {
361
362
 
362
363
  function defaultResourceForAction(type, action) {
363
364
  if (type === 'mcp_protected_tool') return 'mcp://protected/tool';
364
- if (type === 'agentfolio_trust_gate') {
365
+ if (TRUST_SCORE_GATE_TYPES.has(type)) {
365
366
  if (action.profileId) {
366
367
  return `https://agentfolio.bot/api/profile/${encodeURIComponent(String(action.profileId))}/trust-score`;
367
368
  }
@@ -373,27 +374,27 @@ function defaultResourceForAction(type, action) {
373
374
 
374
375
  function defaultOperationForAction(type) {
375
376
  if (type === 'mcp_protected_tool') return 'invoke';
376
- if (type === 'agentfolio_trust_gate') return 'trust-score-read';
377
+ if (TRUST_SCORE_GATE_TYPES.has(type)) return 'trust-score-read';
377
378
  if (type === 'x402_endpoint') return 'lookup';
378
379
  return null;
379
380
  }
380
381
 
381
382
  function defaultCapabilityForAction(type) {
382
- if (type === 'agentfolio_trust_gate') return 'agentfolio:trust-read';
383
+ if (TRUST_SCORE_GATE_TYPES.has(type)) return 'agentfolio:trust-read';
383
384
  return null;
384
385
  }
385
386
 
386
387
  function defaultMinimumTrustScoreForAction(type) {
387
- if (type === 'agentfolio_trust_gate') return DEFAULT_POLICY.minimumTrustScore;
388
+ if (TRUST_SCORE_GATE_TYPES.has(type)) return DEFAULT_POLICY.minimumTrustScore;
388
389
  return null;
389
390
  }
390
391
 
391
392
  function defaultAllowDegradedForAction(type) {
392
- return type === 'agentfolio_trust_gate';
393
+ return TRUST_SCORE_GATE_TYPES.has(type);
393
394
  }
394
395
 
395
396
  function defaultRequiresFreshEvidenceForAction(type) {
396
- return type === 'mcp_protected_tool' || type === 'agentfolio_trust_gate';
397
+ return type === 'mcp_protected_tool' || TRUST_SCORE_GATE_TYPES.has(type);
397
398
  }
398
399
 
399
400
  function defaultCostUsdForAction(type) {
package/src/v3-pda.js CHANGED
@@ -15,7 +15,7 @@ const V3_DEVNET_PROGRAM_IDS = {
15
15
  };
16
16
 
17
17
  // Mainnet registry source: Anchor.toml [programs.mainnet], checked by
18
- // scripts/verify-v3-program-sources.mjs and the D1 authority packet.
18
+ // scripts/verify-v3-program-sources.mjs and docs/v3-program-source-verification.md.
19
19
  // docs/mainnet-authority-decision-packet-6c8a5545.md remains the owner-gating
20
20
  // packet for deploy/key/publish actions. Keep V3 separate from legacy V2
21
21
  // MAINNET_PROGRAM_IDS in constants.js; do not fall back to V2 program IDs.
package/src/v3-sdk.d.ts CHANGED
@@ -48,7 +48,9 @@ export interface GenesisRecord {
48
48
  metadataUri: string;
49
49
  reputationScore: number;
50
50
  verificationLevel: number;
51
- isActive: boolean;
51
+ isActive: boolean | null;
52
+ layout?: 'idl-with-is-active' | 'deployed-no-is-active';
53
+ hasIsActiveField?: boolean;
52
54
  createdAt: number;
53
55
  updatedAt: number;
54
56
  pendingAuthority: string | null;
package/src/v3-sdk.js CHANGED
@@ -26,6 +26,7 @@ const {
26
26
  SPL_TOKEN_PROGRAM_ID,
27
27
  ASSOCIATED_TOKEN_PROGRAM_ID,
28
28
  } = require('./v3-pda');
29
+ const { deserializeGenesisRecord } = require('./borsh-reader');
29
30
 
30
31
  const DEVNET_RPC = 'https://api.devnet.solana.com';
31
32
  const MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
@@ -1960,78 +1961,9 @@ class SATPV3SDK {
1960
1961
  if (!acct) return null;
1961
1962
 
1962
1963
  try {
1963
- const data = acct.data.slice(8); // skip Anchor discriminator
1964
- let offset = 0;
1965
-
1966
- const agentIdHash = data.slice(offset, offset + 32); offset += 32;
1967
-
1968
- // Read strings
1969
- const readString = () => {
1970
- const len = data.readUInt32LE(offset); offset += 4;
1971
- const str = data.slice(offset, offset + len).toString('utf8'); offset += len;
1972
- return str;
1973
- };
1974
-
1975
- const readVecString = () => {
1976
- const count = data.readUInt32LE(offset); offset += 4;
1977
- const arr = [];
1978
- for (let i = 0; i < count; i++) arr.push(readString());
1979
- return arr;
1980
- };
1981
-
1982
- const agentName = readString();
1983
- const description = readString();
1984
- const category = readString();
1985
- const capabilities = readVecString();
1986
- const metadataUri = readString();
1987
- const faceImage = readString();
1988
- const faceMint = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
1989
- const faceBurnTx = readString();
1990
- const genesisRecord = Number(data.readBigInt64LE(offset)); offset += 8;
1991
- const isActive = data[offset] === 1; offset += 1;
1992
- const authority = new PublicKey(data.slice(offset, offset + 32)); offset += 32;
1993
-
1994
- // Option<Pubkey> — Borsh: 0x00 = None (1 byte only), 0x01 + 32 bytes = Some
1995
- const hasPending = data[offset] === 1; offset += 1;
1996
- let pendingAuthority = null;
1997
- if (hasPending) {
1998
- pendingAuthority = new PublicKey(data.slice(offset, offset + 32)).toBase58();
1999
- offset += 32;
2000
- }
2001
-
2002
- const reputationScore = Number(data.readBigUInt64LE(offset)); offset += 8;
2003
- const verificationLevel = data[offset]; offset += 1;
2004
- const reputationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
2005
- const verificationUpdatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
2006
- const createdAt = Number(data.readBigInt64LE(offset)); offset += 8;
2007
- const updatedAt = Number(data.readBigInt64LE(offset)); offset += 8;
2008
- const bump = data[offset]; offset += 1;
2009
-
2010
- const isBorn = genesisRecord !== 0;
2011
-
2012
1964
  return {
1965
+ ...deserializeGenesisRecord(acct.data),
2013
1966
  pda: pda.toBase58(),
2014
- agentIdHash: Buffer.from(agentIdHash).toString('hex'),
2015
- agentName,
2016
- description,
2017
- category,
2018
- capabilities,
2019
- metadataUri,
2020
- faceImage: faceImage || null,
2021
- faceMint: faceMint.equals(PublicKey.default) ? null : faceMint.toBase58(),
2022
- faceBurnTx: faceBurnTx || null,
2023
- genesisRecord,
2024
- isBorn,
2025
- isActive,
2026
- authority: authority.toBase58(),
2027
- pendingAuthority,
2028
- reputationScore,
2029
- verificationLevel,
2030
- reputationUpdatedAt,
2031
- verificationUpdatedAt,
2032
- createdAt,
2033
- updatedAt,
2034
- bump,
2035
1967
  };
2036
1968
  } catch (e) {
2037
1969
  return { pda: pda.toBase58(), raw: acct.data.toString('hex'), error: e.message };