@cascade-fyi/sati-agent0-sdk 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1452 @@
1
+ import { ContentType, ContentType as ContentType$1, MAX_SINGLE_SIGNATURE_CONTENT_SIZE, Outcome, Outcome as Outcome$1, SATI_PROGRAM_ADDRESS, Sati, TOKEN_2022_PROGRAM_ADDRESS, buildCounterpartyMessage, buildRegistrationFile, buildRegistrationFile as buildRegistrationFile$1, createPinataUploader, fetchRegistrationFile, fetchRegistryConfig, findAgentIndexPda, findAssociatedTokenAddress, findRegistryConfigPda, getImageUrl, getRegisterAgentInstructionAsync, handleTransactionError, loadDeployedConfig, parseFeedbackContent, serializeFeedback, stringifyRegistrationFile, zeroDataHash } from "@cascade-fyi/sati-sdk";
2
+ import { address, generateKeyPairSigner, getAddressDecoder, signBytes } from "@solana/kit";
3
+ import { getCreateAssociatedTokenIdempotentInstruction, getTransferInstruction, getUpdateTokenMetadataFieldInstruction, tokenMetadataField } from "@solana-program/token-2022";
4
+ import { EndpointCrawler, EndpointCrawler as EndpointCrawler$1, EndpointType, EndpointType as EndpointType$1, TrustModel, TrustModel as TrustModel$1 } from "agent0-sdk";
5
+
6
+ //#region src/adapters.ts
7
+ /** CAIP-2 chain references for Solana networks */
8
+ const SOLANA_CAIP2_CHAINS = {
9
+ mainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
10
+ devnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
11
+ localnet: "solana:localnet"
12
+ };
13
+ /**
14
+ * Format a SATI agent as a CAIP-2 compatible AgentId string.
15
+ *
16
+ * Format: `solana:<chainRef>:<mintAddress>`
17
+ */
18
+ function formatSatiAgentId(mint, chain) {
19
+ return `${chain}:${mint}`;
20
+ }
21
+ /**
22
+ * Parse a SATI CAIP-2 AgentId back to a mint address.
23
+ * Returns null if the agentId is not a Solana agent.
24
+ *
25
+ * Expected format: `solana:<chainRef>:<mintAddress>`
26
+ */
27
+ function parseSatiAgentId(agentId) {
28
+ if (!agentId.startsWith("solana:")) return null;
29
+ const parts = agentId.split(":");
30
+ if (parts.length !== 3) return null;
31
+ return parts[2];
32
+ }
33
+ const SATI_TO_AGENT0_TYPE = {
34
+ MCP: EndpointType$1.MCP,
35
+ A2A: EndpointType$1.A2A,
36
+ ENS: EndpointType$1.ENS,
37
+ DID: EndpointType$1.DID,
38
+ AGENTWALLET: EndpointType$1.WALLET,
39
+ WALLET: EndpointType$1.WALLET,
40
+ OASF: EndpointType$1.OASF
41
+ };
42
+ const AGENT0_TO_SATI_NAME = {
43
+ [EndpointType$1.MCP]: "MCP",
44
+ [EndpointType$1.A2A]: "A2A",
45
+ [EndpointType$1.ENS]: "ENS",
46
+ [EndpointType$1.DID]: "DID",
47
+ [EndpointType$1.WALLET]: "agentWallet",
48
+ [EndpointType$1.OASF]: "OASF"
49
+ };
50
+ /**
51
+ * Convert SATI endpoints (ERC-8004 format) to agent0 endpoint format.
52
+ */
53
+ function toAgent0Endpoints(satiEndpoints) {
54
+ return satiEndpoints.map((ep) => {
55
+ const meta = {};
56
+ if (ep.version) meta.version = ep.version;
57
+ if (ep.mcpTools?.length) meta.mcpTools = ep.mcpTools;
58
+ if (ep.mcpPrompts?.length) meta.mcpPrompts = ep.mcpPrompts;
59
+ if (ep.mcpResources?.length) meta.mcpResources = ep.mcpResources;
60
+ if (ep.a2aSkills?.length) meta.a2aSkills = ep.a2aSkills;
61
+ if (ep.skills?.length) meta.skills = ep.skills;
62
+ if (ep.domains?.length) meta.domains = ep.domains;
63
+ return {
64
+ type: SATI_TO_AGENT0_TYPE[ep.name.toUpperCase()] ?? ep.name,
65
+ value: ep.endpoint,
66
+ meta: Object.keys(meta).length > 0 ? meta : void 0
67
+ };
68
+ });
69
+ }
70
+ /**
71
+ * Convert agent0 endpoints to SATI registration file endpoint format.
72
+ */
73
+ function fromAgent0Endpoints(agent0Endpoints) {
74
+ return agent0Endpoints.map((ep) => {
75
+ const result = {
76
+ name: AGENT0_TO_SATI_NAME[ep.type] ?? ep.type,
77
+ endpoint: ep.value,
78
+ version: ep.meta?.version
79
+ };
80
+ if (ep.meta?.mcpTools) result.mcpTools = ep.meta.mcpTools;
81
+ if (ep.meta?.mcpPrompts) result.mcpPrompts = ep.meta.mcpPrompts;
82
+ if (ep.meta?.mcpResources) result.mcpResources = ep.meta.mcpResources;
83
+ if (ep.meta?.a2aSkills) result.a2aSkills = ep.meta.a2aSkills;
84
+ if (ep.meta?.skills) result.skills = ep.meta.skills;
85
+ if (ep.meta?.domains) result.domains = ep.meta.domains;
86
+ return result;
87
+ });
88
+ }
89
+ /**
90
+ * Convert a SATI AgentIdentity + optional registration file to agent0 AgentSummary.
91
+ *
92
+ * @param identity - On-chain agent identity
93
+ * @param chain - CAIP-2 chain reference (e.g. "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
94
+ * @param regFile - Off-chain registration file (optional)
95
+ * @param feedbackStats - Pre-computed feedback stats (optional, from includeFeedbackStats)
96
+ */
97
+ function toAgentSummary(identity, chain, regFile, feedbackStats) {
98
+ const endpoints = regFile?.endpoints ?? [];
99
+ const mcpEp = endpoints.find((e) => e.name.toUpperCase() === "MCP");
100
+ const a2aEp = endpoints.find((e) => e.name.toUpperCase() === "A2A");
101
+ const oasfEp = endpoints.find((e) => e.name.toUpperCase() === "OASF");
102
+ const ensEp = endpoints.find((e) => e.name.toUpperCase() === "ENS");
103
+ const didEp = endpoints.find((e) => e.name.toUpperCase() === "DID");
104
+ const walletEp = endpoints.find((e) => e.name.toUpperCase() === "AGENTWALLET" || e.name.toUpperCase() === "WALLET");
105
+ const webEp = endpoints.find((e) => e.name.toUpperCase() === "WEB");
106
+ return {
107
+ chainId: 0,
108
+ agentId: formatSatiAgentId(identity.mint, chain),
109
+ name: identity.name,
110
+ image: regFile?.image,
111
+ description: regFile?.description ?? "",
112
+ owners: [identity.owner],
113
+ operators: [],
114
+ mcp: mcpEp?.endpoint,
115
+ a2a: a2aEp?.endpoint,
116
+ web: webEp?.endpoint,
117
+ ens: ensEp?.endpoint,
118
+ did: didEp?.endpoint,
119
+ walletAddress: walletEp?.endpoint,
120
+ supportedTrusts: regFile?.supportedTrust ?? [],
121
+ mcpTools: mcpEp?.mcpTools ?? [],
122
+ mcpPrompts: mcpEp?.mcpPrompts ?? [],
123
+ mcpResources: mcpEp?.mcpResources ?? [],
124
+ a2aSkills: a2aEp?.a2aSkills ?? [],
125
+ oasfSkills: oasfEp?.skills ?? [],
126
+ oasfDomains: oasfEp?.domains ?? [],
127
+ active: regFile?.active ?? true,
128
+ x402support: regFile?.x402support ?? false,
129
+ agentURI: identity.uri,
130
+ agentURIType: identity.uri ? detectURIType(identity.uri) : void 0,
131
+ feedbackCount: feedbackStats?.count,
132
+ averageValue: feedbackStats?.averageValue,
133
+ extras: {}
134
+ };
135
+ }
136
+ /** Detect URI type from prefix. */
137
+ function detectURIType(uri) {
138
+ if (uri.startsWith("ipfs://") || uri.includes("/ipfs/")) return "ipfs";
139
+ if (uri.startsWith("ar://") || uri.includes("arweave.net")) return "arweave";
140
+ if (uri.startsWith("https://") || uri.startsWith("http://")) return "https";
141
+ return "unknown";
142
+ }
143
+ /**
144
+ * Convert a SATI registration file to agent0 RegistrationFile format.
145
+ *
146
+ * @param satiFile - SATI registration file
147
+ * @param identity - On-chain agent identity (optional)
148
+ * @param chain - CAIP-2 chain reference (required when identity is provided)
149
+ */
150
+ function toAgent0RegistrationFile(satiFile, identity, chain) {
151
+ const endpoints = toAgent0Endpoints(satiFile.endpoints ?? []);
152
+ const trustModels = (satiFile.supportedTrust ?? []).map((t) => {
153
+ if (t === "reputation") return TrustModel$1.REPUTATION;
154
+ if (t === "crypto-economic") return TrustModel$1.CRYPTO_ECONOMIC;
155
+ if (t === "tee-attestation") return TrustModel$1.TEE_ATTESTATION;
156
+ return t;
157
+ });
158
+ return {
159
+ agentId: identity && chain ? formatSatiAgentId(identity.mint, chain) : void 0,
160
+ agentURI: identity?.uri,
161
+ name: satiFile.name,
162
+ description: satiFile.description,
163
+ image: satiFile.image,
164
+ endpoints,
165
+ trustModels,
166
+ owners: identity ? [identity.owner] : [],
167
+ operators: [],
168
+ active: satiFile.active ?? true,
169
+ x402support: satiFile.x402support ?? false,
170
+ metadata: {},
171
+ updatedAt: Math.floor(Date.now() / 1e3)
172
+ };
173
+ }
174
+ /**
175
+ * Convert agent0 RegistrationFile back to SATI registration file params.
176
+ */
177
+ function fromAgent0RegistrationFile(agent0File) {
178
+ const endpoints = fromAgent0Endpoints(agent0File.endpoints);
179
+ const supportedTrust = agent0File.trustModels.map((t) => {
180
+ if (t === TrustModel$1.REPUTATION) return "reputation";
181
+ if (t === TrustModel$1.CRYPTO_ECONOMIC) return "crypto-economic";
182
+ if (t === TrustModel$1.TEE_ATTESTATION) return "tee-attestation";
183
+ return null;
184
+ }).filter((t) => t !== null);
185
+ return {
186
+ name: agent0File.name,
187
+ description: agent0File.description,
188
+ image: agent0File.image ?? "",
189
+ endpoints,
190
+ supportedTrust,
191
+ active: agent0File.active,
192
+ x402support: agent0File.x402support
193
+ };
194
+ }
195
+ /**
196
+ * Convert SATI feedback attestation data to agent0 Feedback format.
197
+ *
198
+ * SATI stores feedback as compressed attestations with:
199
+ * - outcome (always Neutral for FeedbackPublicV1)
200
+ * - content JSON with value, tag1, tag2, endpoint
201
+ *
202
+ * agent0-sdk expects the Feedback interface with value, tags, endpoint, etc.
203
+ */
204
+ function toFeedback(opts) {
205
+ const agentId = formatSatiAgentId(opts.agentMint, opts.chain);
206
+ const tags = [];
207
+ if (opts.content.tag1) tags.push(opts.content.tag1);
208
+ if (opts.content.tag2) tags.push(opts.content.tag2);
209
+ const id = [
210
+ agentId,
211
+ opts.reviewer,
212
+ opts.feedbackIndex
213
+ ];
214
+ const context = { ...opts.content.context };
215
+ if (opts.outcome !== void 0) context.outcome = opts.outcome;
216
+ return {
217
+ id,
218
+ agentId,
219
+ reviewer: opts.reviewer,
220
+ txHash: opts.txSignature,
221
+ value: opts.content.value,
222
+ tags,
223
+ endpoint: opts.content.endpoint,
224
+ text: opts.content.text,
225
+ context: Object.keys(context).length > 0 ? context : opts.content.context,
226
+ createdAt: opts.createdAt ?? Math.floor(Date.now() / 1e3),
227
+ answers: [],
228
+ isRevoked: false
229
+ };
230
+ }
231
+
232
+ //#endregion
233
+ //#region src/agent.ts
234
+ /**
235
+ * Agent0-compatible agent class backed by SATI's Solana infrastructure.
236
+ *
237
+ * Manages an in-memory registration file that can be flushed to chain
238
+ * via `registerIPFS()`.
239
+ */
240
+ var SatiAgent = class SatiAgent {
241
+ _registrationFile;
242
+ _endpointCrawler;
243
+ _identity;
244
+ _sdk;
245
+ constructor(sdk, registrationFile) {
246
+ this._sdk = sdk;
247
+ this._registrationFile = registrationFile;
248
+ this._endpointCrawler = new EndpointCrawler$1(5e3);
249
+ }
250
+ /** @internal Access the underlying SatiSDK instance. */
251
+ get sdk() {
252
+ return this._sdk;
253
+ }
254
+ /**
255
+ * Create a new agent in memory (not yet registered on-chain).
256
+ * @internal Called by SatiSDK.createAgent()
257
+ */
258
+ static create(sdk, name, description, image) {
259
+ return new SatiAgent(sdk, {
260
+ name,
261
+ description,
262
+ image,
263
+ endpoints: [],
264
+ trustModels: [TrustModel$1.REPUTATION],
265
+ owners: [],
266
+ operators: [],
267
+ active: false,
268
+ x402support: false,
269
+ metadata: {},
270
+ updatedAt: Math.floor(Date.now() / 1e3)
271
+ });
272
+ }
273
+ /**
274
+ * Reconstruct an agent from on-chain identity and registration file.
275
+ * @internal Called by SatiSDK.loadAgent()
276
+ */
277
+ static fromIdentity(sdk, identity, registrationFile) {
278
+ const agent = new SatiAgent(sdk, registrationFile);
279
+ agent._identity = identity;
280
+ agent._registrationFile.agentId = formatSatiAgentId(identity.mint, sdk.chain);
281
+ agent._registrationFile.agentURI = identity.uri;
282
+ return agent;
283
+ }
284
+ get agentId() {
285
+ return this._registrationFile.agentId;
286
+ }
287
+ get agentURI() {
288
+ return this._registrationFile.agentURI;
289
+ }
290
+ get name() {
291
+ return this._registrationFile.name;
292
+ }
293
+ get description() {
294
+ return this._registrationFile.description;
295
+ }
296
+ get image() {
297
+ return this._registrationFile.image;
298
+ }
299
+ get mcpEndpoint() {
300
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.value;
301
+ }
302
+ get a2aEndpoint() {
303
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.A2A)?.value;
304
+ }
305
+ get walletAddress() {
306
+ return this._registrationFile.walletAddress;
307
+ }
308
+ /** SATI-specific: the on-chain agent identity (available after registration). */
309
+ get identity() {
310
+ return this._identity;
311
+ }
312
+ get ensEndpoint() {
313
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.ENS)?.value;
314
+ }
315
+ get didEndpoint() {
316
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.DID)?.value;
317
+ }
318
+ get mcpTools() {
319
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.meta?.mcpTools ?? [];
320
+ }
321
+ get mcpPrompts() {
322
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.meta?.mcpPrompts ?? [];
323
+ }
324
+ get mcpResources() {
325
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.MCP)?.meta?.mcpResources ?? [];
326
+ }
327
+ get a2aSkills() {
328
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.A2A)?.meta?.a2aSkills ?? [];
329
+ }
330
+ get oasfSkills() {
331
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.OASF)?.meta?.skills ?? [];
332
+ }
333
+ get oasfDomains() {
334
+ return this._registrationFile.endpoints.find((e) => e.type === EndpointType$1.OASF)?.meta?.domains ?? [];
335
+ }
336
+ /**
337
+ * Set MCP endpoint. Auto-fetches capabilities by default.
338
+ */
339
+ async setMCP(endpoint, version = "2025-06-18", autoFetch = true) {
340
+ this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.MCP);
341
+ const meta = { version };
342
+ if (autoFetch) try {
343
+ const capabilities = await this._endpointCrawler.fetchMcpCapabilities(endpoint);
344
+ if (capabilities) {
345
+ if (capabilities.mcpTools) meta.mcpTools = capabilities.mcpTools;
346
+ if (capabilities.mcpPrompts) meta.mcpPrompts = capabilities.mcpPrompts;
347
+ if (capabilities.mcpResources) meta.mcpResources = capabilities.mcpResources;
348
+ }
349
+ } catch {}
350
+ this._registrationFile.endpoints.push({
351
+ type: EndpointType$1.MCP,
352
+ value: endpoint,
353
+ meta
354
+ });
355
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
356
+ return this;
357
+ }
358
+ /**
359
+ * Set A2A endpoint. Auto-fetches capabilities by default.
360
+ */
361
+ async setA2A(agentcard, version = "0.30", autoFetch = true) {
362
+ this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.A2A);
363
+ const meta = { version };
364
+ if (autoFetch) try {
365
+ const capabilities = await this._endpointCrawler.fetchA2aCapabilities(agentcard);
366
+ if (capabilities?.a2aSkills) meta.a2aSkills = capabilities.a2aSkills;
367
+ } catch {}
368
+ this._registrationFile.endpoints.push({
369
+ type: EndpointType$1.A2A,
370
+ value: agentcard,
371
+ meta
372
+ });
373
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
374
+ return this;
375
+ }
376
+ /**
377
+ * Set ENS endpoint.
378
+ */
379
+ setENS(name, version = "1.0") {
380
+ this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.ENS);
381
+ this._registrationFile.endpoints.push({
382
+ type: EndpointType$1.ENS,
383
+ value: name,
384
+ meta: { version }
385
+ });
386
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
387
+ return this;
388
+ }
389
+ /**
390
+ * Remove endpoint(s).
391
+ */
392
+ removeEndpoint(opts) {
393
+ if (!opts || opts.type === void 0 && opts.value === void 0) this._registrationFile.endpoints = [];
394
+ else this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => {
395
+ const typeMatches = opts.type === void 0 || ep.type === opts.type;
396
+ const valueMatches = opts.value === void 0 || ep.value === opts.value;
397
+ return !(typeMatches && valueMatches);
398
+ });
399
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
400
+ return this;
401
+ }
402
+ /**
403
+ * Remove all endpoints (alias for removeEndpoint() with no args).
404
+ */
405
+ removeEndpoints() {
406
+ return this.removeEndpoint();
407
+ }
408
+ /**
409
+ * Get wallet address from the registration file.
410
+ */
411
+ getWallet() {
412
+ return this._registrationFile.walletAddress;
413
+ }
414
+ /**
415
+ * Set wallet address and add a WALLET endpoint.
416
+ * In-memory only - call registerIPFS() or registerHTTP() to persist on-chain.
417
+ */
418
+ setWallet(addr) {
419
+ this._registrationFile.walletAddress = addr;
420
+ this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.WALLET);
421
+ this._registrationFile.endpoints.push({
422
+ type: EndpointType$1.WALLET,
423
+ value: addr
424
+ });
425
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
426
+ return this;
427
+ }
428
+ /**
429
+ * Remove wallet address and wallet endpoint.
430
+ */
431
+ unsetWallet() {
432
+ this._registrationFile.walletAddress = void 0;
433
+ this._registrationFile.endpoints = this._registrationFile.endpoints.filter((ep) => ep.type !== EndpointType$1.WALLET);
434
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
435
+ return this;
436
+ }
437
+ addSkill(slug) {
438
+ const oasfEp = this._getOrCreateOasfEndpoint();
439
+ if (!oasfEp.meta) oasfEp.meta = {};
440
+ if (!Array.isArray(oasfEp.meta.skills)) oasfEp.meta.skills = [];
441
+ const skills = oasfEp.meta.skills;
442
+ if (!skills.includes(slug)) skills.push(slug);
443
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
444
+ return this;
445
+ }
446
+ removeSkill(slug) {
447
+ const oasfEp = this._registrationFile.endpoints.find((ep) => ep.type === EndpointType$1.OASF);
448
+ if (oasfEp?.meta) {
449
+ const skills = oasfEp.meta.skills;
450
+ if (Array.isArray(skills)) {
451
+ const idx = skills.indexOf(slug);
452
+ if (idx !== -1) skills.splice(idx, 1);
453
+ }
454
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
455
+ }
456
+ return this;
457
+ }
458
+ addDomain(slug) {
459
+ const oasfEp = this._getOrCreateOasfEndpoint();
460
+ if (!oasfEp.meta) oasfEp.meta = {};
461
+ if (!Array.isArray(oasfEp.meta.domains)) oasfEp.meta.domains = [];
462
+ const domains = oasfEp.meta.domains;
463
+ if (!domains.includes(slug)) domains.push(slug);
464
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
465
+ return this;
466
+ }
467
+ removeDomain(slug) {
468
+ const oasfEp = this._registrationFile.endpoints.find((ep) => ep.type === EndpointType$1.OASF);
469
+ if (oasfEp?.meta) {
470
+ const domains = oasfEp.meta.domains;
471
+ if (Array.isArray(domains)) {
472
+ const idx = domains.indexOf(slug);
473
+ if (idx !== -1) domains.splice(idx, 1);
474
+ }
475
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
476
+ }
477
+ return this;
478
+ }
479
+ setActive(active) {
480
+ this._registrationFile.active = active;
481
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
482
+ return this;
483
+ }
484
+ setX402Support(x402Support) {
485
+ this._registrationFile.x402support = x402Support;
486
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
487
+ return this;
488
+ }
489
+ setTrust(reputation = false, cryptoEconomic = false, teeAttestation = false) {
490
+ const trustModels = [];
491
+ if (reputation) trustModels.push(TrustModel$1.REPUTATION);
492
+ if (cryptoEconomic) trustModels.push(TrustModel$1.CRYPTO_ECONOMIC);
493
+ if (teeAttestation) trustModels.push(TrustModel$1.TEE_ATTESTATION);
494
+ this._registrationFile.trustModels = trustModels;
495
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
496
+ return this;
497
+ }
498
+ setMetadata(kv) {
499
+ Object.assign(this._registrationFile.metadata, kv);
500
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
501
+ return this;
502
+ }
503
+ getMetadata() {
504
+ return { ...this._registrationFile.metadata };
505
+ }
506
+ delMetadata(key) {
507
+ if (key in this._registrationFile.metadata) {
508
+ delete this._registrationFile.metadata[key];
509
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
510
+ }
511
+ return this;
512
+ }
513
+ /**
514
+ * Update basic agent information.
515
+ */
516
+ updateInfo(name, description, image) {
517
+ if (name !== void 0) this._registrationFile.name = name;
518
+ if (description !== void 0) this._registrationFile.description = description;
519
+ if (image !== void 0) this._registrationFile.image = image;
520
+ this._registrationFile.updatedAt = Math.floor(Date.now() / 1e3);
521
+ return this;
522
+ }
523
+ /**
524
+ * Get the current in-memory registration file.
525
+ */
526
+ getRegistrationFile() {
527
+ return this._registrationFile;
528
+ }
529
+ /**
530
+ * Register agent on-chain with IPFS.
531
+ *
532
+ * Converts the in-memory agent0 registration file to SATI format,
533
+ * uploads to IPFS via Pinata, then mints the agent NFT on Solana.
534
+ *
535
+ * @throws Error if image URL is not set
536
+ * @throws Error if pinataJwt is not configured
537
+ * @throws Error if SDK is in read-only mode
538
+ */
539
+ async registerIPFS() {
540
+ if (!this._registrationFile.image) throw new Error("Image URL is required for registration. Set it via updateInfo() or createAgent().");
541
+ const pinataJwt = this._sdk.config.pinataJwt;
542
+ if (!pinataJwt) throw new Error("pinataJwt is required for IPFS uploads. Set it in SatiSDKConfig.");
543
+ const satiParams = fromAgent0RegistrationFile(this._registrationFile);
544
+ const uploader = createPinataUploader(pinataJwt);
545
+ const uri = await this._sdk.sati.uploadRegistrationFile(satiParams, uploader);
546
+ return this._registerOnChain(uri);
547
+ }
548
+ /**
549
+ * Register agent on-chain with an HTTP URI.
550
+ *
551
+ * Same as registerIPFS but takes a URI directly instead of uploading to IPFS.
552
+ * Use this when you already have a hosted registration file.
553
+ *
554
+ * @throws Error if SDK is in read-only mode
555
+ */
556
+ async registerHTTP(agentUri) {
557
+ return this._registerOnChain(agentUri);
558
+ }
559
+ /**
560
+ * Update the agent's on-chain URI (metadata pointer).
561
+ *
562
+ * @throws Error if agent is not registered on-chain
563
+ * @throws Error if SDK is in read-only mode
564
+ */
565
+ async setAgentURI(agentURI) {
566
+ if (!this._identity) throw new Error("Agent is not registered on-chain. Call registerIPFS() or registerHTTP() first.");
567
+ const access = this._requireWriteAccess();
568
+ if (access.type === "keypair") {
569
+ const result = await this._sdk.sati.updateAgentMetadata({
570
+ payer: access.signer,
571
+ owner: access.signer,
572
+ mint: address(this._identity.mint),
573
+ updates: { uri: agentURI }
574
+ });
575
+ this._identity.uri = agentURI;
576
+ this._registrationFile.agentURI = agentURI;
577
+ return result;
578
+ }
579
+ const updateIx = getUpdateTokenMetadataFieldInstruction({
580
+ metadata: this._identity.mint,
581
+ updateAuthority: { address: address(access.sender.address) },
582
+ field: tokenMetadataField("Uri"),
583
+ value: agentURI
584
+ });
585
+ const signature = await access.sender.signAndSend([updateIx]);
586
+ this._identity.uri = agentURI;
587
+ this._registrationFile.agentURI = agentURI;
588
+ return { signature };
589
+ }
590
+ /**
591
+ * Transfer agent ownership to a new Solana address.
592
+ *
593
+ * @throws Error if agent is not registered on-chain
594
+ * @throws Error if SDK is in read-only mode
595
+ */
596
+ async transfer(newOwner) {
597
+ if (!this._identity) throw new Error("Agent is not registered on-chain. Call registerIPFS() or registerHTTP() first.");
598
+ const access = this._requireWriteAccess();
599
+ if (access.type === "keypair") return this._sdk.sati.transferAgent({
600
+ payer: access.signer,
601
+ owner: access.signer,
602
+ mint: this._identity.mint,
603
+ newOwner: address(newOwner)
604
+ });
605
+ const ownerAddr = address(access.sender.address);
606
+ const [sourceAta] = await findAssociatedTokenAddress(this._identity.mint, ownerAddr);
607
+ const [destAta] = await findAssociatedTokenAddress(this._identity.mint, address(newOwner));
608
+ const createAtaIx = getCreateAssociatedTokenIdempotentInstruction({
609
+ payer: { address: ownerAddr },
610
+ owner: address(newOwner),
611
+ mint: this._identity.mint,
612
+ ata: destAta,
613
+ tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
614
+ });
615
+ const transferIx = getTransferInstruction({
616
+ source: sourceAta,
617
+ destination: destAta,
618
+ authority: { address: ownerAddr },
619
+ amount: 1n
620
+ });
621
+ return { signature: await access.sender.signAndSend([createAtaIx, transferIx]) };
622
+ }
623
+ _requireWriteAccess() {
624
+ if (this._sdk.config.signer) return {
625
+ type: "keypair",
626
+ signer: this._sdk.config.signer
627
+ };
628
+ if (this._sdk.config.transactionSender) return {
629
+ type: "sender",
630
+ sender: this._sdk.config.transactionSender
631
+ };
632
+ throw new Error("This operation requires a signer or transactionSender. Initialize SatiSDK with one for write operations.");
633
+ }
634
+ /**
635
+ * Shared registration logic for registerIPFS/registerHTTP.
636
+ * Supports both keypair and sender paths.
637
+ */
638
+ async _registerOnChain(uri) {
639
+ const access = this._requireWriteAccess();
640
+ if (access.type === "keypair") {
641
+ const result = await this._sdk.sati.registerAgent({
642
+ payer: access.signer,
643
+ name: this._registrationFile.name,
644
+ uri
645
+ });
646
+ const agentId = this._storeIdentity(result.mint, access.signer.address, uri, result.memberNumber);
647
+ return {
648
+ signature: result.signature,
649
+ agentId
650
+ };
651
+ }
652
+ const agentMint = await generateKeyPairSigner();
653
+ const rpc = this._sdk.sati.getRpc();
654
+ const [registryConfigAddress] = await findRegistryConfigPda();
655
+ const registryConfig = await fetchRegistryConfig(rpc, registryConfigAddress);
656
+ const groupMint = registryConfig.data.groupMint;
657
+ const ownerAddress = address(access.sender.address);
658
+ const [agentTokenAccount] = await findAssociatedTokenAddress(agentMint.address, ownerAddress);
659
+ const [agentIndex] = await findAgentIndexPda(registryConfig.data.totalAgents + 1n);
660
+ const registerIx = await getRegisterAgentInstructionAsync({
661
+ payer: { address: ownerAddress },
662
+ owner: ownerAddress,
663
+ groupMint,
664
+ agentMint,
665
+ agentTokenAccount,
666
+ agentIndex,
667
+ name: this._registrationFile.name,
668
+ symbol: "",
669
+ uri,
670
+ additionalMetadata: null,
671
+ nonTransferable: false
672
+ });
673
+ const signature = await access.sender.signAndSend([registerIx], [agentMint]);
674
+ const finalMemberNumber = (await fetchRegistryConfig(rpc, registryConfigAddress)).data.totalAgents;
675
+ return {
676
+ signature,
677
+ agentId: this._storeIdentity(agentMint.address, ownerAddress, uri, finalMemberNumber)
678
+ };
679
+ }
680
+ _storeIdentity(mint, owner, uri, memberNumber) {
681
+ this._identity = {
682
+ mint,
683
+ owner,
684
+ name: this._registrationFile.name,
685
+ uri,
686
+ memberNumber,
687
+ additionalMetadata: {},
688
+ nonTransferable: false
689
+ };
690
+ const agentId = formatSatiAgentId(mint, this._sdk.chain);
691
+ this._registrationFile.agentId = agentId;
692
+ this._registrationFile.agentURI = uri;
693
+ return agentId;
694
+ }
695
+ _getOrCreateOasfEndpoint() {
696
+ const existing = this._registrationFile.endpoints.find((ep) => ep.type === EndpointType$1.OASF);
697
+ if (existing) return existing;
698
+ const oasfEndpoint = {
699
+ type: EndpointType$1.OASF,
700
+ value: "https://github.com/agntcy/oasf/",
701
+ meta: {
702
+ version: "v0.8.0",
703
+ skills: [],
704
+ domains: []
705
+ }
706
+ };
707
+ this._registrationFile.endpoints.push(oasfEndpoint);
708
+ return oasfEndpoint;
709
+ }
710
+ };
711
+
712
+ //#endregion
713
+ //#region src/sdk.ts
714
+ /**
715
+ * Main SDK class for the SATI Agent0 adapter.
716
+ *
717
+ * Provides agent0-sdk compatible method signatures backed by SATI's
718
+ * Solana infrastructure. Drop-in replacement for agent0-sdk's SDK class
719
+ * when working with SATI agents.
720
+ */
721
+ /**
722
+ * SATI Agent0 SDK - agent0-sdk compatible interface backed by Solana.
723
+ *
724
+ * Method signatures match agent0-sdk's `SDK` class so that example code
725
+ * can switch between EVM and Solana by changing only the import and config.
726
+ *
727
+ * @example
728
+ * ```typescript
729
+ * import { SatiSDK } from "@cascade-fyi/sati-agent0-sdk";
730
+ * import { generateKeyPairSigner } from "@solana/kit";
731
+ *
732
+ * const signer = await generateKeyPairSigner();
733
+ * const sdk = new SatiSDK({
734
+ * network: "devnet",
735
+ * signer,
736
+ * });
737
+ *
738
+ * const agent = sdk.createAgent("MyAgent", "An AI assistant");
739
+ * ```
740
+ */
741
+ var SatiSDK = class {
742
+ _config;
743
+ _sati;
744
+ _sasConfig;
745
+ _chain;
746
+ constructor(config) {
747
+ this._config = config;
748
+ this._sati = new Sati({
749
+ network: config.network,
750
+ rpcUrl: config.rpcUrl
751
+ });
752
+ this._sasConfig = loadDeployedConfig(config.network);
753
+ this._chain = SOLANA_CAIP2_CHAINS[config.network] ?? `solana:${config.network}`;
754
+ }
755
+ /** Get the SATI network. */
756
+ get network() {
757
+ return this._config.network;
758
+ }
759
+ /**
760
+ * Chain ID (0 for Solana - not an EVM chain).
761
+ * Use `chain` property for the CAIP-2 identifier.
762
+ */
763
+ get chainId() {
764
+ return 0;
765
+ }
766
+ /** Get the CAIP-2 chain reference. */
767
+ get chain() {
768
+ return this._chain;
769
+ }
770
+ /** True if SDK has no signer or transaction sender configured (read-only mode). */
771
+ get isReadOnly() {
772
+ return !this._config.signer && !this._config.transactionSender;
773
+ }
774
+ /**
775
+ * Access the underlying SATI client for SATI-specific operations
776
+ * not covered by the agent0-sdk interface (e.g. validations, compression).
777
+ */
778
+ get sati() {
779
+ return this._sati;
780
+ }
781
+ /** @internal Access the SAS schema config. */
782
+ get sasConfig() {
783
+ return this._sasConfig;
784
+ }
785
+ /**
786
+ * Create a new agent (off-chain object in memory).
787
+ * Call `agent.registerIPFS()` to register on-chain.
788
+ */
789
+ createAgent(name, description, image) {
790
+ return SatiAgent.create(this, name, description, image);
791
+ }
792
+ /**
793
+ * Load an existing agent by agent0-compatible AgentId.
794
+ */
795
+ async loadAgent(agentId) {
796
+ const identity = await this._resolveIdentity(agentId);
797
+ const agent0RegFile = toAgent0RegistrationFile(await fetchRegistrationFile(identity.uri) ?? buildRegistrationFile$1({
798
+ name: identity.name,
799
+ description: "",
800
+ image: "https://placehold.co/256"
801
+ }), identity, this._chain);
802
+ return SatiAgent.fromIdentity(this, identity, agent0RegFile);
803
+ }
804
+ /**
805
+ * Get agent summary (read-only).
806
+ */
807
+ async getAgent(agentId) {
808
+ const mint = parseSatiAgentId(agentId);
809
+ if (mint === null) return null;
810
+ const identity = await this._sati.loadAgent(address(mint));
811
+ if (!identity) return null;
812
+ const regFile = await fetchRegistrationFile(identity.uri);
813
+ return toAgentSummary(identity, this._chain, regFile);
814
+ }
815
+ /**
816
+ * Search agents with filters.
817
+ *
818
+ * Fetches agents from on-chain registry and applies client-side filtering
819
+ * against on-chain data and registration files.
820
+ *
821
+ * Supports most agent0-sdk SearchFilters. Unsupported filters (require indexer):
822
+ * `keyword`, `registeredAtFrom/To`, `updatedAtFrom/To`, `hasMetadataKey`,
823
+ * `metadataValue`, `operators`.
824
+ *
825
+ * Pass `includeFeedbackStats: true` in options to populate `feedbackCount` and
826
+ * `averageValue` on results (slower - extra RPC calls per agent).
827
+ * Automatically enabled when `filters.feedback` is set.
828
+ */
829
+ async searchAgents(filters, options) {
830
+ let agents;
831
+ if (filters?.owners?.length) agents = (await Promise.all(filters.owners.map((o) => this._sati.listAgentsByOwner(address(o))))).flat();
832
+ else agents = await this._sati.listAllAgents({ limit: 1e3 });
833
+ if (filters?.agentIds?.length) {
834
+ const mintSet = new Set(filters.agentIds.map((id) => parseSatiAgentId(id)).filter((m) => m !== null));
835
+ agents = agents.filter((a) => mintSet.has(a.mint));
836
+ }
837
+ if (filters?.name) {
838
+ const lower = filters.name.toLowerCase();
839
+ agents = agents.filter((a) => a.name.toLowerCase().includes(lower));
840
+ }
841
+ const regFiles = !!(filters?.description || filters?.active !== void 0 || filters?.hasMCP || filters?.hasA2A || filters?.hasOASF || filters?.hasWeb || filters?.hasEndpoints !== void 0 || filters?.hasRegistrationFile !== void 0 || filters?.x402support !== void 0 || filters?.supportedTrust?.length || filters?.mcpTools?.length || filters?.mcpPrompts?.length || filters?.mcpResources?.length || filters?.a2aSkills?.length || filters?.oasfSkills?.length || filters?.oasfDomains?.length || filters?.walletAddress || filters?.webContains || filters?.mcpContains || filters?.a2aContains || filters?.ensContains || filters?.didContains) ? await Promise.all(agents.map((a) => fetchRegistrationFile(a.uri))) : agents.map(() => null);
842
+ const wantFeedbackStats = !!(options?.includeFeedbackStats || filters?.feedback);
843
+ let feedbackStatsMap = null;
844
+ if (wantFeedbackStats && this._sasConfig) {
845
+ feedbackStatsMap = /* @__PURE__ */ new Map();
846
+ const schema = this._sasConfig.schemas.feedbackPublic ?? this._sasConfig.schemas.feedback;
847
+ await Promise.all(agents.map(async (agent) => {
848
+ try {
849
+ const scores = (await this._sati.listFeedbacks({
850
+ sasSchema: schema,
851
+ agentMint: agent.mint
852
+ })).items.map((item) => {
853
+ if (item.data.contentType === ContentType$1.JSON && item.data.content.length > 0) return JSON.parse(new TextDecoder().decode(item.data.content))?.score;
854
+ }).filter((s) => s !== void 0);
855
+ const count = scores.length;
856
+ const averageValue = count > 0 ? scores.reduce((a, b) => a + b, 0) / count : 0;
857
+ feedbackStatsMap?.set(agent.mint, {
858
+ count,
859
+ averageValue
860
+ });
861
+ } catch {}
862
+ }));
863
+ }
864
+ const results = [];
865
+ for (let i = 0; i < agents.length; i++) {
866
+ const identity = agents[i];
867
+ const regFile = regFiles[i];
868
+ const endpoints = regFile?.endpoints ?? [];
869
+ if (filters?.hasRegistrationFile === true && !regFile) continue;
870
+ if (filters?.hasRegistrationFile === false && regFile) continue;
871
+ if (filters?.description) {
872
+ if (!regFile?.description?.toLowerCase().includes(filters.description.toLowerCase())) continue;
873
+ }
874
+ if (filters?.active !== void 0 && (regFile?.active ?? true) !== filters.active) continue;
875
+ if (filters?.x402support !== void 0 && (regFile?.x402support ?? false) !== filters.x402support) continue;
876
+ if (filters?.hasMCP && !endpoints.some((e) => e.name.toUpperCase() === "MCP")) continue;
877
+ if (filters?.hasA2A && !endpoints.some((e) => e.name.toUpperCase() === "A2A")) continue;
878
+ if (filters?.hasOASF && !endpoints.some((e) => e.name.toUpperCase() === "OASF")) continue;
879
+ if (filters?.hasWeb && !endpoints.some((e) => e.name.toUpperCase() === "WEB")) continue;
880
+ if (filters?.hasEndpoints === true && endpoints.length === 0) continue;
881
+ if (filters?.mcpContains) {
882
+ if (!endpoints.find((e) => e.name.toUpperCase() === "MCP")?.endpoint.includes(filters.mcpContains)) continue;
883
+ }
884
+ if (filters?.a2aContains) {
885
+ if (!endpoints.find((e) => e.name.toUpperCase() === "A2A")?.endpoint.includes(filters.a2aContains)) continue;
886
+ }
887
+ if (filters?.ensContains) {
888
+ if (!endpoints.find((e) => e.name.toUpperCase() === "ENS")?.endpoint.includes(filters.ensContains)) continue;
889
+ }
890
+ if (filters?.didContains) {
891
+ if (!endpoints.find((e) => e.name.toUpperCase() === "DID")?.endpoint.includes(filters.didContains)) continue;
892
+ }
893
+ if (filters?.webContains) {
894
+ if (!endpoints.find((e) => e.name.toUpperCase() === "WEB")?.endpoint.includes(filters.webContains)) continue;
895
+ }
896
+ if (filters?.walletAddress) {
897
+ if (!endpoints.find((e) => e.name.toUpperCase() === "AGENTWALLET" || e.name.toUpperCase() === "WALLET")?.endpoint.includes(filters.walletAddress)) continue;
898
+ }
899
+ if (filters?.supportedTrust?.length) {
900
+ const trusts = regFile?.supportedTrust ?? [];
901
+ if (!filters.supportedTrust.every((t) => trusts.includes(t))) continue;
902
+ }
903
+ if (filters?.mcpTools?.length) {
904
+ const tools = endpoints.find((e) => e.name.toUpperCase() === "MCP")?.mcpTools ?? [];
905
+ if (!filters.mcpTools.some((t) => tools.includes(t))) continue;
906
+ }
907
+ if (filters?.mcpPrompts?.length) {
908
+ const prompts = endpoints.find((e) => e.name.toUpperCase() === "MCP")?.mcpPrompts ?? [];
909
+ if (!filters.mcpPrompts.some((t) => prompts.includes(t))) continue;
910
+ }
911
+ if (filters?.mcpResources?.length) {
912
+ const resources = endpoints.find((e) => e.name.toUpperCase() === "MCP")?.mcpResources ?? [];
913
+ if (!filters.mcpResources.some((t) => resources.includes(t))) continue;
914
+ }
915
+ if (filters?.a2aSkills?.length) {
916
+ const skills = endpoints.find((e) => e.name.toUpperCase() === "A2A")?.a2aSkills ?? [];
917
+ if (!filters.a2aSkills.some((t) => skills.includes(t))) continue;
918
+ }
919
+ if (filters?.oasfSkills?.length) {
920
+ const skills = endpoints.find((e) => e.name.toUpperCase() === "OASF")?.skills ?? [];
921
+ if (!filters.oasfSkills.some((t) => skills.includes(t))) continue;
922
+ }
923
+ if (filters?.oasfDomains?.length) {
924
+ const domains = endpoints.find((e) => e.name.toUpperCase() === "OASF")?.domains ?? [];
925
+ if (!filters.oasfDomains.some((t) => domains.includes(t))) continue;
926
+ }
927
+ if (filters?.feedback) {
928
+ const fbStats = feedbackStatsMap?.get(identity.mint);
929
+ const fb = filters.feedback;
930
+ if (fb.hasFeedback === true && (!fbStats || fbStats.count === 0)) continue;
931
+ if (fb.hasNoFeedback === true && fbStats && fbStats.count > 0) continue;
932
+ if (fb.minValue !== void 0 && (!fbStats || fbStats.averageValue < fb.minValue)) continue;
933
+ if (fb.maxValue !== void 0 && (!fbStats || fbStats.averageValue > fb.maxValue)) continue;
934
+ if (fb.minCount !== void 0 && (!fbStats || fbStats.count < fb.minCount)) continue;
935
+ if (fb.maxCount !== void 0 && (!fbStats || fbStats.count > fb.maxCount)) continue;
936
+ }
937
+ const stats = feedbackStatsMap?.get(identity.mint) ?? null;
938
+ results.push(toAgentSummary(identity, this._chain, regFile, stats));
939
+ }
940
+ const sortFields = options?.sort;
941
+ if (sortFields?.length) results.sort((a, b) => {
942
+ for (const sortStr of sortFields) {
943
+ const [field, dir] = sortStr.split(":");
944
+ const mul = dir === "asc" ? 1 : -1;
945
+ const aVal = a[field] ?? 0;
946
+ const bVal = b[field] ?? 0;
947
+ if (aVal !== bVal) return (aVal > bVal ? 1 : -1) * mul;
948
+ }
949
+ return 0;
950
+ });
951
+ return results;
952
+ }
953
+ /**
954
+ * Transfer agent ownership to a new Solana address.
955
+ */
956
+ async transferAgent(agentId, newOwner) {
957
+ const identity = await this._resolveIdentity(agentId);
958
+ const access = this._requireWriteAccess();
959
+ if (access.type === "keypair") return this._sati.transferAgent({
960
+ payer: access.signer,
961
+ owner: access.signer,
962
+ mint: identity.mint,
963
+ newOwner: address(newOwner)
964
+ });
965
+ const ownerAddr = address(access.sender.address);
966
+ const [sourceAta] = await findAssociatedTokenAddress(identity.mint, ownerAddr);
967
+ const [destAta] = await findAssociatedTokenAddress(identity.mint, address(newOwner));
968
+ const createAtaIx = getCreateAssociatedTokenIdempotentInstruction({
969
+ payer: { address: ownerAddr },
970
+ owner: address(newOwner),
971
+ mint: identity.mint,
972
+ ata: destAta,
973
+ tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
974
+ });
975
+ const transferIx = getTransferInstruction({
976
+ source: sourceAta,
977
+ destination: destAta,
978
+ authority: { address: ownerAddr },
979
+ amount: 1n
980
+ });
981
+ return { signature: await access.sender.signAndSend([createAtaIx, transferIx]) };
982
+ }
983
+ /**
984
+ * Check if address owns the agent.
985
+ */
986
+ async isAgentOwner(agentId, addr) {
987
+ return await this.getAgentOwner(agentId) === addr;
988
+ }
989
+ /**
990
+ * Get agent owner address.
991
+ */
992
+ async getAgentOwner(agentId) {
993
+ const identity = await this._resolveIdentity(agentId);
994
+ return await this._sati.getAgentOwner(identity.mint);
995
+ }
996
+ /**
997
+ * Prepare an off-chain feedback file.
998
+ */
999
+ prepareFeedbackFile(input, extra) {
1000
+ return {
1001
+ ...input,
1002
+ ...extra
1003
+ };
1004
+ }
1005
+ /**
1006
+ * Give feedback to an agent.
1007
+ *
1008
+ * Uses FeedbackPublicV1 schema (CounterpartySigned mode).
1009
+ * Value/tags stored in content JSON.
1010
+ *
1011
+ * @param satiOptions - SATI-specific overrides (outcome, taskRef). When omitted,
1012
+ * outcome defaults to Neutral and taskRef is random.
1013
+ */
1014
+ async giveFeedback(agentId, value, tag1, tag2, endpoint, feedbackFile, satiOptions) {
1015
+ const sasConfig = this._requireSASConfig();
1016
+ const feedbackPublicSchema = sasConfig.schemas.feedbackPublic;
1017
+ if (!feedbackPublicSchema) throw new Error("FeedbackPublic schema not deployed on this network");
1018
+ const identity = await this._resolveIdentity(agentId);
1019
+ const access = this._requireWriteAccess();
1020
+ const contentObj = {};
1021
+ if (value !== void 0) contentObj.score = typeof value === "string" ? Number.parseFloat(value) : value;
1022
+ if (tag1) contentObj.tags = tag2 ? [tag1, tag2] : [tag1];
1023
+ if (endpoint) contentObj.endpoint = endpoint;
1024
+ if (feedbackFile?.text) contentObj.m = feedbackFile.text;
1025
+ const content = Object.keys(contentObj).length > 0 ? new TextEncoder().encode(JSON.stringify(contentObj)) : new Uint8Array(0);
1026
+ const contentType = content.length > 0 ? ContentType$1.JSON : ContentType$1.None;
1027
+ const taskRef = satiOptions?.taskRef ?? globalThis.crypto.getRandomValues(new Uint8Array(32));
1028
+ const outcome = satiOptions?.outcome ?? Outcome$1.Neutral;
1029
+ const numericValue = typeof value === "string" ? Number.parseFloat(value) : value;
1030
+ if (access.type === "keypair") {
1031
+ const signer = access.signer;
1032
+ const { messageBytes } = buildCounterpartyMessage({
1033
+ schemaName: "FeedbackPublic",
1034
+ data: serializeFeedback({
1035
+ taskRef,
1036
+ agentMint: identity.mint,
1037
+ counterparty: signer.address,
1038
+ dataHash: zeroDataHash(),
1039
+ outcome,
1040
+ contentType,
1041
+ content
1042
+ })
1043
+ });
1044
+ const sig = await signBytes(signer.keyPair.privateKey, messageBytes);
1045
+ const result = await this._sati.createFeedback({
1046
+ payer: signer,
1047
+ sasSchema: feedbackPublicSchema,
1048
+ taskRef,
1049
+ agentMint: identity.mint,
1050
+ counterparty: signer.address,
1051
+ dataHash: zeroDataHash(),
1052
+ outcome,
1053
+ contentType,
1054
+ content,
1055
+ agentSignature: {
1056
+ pubkey: signer.address,
1057
+ signature: new Uint8Array(sig)
1058
+ },
1059
+ counterpartyMessage: messageBytes,
1060
+ lookupTableAddress: sasConfig.lookupTable
1061
+ });
1062
+ const feedback = toFeedback({
1063
+ agentMint: identity.mint,
1064
+ chain: this._chain,
1065
+ reviewer: signer.address,
1066
+ feedbackIndex: 0,
1067
+ content: {
1068
+ value: numericValue,
1069
+ tag1,
1070
+ tag2,
1071
+ endpoint,
1072
+ text: feedbackFile?.text
1073
+ },
1074
+ txSignature: result.signature,
1075
+ outcome
1076
+ });
1077
+ return {
1078
+ signature: result.signature,
1079
+ feedback
1080
+ };
1081
+ }
1082
+ throw new Error("giveFeedback is not supported via transactionSender (browser wallet). Use prepareFeedback() to get SIWS message bytes, have the wallet sign them, then call submitPreparedFeedback() with a server-side KeyPairSigner to submit.");
1083
+ }
1084
+ /**
1085
+ * Prepare a feedback submission for browser wallet signing.
1086
+ *
1087
+ * Returns SIWS message bytes that the counterparty wallet must sign.
1088
+ * Pass the result + signature to `submitPreparedFeedback()`.
1089
+ *
1090
+ * @param agentId - Agent to give feedback to
1091
+ * @param value - Numeric feedback value
1092
+ * @param tag1 - Optional first tag
1093
+ * @param tag2 - Optional second tag
1094
+ * @param opts - Optional: endpoint, text, counterparty address (defaults to transactionSender address)
1095
+ * @param satiOptions - SATI-specific overrides (outcome, taskRef)
1096
+ */
1097
+ async prepareFeedback(agentId, value, tag1, tag2, opts, satiOptions) {
1098
+ const sasConfig = this._requireSASConfig();
1099
+ const feedbackPublicSchema = sasConfig.schemas.feedbackPublic;
1100
+ if (!feedbackPublicSchema) throw new Error("FeedbackPublic schema not deployed on this network");
1101
+ const identity = await this._resolveIdentity(agentId);
1102
+ const counterpartyAddr = opts?.counterparty ?? this._config.transactionSender?.address;
1103
+ if (!counterpartyAddr) throw new Error("counterparty address required - provide via opts.counterparty or configure transactionSender");
1104
+ const contentObj = {};
1105
+ if (value !== void 0) contentObj.score = value;
1106
+ if (tag1) contentObj.tags = tag2 ? [tag1, tag2] : [tag1];
1107
+ if (opts?.endpoint) contentObj.endpoint = opts.endpoint;
1108
+ if (opts?.text) contentObj.m = opts.text;
1109
+ const content = Object.keys(contentObj).length > 0 ? new TextEncoder().encode(JSON.stringify(contentObj)) : new Uint8Array(0);
1110
+ const contentType = content.length > 0 ? ContentType$1.JSON : ContentType$1.None;
1111
+ const taskRef = satiOptions?.taskRef ?? globalThis.crypto.getRandomValues(new Uint8Array(32));
1112
+ const outcome = satiOptions?.outcome ?? Outcome$1.Neutral;
1113
+ const { messageBytes } = buildCounterpartyMessage({
1114
+ schemaName: "FeedbackPublic",
1115
+ data: serializeFeedback({
1116
+ taskRef,
1117
+ agentMint: identity.mint,
1118
+ counterparty: address(counterpartyAddr),
1119
+ dataHash: zeroDataHash(),
1120
+ outcome,
1121
+ contentType,
1122
+ content
1123
+ })
1124
+ });
1125
+ return {
1126
+ messageBytes,
1127
+ agentMint: identity.mint,
1128
+ counterparty: address(counterpartyAddr),
1129
+ taskRef,
1130
+ dataHash: zeroDataHash(),
1131
+ outcome,
1132
+ contentType,
1133
+ content,
1134
+ sasSchema: feedbackPublicSchema,
1135
+ lookupTable: sasConfig.lookupTable,
1136
+ feedbackMeta: {
1137
+ value,
1138
+ tag1,
1139
+ tag2,
1140
+ endpoint: opts?.endpoint,
1141
+ text: opts?.text
1142
+ }
1143
+ };
1144
+ }
1145
+ /**
1146
+ * Submit a prepared feedback using a server-side KeyPairSigner.
1147
+ *
1148
+ * The signer pays gas. The counterparty's SIWS signature proves consent.
1149
+ *
1150
+ * @param prepared - Result from `prepareFeedback()`
1151
+ * @param counterpartySignature - Counterparty wallet's signature of `prepared.messageBytes`
1152
+ */
1153
+ async submitPreparedFeedback(prepared, counterpartySignature) {
1154
+ const signer = this._requireSigner();
1155
+ const result = await this._sati.createFeedback({
1156
+ payer: signer,
1157
+ sasSchema: prepared.sasSchema,
1158
+ taskRef: prepared.taskRef,
1159
+ agentMint: prepared.agentMint,
1160
+ counterparty: prepared.counterparty,
1161
+ dataHash: prepared.dataHash,
1162
+ outcome: prepared.outcome,
1163
+ contentType: prepared.contentType,
1164
+ content: prepared.content,
1165
+ agentSignature: {
1166
+ pubkey: prepared.counterparty,
1167
+ signature: new Uint8Array(counterpartySignature)
1168
+ },
1169
+ counterpartyMessage: prepared.messageBytes,
1170
+ lookupTableAddress: prepared.lookupTable
1171
+ });
1172
+ const feedback = toFeedback({
1173
+ agentMint: prepared.agentMint,
1174
+ chain: this._chain,
1175
+ reviewer: prepared.counterparty,
1176
+ feedbackIndex: 0,
1177
+ content: prepared.feedbackMeta,
1178
+ txSignature: result.signature,
1179
+ outcome: prepared.outcome
1180
+ });
1181
+ return {
1182
+ signature: result.signature,
1183
+ feedback
1184
+ };
1185
+ }
1186
+ /**
1187
+ * Search feedback with filters.
1188
+ *
1189
+ * Supports most agent0-sdk FeedbackSearchFilters. The `includeRevoked`
1190
+ * filter is a no-op on SATI (closed attestations are permanently deleted).
1191
+ */
1192
+ async searchFeedback(filters, options) {
1193
+ const sasConfig = this._requireSASConfig();
1194
+ const satiFilter = { sasSchema: sasConfig.schemas.feedbackPublic ?? sasConfig.schemas.feedback };
1195
+ let knownIdentity = null;
1196
+ const targetAgentId = filters.agentId ?? filters.agents?.[0];
1197
+ if (targetAgentId) {
1198
+ knownIdentity = await this._resolveIdentity(targetAgentId);
1199
+ satiFilter.agentMint = knownIdentity.mint;
1200
+ }
1201
+ if (filters.reviewers?.length) satiFilter.counterparty = address(filters.reviewers[0]);
1202
+ const result = await this._sati.listFeedbacks(satiFilter);
1203
+ const currentSlot = await this._sati.getRpc().getSlot({ commitment: "confirmed" }).send();
1204
+ const nowSec = Math.floor(Date.now() / 1e3);
1205
+ const feedbacks = [];
1206
+ for (let i = 0; i < result.items.length; i++) {
1207
+ const item = result.items[i];
1208
+ const rawContent = item.data.contentType === ContentType$1.JSON && item.data.content.length > 0 ? JSON.parse(new TextDecoder().decode(item.data.content)) : null;
1209
+ const score = rawContent?.score;
1210
+ const tags = rawContent?.tags ?? [];
1211
+ const text = rawContent?.m;
1212
+ const endpointVal = rawContent?.endpoint;
1213
+ const capability = rawContent?.cap;
1214
+ const name = rawContent?.n;
1215
+ const skill = rawContent?.sk;
1216
+ const task = rawContent?.tsk;
1217
+ const proofOfPayment = rawContent?.pop;
1218
+ const fileURI = rawContent?.fileURI;
1219
+ if (filters.tags?.length) {
1220
+ if (!filters.tags.every((t) => tags.includes(t))) continue;
1221
+ }
1222
+ if (options?.minValue !== void 0 && (score === void 0 || score < options.minValue)) continue;
1223
+ if (options?.maxValue !== void 0 && (score === void 0 || score > options.maxValue)) continue;
1224
+ if (filters.capabilities?.length && (!capability || !filters.capabilities.includes(capability))) continue;
1225
+ if (filters.skills?.length && (!skill || !filters.skills.includes(skill))) continue;
1226
+ if (filters.tasks?.length && (!task || !filters.tasks.includes(task))) continue;
1227
+ if (filters.names?.length && (!name || !filters.names.includes(name))) continue;
1228
+ const slotDiff = Number(BigInt(currentSlot) - item.raw.slotCreated);
1229
+ const createdAt = nowSec - Math.floor(slotDiff * .4);
1230
+ const [compressedAddress] = getAddressDecoder().read(item.address, 0);
1231
+ const feedback = toFeedback({
1232
+ agentMint: item.data.agentMint,
1233
+ chain: this._chain,
1234
+ reviewer: item.data.counterparty,
1235
+ feedbackIndex: i,
1236
+ content: {
1237
+ value: score,
1238
+ tag1: tags[0],
1239
+ tag2: tags[1],
1240
+ endpoint: endpointVal,
1241
+ text,
1242
+ context: {
1243
+ satiCompressedAddress: compressedAddress,
1244
+ counterparty: item.data.counterparty,
1245
+ agentMint: item.data.agentMint
1246
+ }
1247
+ },
1248
+ createdAt,
1249
+ outcome: item.data.outcome
1250
+ });
1251
+ if (capability) feedback.capability = capability;
1252
+ if (name) feedback.name = name;
1253
+ if (skill) feedback.skill = skill;
1254
+ if (task) feedback.task = task;
1255
+ if (proofOfPayment) feedback.proofOfPayment = proofOfPayment;
1256
+ if (fileURI) feedback.fileURI = fileURI;
1257
+ feedbacks.push(feedback);
1258
+ }
1259
+ if (options?.includeTxHash) {
1260
+ const photon = this._sati.getLightClient().getRpc();
1261
+ await Promise.all(feedbacks.map(async (fb) => {
1262
+ const addr = fb.context?.satiCompressedAddress;
1263
+ if (!addr) return;
1264
+ try {
1265
+ fb.txHash = (await photon.getCompressionSignaturesForAddress(address(addr), { limit: 1 })).items[0]?.signature;
1266
+ } catch {}
1267
+ }));
1268
+ }
1269
+ return feedbacks;
1270
+ }
1271
+ /**
1272
+ * Append response to feedback.
1273
+ *
1274
+ * @throws Error - Not supported on SATI at the moment.
1275
+ */
1276
+ async appendResponse(_agentId, _clientAddress, _feedbackIndex, _response) {
1277
+ throw new Error("appendResponse is not supported on SATI at the moment");
1278
+ }
1279
+ /**
1280
+ * Get reputation summary for an agent.
1281
+ *
1282
+ * Computes average score from all FeedbackPublic attestations,
1283
+ * optionally filtered by tags.
1284
+ */
1285
+ async getReputationSummary(agentId, tag1, tag2) {
1286
+ const sasConfig = this._requireSASConfig();
1287
+ const identity = await this._resolveIdentity(agentId);
1288
+ const result = await this._sati.listFeedbacks({
1289
+ sasSchema: sasConfig.schemas.feedbackPublic ?? sasConfig.schemas.feedback,
1290
+ agentMint: identity.mint
1291
+ });
1292
+ if (result.items.length === 0) return {
1293
+ count: 0,
1294
+ averageValue: 0
1295
+ };
1296
+ let sum = 0;
1297
+ let count = 0;
1298
+ for (const item of result.items) {
1299
+ const rawContent = item.data.contentType === ContentType$1.JSON && item.data.content.length > 0 ? JSON.parse(new TextDecoder().decode(item.data.content)) : null;
1300
+ const score = rawContent?.score;
1301
+ const tags = rawContent?.tags ?? [];
1302
+ if (tag1 && !tags.includes(tag1)) continue;
1303
+ if (tag2 && !tags.includes(tag2)) continue;
1304
+ if (score !== void 0) {
1305
+ sum += score;
1306
+ count++;
1307
+ }
1308
+ }
1309
+ return {
1310
+ count,
1311
+ averageValue: count > 0 ? sum / count : 0
1312
+ };
1313
+ }
1314
+ /**
1315
+ * Get a single feedback entry.
1316
+ *
1317
+ * Fetches feedbacks for the given agent + reviewer, then returns the one at feedbackIndex.
1318
+ */
1319
+ async getFeedback(agentId, clientAddress, feedbackIndex) {
1320
+ const fb = (await this.searchFeedback({
1321
+ agentId,
1322
+ reviewers: [clientAddress]
1323
+ }))[feedbackIndex];
1324
+ if (!fb) throw new Error(`Feedback not found at index ${feedbackIndex} for agent ${agentId} reviewer ${clientAddress}`);
1325
+ return fb;
1326
+ }
1327
+ /**
1328
+ * Look up the creation transaction signature for a compressed attestation.
1329
+ *
1330
+ * Use `feedback.context.satiCompressedAddress` from `searchFeedback()` results
1331
+ * as the `compressedAddress` parameter.
1332
+ *
1333
+ * @param compressedAddress - Base58 address of the compressed account
1334
+ * @returns Transaction signature or null if not found
1335
+ */
1336
+ async getCreationSignature(compressedAddress) {
1337
+ try {
1338
+ return (await this._sati.getLightClient().getRpc().getCompressionSignaturesForAddress(address(compressedAddress), { limit: 1 })).items[0]?.signature ?? null;
1339
+ } catch {
1340
+ return null;
1341
+ }
1342
+ }
1343
+ /**
1344
+ * Revoke (close) a previously submitted feedback.
1345
+ *
1346
+ * Closes the compressed attestation on-chain. The signer must be the
1347
+ * counterparty who originally submitted the feedback.
1348
+ *
1349
+ * Note: On SATI, revoked feedbacks are permanently deleted (Light Protocol
1350
+ * compressed accounts are closed). They cannot be recovered.
1351
+ */
1352
+ async revokeFeedback(agentId, feedbackIndex) {
1353
+ const sasConfig = this._requireSASConfig();
1354
+ const signer = this._requireSigner();
1355
+ const identity = await this._resolveIdentity(agentId);
1356
+ const schema = sasConfig.schemas.feedbackPublic ?? sasConfig.schemas.feedback;
1357
+ const item = (await this._sati.listFeedbacks({
1358
+ sasSchema: schema,
1359
+ agentMint: identity.mint,
1360
+ counterparty: signer.address
1361
+ })).items[feedbackIndex];
1362
+ if (!item) throw new Error(`Feedback not found at index ${feedbackIndex}`);
1363
+ const [attestationAddress] = getAddressDecoder().read(item.address, 0);
1364
+ return this._sati.closeCompressedAttestation({
1365
+ payer: signer,
1366
+ counterparty: signer,
1367
+ sasSchema: item.attestation.sasSchema,
1368
+ attestationAddress,
1369
+ lookupTableAddress: sasConfig.lookupTable
1370
+ });
1371
+ }
1372
+ /**
1373
+ * Search validation attestations for an agent.
1374
+ *
1375
+ * Validations are on-chain attestations from validators (TEE, zkML, re-execution, etc.)
1376
+ * that verify an agent's behavior. Unlike feedback, validations are typically automated.
1377
+ */
1378
+ async searchValidations(agentId) {
1379
+ const validationSchema = this._requireSASConfig().schemas.validation;
1380
+ if (!validationSchema) throw new Error("Validation schema not deployed on this network");
1381
+ const identity = await this._resolveIdentity(agentId);
1382
+ const result = await this._sati.listValidations({
1383
+ sasSchema: validationSchema,
1384
+ agentMint: identity.mint
1385
+ });
1386
+ const currentSlot = await this._sati.getRpc().getSlot({ commitment: "confirmed" }).send();
1387
+ const nowSec = Math.floor(Date.now() / 1e3);
1388
+ return result.items.map((item) => {
1389
+ const slotDiff = Number(BigInt(currentSlot) - item.raw.slotCreated);
1390
+ const createdAt = nowSec - Math.floor(slotDiff * .4);
1391
+ const [compressedAddress] = getAddressDecoder().read(item.address, 0);
1392
+ return {
1393
+ outcome: item.data.outcome,
1394
+ agentMint: item.data.agentMint,
1395
+ counterparty: item.data.counterparty,
1396
+ createdAt,
1397
+ compressedAddress
1398
+ };
1399
+ });
1400
+ }
1401
+ /** Get the feedback schema address for the current network. */
1402
+ get feedbackSchema() {
1403
+ return this._sasConfig?.schemas.feedback;
1404
+ }
1405
+ /** Get the feedbackPublic schema address for the current network. */
1406
+ get feedbackPublicSchema() {
1407
+ return this._sasConfig?.schemas.feedbackPublic;
1408
+ }
1409
+ /** Get the validation schema address for the current network. */
1410
+ get validationSchema() {
1411
+ return this._sasConfig?.schemas.validation;
1412
+ }
1413
+ /** Get the lookup table address for the current network. */
1414
+ get lookupTable() {
1415
+ return this._sasConfig?.lookupTable;
1416
+ }
1417
+ /** @internal */
1418
+ get config() {
1419
+ return this._config;
1420
+ }
1421
+ _requireSigner() {
1422
+ if (!this._config.signer) throw new Error("This operation requires a KeyPairSigner. Initialize SatiSDK with a signer for server-side write operations.");
1423
+ return this._config.signer;
1424
+ }
1425
+ /** Returns either a KeyPairSigner or a TransactionSender, or throws if read-only. */
1426
+ _requireWriteAccess() {
1427
+ if (this._config.signer) return {
1428
+ type: "keypair",
1429
+ signer: this._config.signer
1430
+ };
1431
+ if (this._config.transactionSender) return {
1432
+ type: "sender",
1433
+ sender: this._config.transactionSender
1434
+ };
1435
+ throw new Error("This operation requires a signer or transactionSender. Initialize SatiSDK with one for write operations.");
1436
+ }
1437
+ _requireSASConfig() {
1438
+ if (!this._sasConfig) throw new Error(`No SAS config deployed for network "${this._config.network}". Deploy schemas first.`);
1439
+ return this._sasConfig;
1440
+ }
1441
+ async _resolveIdentity(agentId) {
1442
+ const mint = parseSatiAgentId(agentId);
1443
+ if (mint === null) throw new Error(`Invalid SATI agent ID: ${agentId}. Expected format: solana:<chainRef>:<mintAddress>`);
1444
+ const identity = await this._sati.loadAgent(address(mint));
1445
+ if (!identity) throw new Error(`Agent not found: ${agentId}`);
1446
+ return identity;
1447
+ }
1448
+ };
1449
+
1450
+ //#endregion
1451
+ export { ContentType, EndpointCrawler, EndpointType, MAX_SINGLE_SIGNATURE_CONTENT_SIZE, Outcome, SATI_PROGRAM_ADDRESS, SOLANA_CAIP2_CHAINS, SatiAgent, SatiSDK, TrustModel, buildRegistrationFile, formatSatiAgentId, fromAgent0Endpoints, fromAgent0RegistrationFile, getImageUrl, handleTransactionError, parseFeedbackContent, parseSatiAgentId, stringifyRegistrationFile, toAgent0Endpoints, toAgent0RegistrationFile, toAgentSummary, toFeedback };
1452
+ //# sourceMappingURL=index.mjs.map