@cascade-fyi/sati-sdk 0.4.2 → 0.5.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/CHANGELOG.md +20 -0
- package/README.md +43 -0
- package/dist/index.cjs +293 -212
- package/dist/index.d.cts +224 -161
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +224 -161
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +293 -213
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-02-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `createPinataUploader(jwt)` and `MetadataUploader` interface exported from main entry - pluggable IPFS upload for agent registration
|
|
13
|
+
- `uploadRegistrationFile(params, uploader)` convenience method on `Sati` class
|
|
14
|
+
- Registration `Endpoint` type extended with optional fields: `mcpTools`, `mcpPrompts`, `mcpResources`, `a2aSkills`, `skills`, `domains`
|
|
15
|
+
- Gateway verification after Pinata uploads - catches silent upload failures with 5s timeout (non-fatal for timeouts/rate limits)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **BREAKING**: Pinata uploader migrated from v1 (`pinJSONToIPFS`) to v3 Files API (`uploads.pinata.cloud/v3/files`) - returns CIDv1 (`bafkrei...`) instead of CIDv0 (`Qm...`). Both formats are valid IPFS URIs.
|
|
20
|
+
- Compressed attestation queries now use server-side memcmp filtering via Photon RPC for `sasSchema` and `agentMint` (significant performance improvement for large datasets)
|
|
21
|
+
- Client-side filtering retained for `counterparty` and `outcome` fields only
|
|
22
|
+
|
|
23
|
+
### Dependencies
|
|
24
|
+
|
|
25
|
+
- Requires `@cascade-fyi/compression-kit` >= 0.2.1 (memcmp filter support)
|
|
26
|
+
|
|
8
27
|
## [0.4.2] - 2026-02-10
|
|
9
28
|
|
|
10
29
|
### Fixed
|
|
@@ -85,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
85
104
|
- Compressed attestation storage via Light Protocol
|
|
86
105
|
- Basic querying via Photon RPC
|
|
87
106
|
|
|
107
|
+
[0.5.0]: https://github.com/cascade-protocol/sati/compare/@cascade-fyi/sati-sdk@0.4.2...@cascade-fyi/sati-sdk@0.5.0
|
|
88
108
|
[0.4.2]: https://github.com/cascade-protocol/sati/compare/@cascade-fyi/sati-sdk@0.4.1...@cascade-fyi/sati-sdk@0.4.2
|
|
89
109
|
[0.4.1]: https://github.com/cascade-protocol/sati/compare/@cascade-fyi/sati-sdk@0.4.0...@cascade-fyi/sati-sdk@0.4.1
|
|
90
110
|
[0.4.0]: https://github.com/cascade-protocol/sati/compare/@cascade-fyi/sati-sdk@0.3.0...@cascade-fyi/sati-sdk@0.4.0
|
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ const result = await sati.registerAgent({
|
|
|
41
41
|
payer, // KeyPairSigner (pays fees + becomes owner)
|
|
42
42
|
name: "MyAgent", // Max 32 chars
|
|
43
43
|
uri: "ipfs://Qm...", // Agent metadata JSON
|
|
44
|
+
owner: ownerAddress, // Optional: mint NFT to a different address
|
|
44
45
|
additionalMetadata: [ // Optional key-value pairs
|
|
45
46
|
{ key: "version", value: "1.0" },
|
|
46
47
|
],
|
|
@@ -51,6 +52,48 @@ console.log(result.mint); // Agent's token address (identity)
|
|
|
51
52
|
console.log(result.memberNumber); // Registry member number
|
|
52
53
|
```
|
|
53
54
|
|
|
55
|
+
### IPFS Upload + Registration
|
|
56
|
+
|
|
57
|
+
Upload a registration file to IPFS and register in one flow:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { createPinataUploader } from "@cascade-fyi/sati-sdk";
|
|
61
|
+
|
|
62
|
+
const uploader = createPinataUploader(process.env.PINATA_JWT!);
|
|
63
|
+
|
|
64
|
+
// Build + upload registration file, then register
|
|
65
|
+
const uri = await sati.uploadRegistrationFile(
|
|
66
|
+
{
|
|
67
|
+
name: "MyAgent",
|
|
68
|
+
description: "AI assistant",
|
|
69
|
+
image: "https://example.com/avatar.png",
|
|
70
|
+
endpoints: [
|
|
71
|
+
{ name: "MCP", endpoint: "https://myagent.com/mcp", version: "2025-06-18", mcpTools: ["search"] },
|
|
72
|
+
{ name: "A2A", endpoint: "https://myagent.com/.well-known/agent.json", version: "0.3.0" },
|
|
73
|
+
],
|
|
74
|
+
supportedTrust: ["reputation"],
|
|
75
|
+
},
|
|
76
|
+
uploader,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const result = await sati.registerAgent({ payer, name: "MyAgent", uri });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Custom Storage Providers
|
|
83
|
+
|
|
84
|
+
Implement the `MetadataUploader` interface for any storage backend:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import type { MetadataUploader } from "@cascade-fyi/sati-sdk";
|
|
88
|
+
|
|
89
|
+
const arweaveUploader: MetadataUploader = {
|
|
90
|
+
async upload(data: unknown): Promise<string> {
|
|
91
|
+
// Upload to Arweave and return ar:// URI
|
|
92
|
+
return `ar://${txId}`;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
54
97
|
---
|
|
55
98
|
|
|
56
99
|
## Creating Attestations
|
package/dist/index.cjs
CHANGED
|
@@ -3832,9 +3832,19 @@ var SATILightClientImpl = class {
|
|
|
3832
3832
|
};
|
|
3833
3833
|
}
|
|
3834
3834
|
async queryAttestations(filter, deserializer) {
|
|
3835
|
+
const memcmpFilters = [];
|
|
3836
|
+
memcmpFilters.push({
|
|
3837
|
+
offset: BORSH_OFFSETS.SAS_SCHEMA,
|
|
3838
|
+
bytes: filter.sasSchema
|
|
3839
|
+
});
|
|
3840
|
+
if (filter.agentMint) memcmpFilters.push({
|
|
3841
|
+
offset: BORSH_OFFSETS.AGENT_MINT,
|
|
3842
|
+
bytes: filter.agentMint
|
|
3843
|
+
});
|
|
3835
3844
|
const result = await this.rpc.getCompressedAccountsByOwner(this.programId, {
|
|
3836
3845
|
cursor: filter.cursor,
|
|
3837
|
-
limit: filter.limit
|
|
3846
|
+
limit: filter.limit,
|
|
3847
|
+
filters: memcmpFilters
|
|
3838
3848
|
});
|
|
3839
3849
|
const attestations = [];
|
|
3840
3850
|
for (const account of result.items) {
|
|
@@ -3842,8 +3852,8 @@ var SATILightClientImpl = class {
|
|
|
3842
3852
|
try {
|
|
3843
3853
|
const parsed = this.parseAttestation(account, deserializer);
|
|
3844
3854
|
if (!parsed) continue;
|
|
3845
|
-
if (parsed.
|
|
3846
|
-
if (filter.
|
|
3855
|
+
if (filter.counterparty && parsed.data.counterparty !== filter.counterparty) continue;
|
|
3856
|
+
if (filter.outcome !== void 0 && parsed.data.outcome !== filter.outcome) continue;
|
|
3847
3857
|
attestations.push(parsed);
|
|
3848
3858
|
} catch {}
|
|
3849
3859
|
}
|
|
@@ -4629,6 +4639,51 @@ function deserializeEncryptedPayload(bytes) {
|
|
|
4629
4639
|
};
|
|
4630
4640
|
}
|
|
4631
4641
|
|
|
4642
|
+
//#endregion
|
|
4643
|
+
//#region src/uploaders.ts
|
|
4644
|
+
/**
|
|
4645
|
+
* Create a Pinata IPFS uploader using the v3 Files API.
|
|
4646
|
+
*
|
|
4647
|
+
* @param jwt - Pinata API JWT token (requires `files:write` permission)
|
|
4648
|
+
* @returns MetadataUploader that pins JSON to public IPFS via Pinata
|
|
4649
|
+
*
|
|
4650
|
+
* @example
|
|
4651
|
+
* ```typescript
|
|
4652
|
+
* const uploader = createPinataUploader(process.env.PINATA_JWT!);
|
|
4653
|
+
* const uri = await uploader.upload({ name: "MyAgent" });
|
|
4654
|
+
* // "ipfs://QmXyz..."
|
|
4655
|
+
* ```
|
|
4656
|
+
*/
|
|
4657
|
+
function createPinataUploader(jwt) {
|
|
4658
|
+
return { async upload(data) {
|
|
4659
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
4660
|
+
const blob = new Blob([jsonStr], { type: "application/json" });
|
|
4661
|
+
const formData = new FormData();
|
|
4662
|
+
formData.append("file", blob, "registration.json");
|
|
4663
|
+
formData.append("network", "public");
|
|
4664
|
+
const response = await fetch("https://uploads.pinata.cloud/v3/files", {
|
|
4665
|
+
method: "POST",
|
|
4666
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
4667
|
+
body: formData
|
|
4668
|
+
});
|
|
4669
|
+
if (!response.ok) {
|
|
4670
|
+
const text = await response.text();
|
|
4671
|
+
throw new Error(`IPFS upload failed (${response.status}): ${text}`);
|
|
4672
|
+
}
|
|
4673
|
+
const result = await response.json();
|
|
4674
|
+
const cid = result.data?.cid;
|
|
4675
|
+
if (!cid) throw new Error(`No CID returned from Pinata. Response: ${JSON.stringify(result)}`);
|
|
4676
|
+
try {
|
|
4677
|
+
const verifyResponse = await fetch(`https://gateway.pinata.cloud/ipfs/${cid}`, { signal: AbortSignal.timeout(5e3) });
|
|
4678
|
+
if (!verifyResponse.ok && verifyResponse.status !== 429) throw new Error(`Pinata returned CID ${cid} but content not accessible on gateway (HTTP ${verifyResponse.status})`);
|
|
4679
|
+
} catch (verifyError) {
|
|
4680
|
+
if (verifyError instanceof Error && !verifyError.message.includes("not accessible")) console.warn(`[SATI] Pinata gateway verification skipped for ${cid}: ${verifyError.message}`);
|
|
4681
|
+
else throw verifyError;
|
|
4682
|
+
}
|
|
4683
|
+
return `ipfs://${cid}`;
|
|
4684
|
+
} };
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4632
4687
|
//#endregion
|
|
4633
4688
|
//#region src/deployed/devnet.json
|
|
4634
4689
|
var config$1 = {
|
|
@@ -4691,6 +4746,221 @@ function getDeployedNetworks() {
|
|
|
4691
4746
|
return Object.entries(configs).filter(([_, config$2]) => config$2 !== null).map(([network]) => network);
|
|
4692
4747
|
}
|
|
4693
4748
|
|
|
4749
|
+
//#endregion
|
|
4750
|
+
//#region src/registration.ts
|
|
4751
|
+
/**
|
|
4752
|
+
* SATI Registration File
|
|
4753
|
+
*
|
|
4754
|
+
* Helpers for building, fetching, and working with ERC-8004 + Phantom
|
|
4755
|
+
* compatible registration files.
|
|
4756
|
+
*
|
|
4757
|
+
* @example
|
|
4758
|
+
* ```typescript
|
|
4759
|
+
* import {
|
|
4760
|
+
* buildRegistrationFile,
|
|
4761
|
+
* fetchRegistrationFile,
|
|
4762
|
+
* getImageUrl,
|
|
4763
|
+
* } from "@cascade-fyi/sati-sdk";
|
|
4764
|
+
*
|
|
4765
|
+
* // Build a registration file
|
|
4766
|
+
* const file = buildRegistrationFile({
|
|
4767
|
+
* name: "MyAgent",
|
|
4768
|
+
* description: "AI assistant",
|
|
4769
|
+
* image: "https://example.com/avatar.png",
|
|
4770
|
+
* });
|
|
4771
|
+
*
|
|
4772
|
+
* // Fetch from URI
|
|
4773
|
+
* const metadata = await fetchRegistrationFile(uri);
|
|
4774
|
+
* const imageUrl = getImageUrl(metadata);
|
|
4775
|
+
* ```
|
|
4776
|
+
*/
|
|
4777
|
+
const PropertyFileSchema = zod.object({
|
|
4778
|
+
uri: zod.url(),
|
|
4779
|
+
type: zod.string()
|
|
4780
|
+
});
|
|
4781
|
+
const PropertiesSchema = zod.object({
|
|
4782
|
+
files: zod.array(PropertyFileSchema).min(1),
|
|
4783
|
+
category: zod.enum([
|
|
4784
|
+
"image",
|
|
4785
|
+
"video",
|
|
4786
|
+
"audio"
|
|
4787
|
+
]).optional()
|
|
4788
|
+
});
|
|
4789
|
+
const EndpointSchema = zod.object({
|
|
4790
|
+
name: zod.string(),
|
|
4791
|
+
endpoint: zod.string(),
|
|
4792
|
+
version: zod.string().optional(),
|
|
4793
|
+
mcpTools: zod.array(zod.string()).optional(),
|
|
4794
|
+
mcpPrompts: zod.array(zod.string()).optional(),
|
|
4795
|
+
mcpResources: zod.array(zod.string()).optional(),
|
|
4796
|
+
a2aSkills: zod.array(zod.string()).optional(),
|
|
4797
|
+
skills: zod.array(zod.string()).optional(),
|
|
4798
|
+
domains: zod.array(zod.string()).optional()
|
|
4799
|
+
});
|
|
4800
|
+
const RegistrationEntrySchema = zod.object({
|
|
4801
|
+
agentId: zod.union([zod.string(), zod.number()]),
|
|
4802
|
+
agentRegistry: zod.string()
|
|
4803
|
+
});
|
|
4804
|
+
const TrustMechanismSchema = zod.enum([
|
|
4805
|
+
"reputation",
|
|
4806
|
+
"crypto-economic",
|
|
4807
|
+
"tee-attestation"
|
|
4808
|
+
]);
|
|
4809
|
+
const RegistrationFileSchema = zod.object({
|
|
4810
|
+
type: zod.literal("https://eips.ethereum.org/EIPS/eip-8004#registration-v1"),
|
|
4811
|
+
name: zod.string().min(1),
|
|
4812
|
+
description: zod.string().min(1),
|
|
4813
|
+
image: zod.url(),
|
|
4814
|
+
properties: PropertiesSchema,
|
|
4815
|
+
external_url: zod.url().optional(),
|
|
4816
|
+
endpoints: zod.array(EndpointSchema).optional(),
|
|
4817
|
+
registrations: zod.array(RegistrationEntrySchema).optional(),
|
|
4818
|
+
supportedTrust: zod.array(TrustMechanismSchema).optional(),
|
|
4819
|
+
active: zod.boolean().optional().default(true),
|
|
4820
|
+
x402support: zod.boolean().optional()
|
|
4821
|
+
});
|
|
4822
|
+
const MIME_TYPES = {
|
|
4823
|
+
png: "image/png",
|
|
4824
|
+
jpg: "image/jpeg",
|
|
4825
|
+
jpeg: "image/jpeg",
|
|
4826
|
+
gif: "image/gif",
|
|
4827
|
+
webp: "image/webp",
|
|
4828
|
+
svg: "image/svg+xml"
|
|
4829
|
+
};
|
|
4830
|
+
/**
|
|
4831
|
+
* Infer MIME type from image URL extension.
|
|
4832
|
+
* Returns "image/png" as default if unrecognized.
|
|
4833
|
+
*/
|
|
4834
|
+
function inferMimeType(url) {
|
|
4835
|
+
return MIME_TYPES[url.split(".").pop()?.toLowerCase().split("?")[0] ?? ""] ?? "image/png";
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* Build a registration file from parameters.
|
|
4839
|
+
*
|
|
4840
|
+
* Automatically:
|
|
4841
|
+
* - Sets the ERC-8004 type identifier
|
|
4842
|
+
* - Generates properties.files from image URL
|
|
4843
|
+
* - Infers MIME type from extension
|
|
4844
|
+
* - Sets active to true by default
|
|
4845
|
+
*
|
|
4846
|
+
* @throws Error if required fields are missing or invalid
|
|
4847
|
+
*/
|
|
4848
|
+
function buildRegistrationFile(params) {
|
|
4849
|
+
const mimeType = params.imageMimeType ?? inferMimeType(params.image);
|
|
4850
|
+
const file = {
|
|
4851
|
+
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
4852
|
+
name: params.name,
|
|
4853
|
+
description: params.description,
|
|
4854
|
+
image: params.image,
|
|
4855
|
+
properties: {
|
|
4856
|
+
files: [{
|
|
4857
|
+
uri: params.image,
|
|
4858
|
+
type: mimeType
|
|
4859
|
+
}],
|
|
4860
|
+
category: "image"
|
|
4861
|
+
},
|
|
4862
|
+
...params.externalUrl && { external_url: params.externalUrl },
|
|
4863
|
+
...params.endpoints?.length && { endpoints: params.endpoints },
|
|
4864
|
+
...params.registrations?.length && { registrations: params.registrations },
|
|
4865
|
+
...params.supportedTrust?.length && { supportedTrust: params.supportedTrust },
|
|
4866
|
+
active: params.active ?? true,
|
|
4867
|
+
...params.x402support !== void 0 && { x402support: params.x402support }
|
|
4868
|
+
};
|
|
4869
|
+
return RegistrationFileSchema.parse(file);
|
|
4870
|
+
}
|
|
4871
|
+
/**
|
|
4872
|
+
* Fetch and parse a registration file from URI.
|
|
4873
|
+
*
|
|
4874
|
+
* - Returns null on network errors or invalid URIs
|
|
4875
|
+
* - Validates structure, logs warnings for non-conforming files
|
|
4876
|
+
* - Never throws
|
|
4877
|
+
*/
|
|
4878
|
+
async function fetchRegistrationFile(uri) {
|
|
4879
|
+
if (!uri) return null;
|
|
4880
|
+
let url = uri;
|
|
4881
|
+
if (uri.startsWith("ipfs://")) url = `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
4882
|
+
else if (uri.startsWith("ar://")) url = `https://arweave.net/${uri.slice(5)}`;
|
|
4883
|
+
else if (!uri.startsWith("http://") && !uri.startsWith("https://")) return null;
|
|
4884
|
+
try {
|
|
4885
|
+
const response = await fetch(url);
|
|
4886
|
+
if (!response.ok) {
|
|
4887
|
+
console.warn(`[SATI] Failed to fetch metadata from ${url}: ${response.status}`);
|
|
4888
|
+
return null;
|
|
4889
|
+
}
|
|
4890
|
+
const data = await response.json();
|
|
4891
|
+
const result = RegistrationFileSchema.safeParse(data);
|
|
4892
|
+
if (!result.success) {
|
|
4893
|
+
console.warn(`[SATI] Registration file validation issues:`, result.error.issues);
|
|
4894
|
+
return data;
|
|
4895
|
+
}
|
|
4896
|
+
return result.data;
|
|
4897
|
+
} catch (error) {
|
|
4898
|
+
console.warn(`[SATI] Failed to fetch metadata from ${uri}:`, error);
|
|
4899
|
+
return null;
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
/**
|
|
4903
|
+
* Extract image URL from a registration file.
|
|
4904
|
+
*
|
|
4905
|
+
* Prefers properties.files (Phantom format), falls back to image field.
|
|
4906
|
+
* Handles IPFS/Arweave URI conversion.
|
|
4907
|
+
*/
|
|
4908
|
+
function getImageUrl(file) {
|
|
4909
|
+
if (!file) return null;
|
|
4910
|
+
const uri = file.properties?.files?.[0]?.uri ?? file.image;
|
|
4911
|
+
if (!uri) return null;
|
|
4912
|
+
if (uri.startsWith("ipfs://")) return `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
4913
|
+
if (uri.startsWith("ar://")) return `https://arweave.net/${uri.slice(5)}`;
|
|
4914
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) return uri;
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Serialize a registration file to JSON string.
|
|
4919
|
+
*/
|
|
4920
|
+
function stringifyRegistrationFile(file, space = 2) {
|
|
4921
|
+
return JSON.stringify(file, null, space);
|
|
4922
|
+
}
|
|
4923
|
+
/** CAIP-2 chain identifier for Solana mainnet */
|
|
4924
|
+
const SATI_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
4925
|
+
/** SATI program ID */
|
|
4926
|
+
const SATI_PROGRAM_ID = "satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe";
|
|
4927
|
+
/**
|
|
4928
|
+
* Build a registrations[] entry for linking a SATI agent to an off-chain registration file.
|
|
4929
|
+
*
|
|
4930
|
+
* @param agentMint - SATI agent mint address
|
|
4931
|
+
* @returns RegistrationEntry for the registrations[] array
|
|
4932
|
+
*
|
|
4933
|
+
* @example
|
|
4934
|
+
* ```typescript
|
|
4935
|
+
* const entry = buildSatiRegistrationEntry("AgentMint...");
|
|
4936
|
+
* // { agentId: "AgentMint...", agentRegistry: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:satiR3q7..." }
|
|
4937
|
+
* ```
|
|
4938
|
+
*/
|
|
4939
|
+
function buildSatiRegistrationEntry(agentMint) {
|
|
4940
|
+
return {
|
|
4941
|
+
agentId: agentMint,
|
|
4942
|
+
agentRegistry: `${SATI_CHAIN_ID}:${SATI_PROGRAM_ID}`
|
|
4943
|
+
};
|
|
4944
|
+
}
|
|
4945
|
+
/**
|
|
4946
|
+
* Check if a registration file contains a SATI registration.
|
|
4947
|
+
*
|
|
4948
|
+
* @param file - Registration file to check
|
|
4949
|
+
* @returns true if file contains at least one SATI registration
|
|
4950
|
+
*/
|
|
4951
|
+
function hasSatiRegistration(file) {
|
|
4952
|
+
return file.registrations?.some((r) => typeof r.agentRegistry === "string" && r.agentRegistry.startsWith(SATI_CHAIN_ID)) ?? false;
|
|
4953
|
+
}
|
|
4954
|
+
/**
|
|
4955
|
+
* Find SATI agent IDs from a registration file.
|
|
4956
|
+
*
|
|
4957
|
+
* @param file - Registration file to search
|
|
4958
|
+
* @returns Array of SATI agent mint addresses
|
|
4959
|
+
*/
|
|
4960
|
+
function getSatiAgentIds(file) {
|
|
4961
|
+
return file.registrations?.filter((r) => typeof r.agentRegistry === "string" && r.agentRegistry.startsWith(SATI_CHAIN_ID)).map((r) => String(r.agentId)) ?? [];
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4694
4964
|
//#endregion
|
|
4695
4965
|
//#region src/client.ts
|
|
4696
4966
|
/**
|
|
@@ -4842,6 +5112,25 @@ var Sati = class {
|
|
|
4842
5112
|
};
|
|
4843
5113
|
}
|
|
4844
5114
|
/**
|
|
5115
|
+
* Build a registration file from params, upload it via the provided uploader,
|
|
5116
|
+
* and return the resulting URI.
|
|
5117
|
+
*
|
|
5118
|
+
* @example
|
|
5119
|
+
* ```typescript
|
|
5120
|
+
* import { createPinataUploader } from "@cascade-fyi/sati-sdk";
|
|
5121
|
+
*
|
|
5122
|
+
* const uploader = createPinataUploader(process.env.PINATA_JWT!);
|
|
5123
|
+
* const uri = await sati.uploadRegistrationFile(
|
|
5124
|
+
* { name: "MyAgent", description: "AI assistant", image: "https://example.com/img.png" },
|
|
5125
|
+
* uploader,
|
|
5126
|
+
* );
|
|
5127
|
+
* ```
|
|
5128
|
+
*/
|
|
5129
|
+
async uploadRegistrationFile(params, uploader) {
|
|
5130
|
+
const regFile = buildRegistrationFile(params);
|
|
5131
|
+
return uploader.upload(regFile);
|
|
5132
|
+
}
|
|
5133
|
+
/**
|
|
4845
5134
|
* Load agent identity from mint address
|
|
4846
5135
|
*/
|
|
4847
5136
|
async loadAgent(mint) {
|
|
@@ -5892,215 +6181,6 @@ var Sati = class {
|
|
|
5892
6181
|
}
|
|
5893
6182
|
};
|
|
5894
6183
|
|
|
5895
|
-
//#endregion
|
|
5896
|
-
//#region src/registration.ts
|
|
5897
|
-
/**
|
|
5898
|
-
* SATI Registration File
|
|
5899
|
-
*
|
|
5900
|
-
* Helpers for building, fetching, and working with ERC-8004 + Phantom
|
|
5901
|
-
* compatible registration files.
|
|
5902
|
-
*
|
|
5903
|
-
* @example
|
|
5904
|
-
* ```typescript
|
|
5905
|
-
* import {
|
|
5906
|
-
* buildRegistrationFile,
|
|
5907
|
-
* fetchRegistrationFile,
|
|
5908
|
-
* getImageUrl,
|
|
5909
|
-
* } from "@cascade-fyi/sati-sdk";
|
|
5910
|
-
*
|
|
5911
|
-
* // Build a registration file
|
|
5912
|
-
* const file = buildRegistrationFile({
|
|
5913
|
-
* name: "MyAgent",
|
|
5914
|
-
* description: "AI assistant",
|
|
5915
|
-
* image: "https://example.com/avatar.png",
|
|
5916
|
-
* });
|
|
5917
|
-
*
|
|
5918
|
-
* // Fetch from URI
|
|
5919
|
-
* const metadata = await fetchRegistrationFile(uri);
|
|
5920
|
-
* const imageUrl = getImageUrl(metadata);
|
|
5921
|
-
* ```
|
|
5922
|
-
*/
|
|
5923
|
-
const PropertyFileSchema = zod.object({
|
|
5924
|
-
uri: zod.url(),
|
|
5925
|
-
type: zod.string()
|
|
5926
|
-
});
|
|
5927
|
-
const PropertiesSchema = zod.object({
|
|
5928
|
-
files: zod.array(PropertyFileSchema).min(1),
|
|
5929
|
-
category: zod.enum([
|
|
5930
|
-
"image",
|
|
5931
|
-
"video",
|
|
5932
|
-
"audio"
|
|
5933
|
-
]).optional()
|
|
5934
|
-
});
|
|
5935
|
-
const EndpointSchema = zod.object({
|
|
5936
|
-
name: zod.string(),
|
|
5937
|
-
endpoint: zod.string(),
|
|
5938
|
-
version: zod.string().optional()
|
|
5939
|
-
});
|
|
5940
|
-
const RegistrationEntrySchema = zod.object({
|
|
5941
|
-
agentId: zod.union([zod.string(), zod.number()]),
|
|
5942
|
-
agentRegistry: zod.string()
|
|
5943
|
-
});
|
|
5944
|
-
const TrustMechanismSchema = zod.enum([
|
|
5945
|
-
"reputation",
|
|
5946
|
-
"crypto-economic",
|
|
5947
|
-
"tee-attestation"
|
|
5948
|
-
]);
|
|
5949
|
-
const RegistrationFileSchema = zod.object({
|
|
5950
|
-
type: zod.literal("https://eips.ethereum.org/EIPS/eip-8004#registration-v1"),
|
|
5951
|
-
name: zod.string().min(1),
|
|
5952
|
-
description: zod.string().min(1),
|
|
5953
|
-
image: zod.url(),
|
|
5954
|
-
properties: PropertiesSchema,
|
|
5955
|
-
external_url: zod.url().optional(),
|
|
5956
|
-
endpoints: zod.array(EndpointSchema).optional(),
|
|
5957
|
-
registrations: zod.array(RegistrationEntrySchema).optional(),
|
|
5958
|
-
supportedTrust: zod.array(TrustMechanismSchema).optional(),
|
|
5959
|
-
active: zod.boolean().optional().default(true),
|
|
5960
|
-
x402support: zod.boolean().optional()
|
|
5961
|
-
});
|
|
5962
|
-
const MIME_TYPES = {
|
|
5963
|
-
png: "image/png",
|
|
5964
|
-
jpg: "image/jpeg",
|
|
5965
|
-
jpeg: "image/jpeg",
|
|
5966
|
-
gif: "image/gif",
|
|
5967
|
-
webp: "image/webp",
|
|
5968
|
-
svg: "image/svg+xml"
|
|
5969
|
-
};
|
|
5970
|
-
/**
|
|
5971
|
-
* Infer MIME type from image URL extension.
|
|
5972
|
-
* Returns "image/png" as default if unrecognized.
|
|
5973
|
-
*/
|
|
5974
|
-
function inferMimeType(url) {
|
|
5975
|
-
return MIME_TYPES[url.split(".").pop()?.toLowerCase().split("?")[0] ?? ""] ?? "image/png";
|
|
5976
|
-
}
|
|
5977
|
-
/**
|
|
5978
|
-
* Build a registration file from parameters.
|
|
5979
|
-
*
|
|
5980
|
-
* Automatically:
|
|
5981
|
-
* - Sets the ERC-8004 type identifier
|
|
5982
|
-
* - Generates properties.files from image URL
|
|
5983
|
-
* - Infers MIME type from extension
|
|
5984
|
-
* - Sets active to true by default
|
|
5985
|
-
*
|
|
5986
|
-
* @throws Error if required fields are missing or invalid
|
|
5987
|
-
*/
|
|
5988
|
-
function buildRegistrationFile(params) {
|
|
5989
|
-
const mimeType = params.imageMimeType ?? inferMimeType(params.image);
|
|
5990
|
-
const file = {
|
|
5991
|
-
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
5992
|
-
name: params.name,
|
|
5993
|
-
description: params.description,
|
|
5994
|
-
image: params.image,
|
|
5995
|
-
properties: {
|
|
5996
|
-
files: [{
|
|
5997
|
-
uri: params.image,
|
|
5998
|
-
type: mimeType
|
|
5999
|
-
}],
|
|
6000
|
-
category: "image"
|
|
6001
|
-
},
|
|
6002
|
-
...params.externalUrl && { external_url: params.externalUrl },
|
|
6003
|
-
...params.endpoints?.length && { endpoints: params.endpoints },
|
|
6004
|
-
...params.registrations?.length && { registrations: params.registrations },
|
|
6005
|
-
...params.supportedTrust?.length && { supportedTrust: params.supportedTrust },
|
|
6006
|
-
active: params.active ?? true,
|
|
6007
|
-
...params.x402support !== void 0 && { x402support: params.x402support }
|
|
6008
|
-
};
|
|
6009
|
-
return RegistrationFileSchema.parse(file);
|
|
6010
|
-
}
|
|
6011
|
-
/**
|
|
6012
|
-
* Fetch and parse a registration file from URI.
|
|
6013
|
-
*
|
|
6014
|
-
* - Returns null on network errors or invalid URIs
|
|
6015
|
-
* - Validates structure, logs warnings for non-conforming files
|
|
6016
|
-
* - Never throws
|
|
6017
|
-
*/
|
|
6018
|
-
async function fetchRegistrationFile(uri) {
|
|
6019
|
-
if (!uri) return null;
|
|
6020
|
-
let url = uri;
|
|
6021
|
-
if (uri.startsWith("ipfs://")) url = `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
6022
|
-
else if (uri.startsWith("ar://")) url = `https://arweave.net/${uri.slice(5)}`;
|
|
6023
|
-
else if (!uri.startsWith("http://") && !uri.startsWith("https://")) return null;
|
|
6024
|
-
try {
|
|
6025
|
-
const response = await fetch(url);
|
|
6026
|
-
if (!response.ok) {
|
|
6027
|
-
console.warn(`[SATI] Failed to fetch metadata from ${url}: ${response.status}`);
|
|
6028
|
-
return null;
|
|
6029
|
-
}
|
|
6030
|
-
const data = await response.json();
|
|
6031
|
-
const result = RegistrationFileSchema.safeParse(data);
|
|
6032
|
-
if (!result.success) {
|
|
6033
|
-
console.warn(`[SATI] Registration file validation issues:`, result.error.issues);
|
|
6034
|
-
return data;
|
|
6035
|
-
}
|
|
6036
|
-
return result.data;
|
|
6037
|
-
} catch (error) {
|
|
6038
|
-
console.warn(`[SATI] Failed to fetch metadata from ${uri}:`, error);
|
|
6039
|
-
return null;
|
|
6040
|
-
}
|
|
6041
|
-
}
|
|
6042
|
-
/**
|
|
6043
|
-
* Extract image URL from a registration file.
|
|
6044
|
-
*
|
|
6045
|
-
* Prefers properties.files (Phantom format), falls back to image field.
|
|
6046
|
-
* Handles IPFS/Arweave URI conversion.
|
|
6047
|
-
*/
|
|
6048
|
-
function getImageUrl(file) {
|
|
6049
|
-
if (!file) return null;
|
|
6050
|
-
const uri = file.properties?.files?.[0]?.uri ?? file.image;
|
|
6051
|
-
if (!uri) return null;
|
|
6052
|
-
if (uri.startsWith("ipfs://")) return `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
6053
|
-
if (uri.startsWith("ar://")) return `https://arweave.net/${uri.slice(5)}`;
|
|
6054
|
-
if (uri.startsWith("http://") || uri.startsWith("https://")) return uri;
|
|
6055
|
-
return null;
|
|
6056
|
-
}
|
|
6057
|
-
/**
|
|
6058
|
-
* Serialize a registration file to JSON string.
|
|
6059
|
-
*/
|
|
6060
|
-
function stringifyRegistrationFile(file, space = 2) {
|
|
6061
|
-
return JSON.stringify(file, null, space);
|
|
6062
|
-
}
|
|
6063
|
-
/** CAIP-2 chain identifier for Solana mainnet */
|
|
6064
|
-
const SATI_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
6065
|
-
/** SATI program ID */
|
|
6066
|
-
const SATI_PROGRAM_ID = "satiRkxEiwZ51cv8PRu8UMzuaqeaNU9jABo6oAFMsLe";
|
|
6067
|
-
/**
|
|
6068
|
-
* Build a registrations[] entry for linking a SATI agent to an off-chain registration file.
|
|
6069
|
-
*
|
|
6070
|
-
* @param agentMint - SATI agent mint address
|
|
6071
|
-
* @returns RegistrationEntry for the registrations[] array
|
|
6072
|
-
*
|
|
6073
|
-
* @example
|
|
6074
|
-
* ```typescript
|
|
6075
|
-
* const entry = buildSatiRegistrationEntry("AgentMint...");
|
|
6076
|
-
* // { agentId: "AgentMint...", agentRegistry: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:satiR3q7..." }
|
|
6077
|
-
* ```
|
|
6078
|
-
*/
|
|
6079
|
-
function buildSatiRegistrationEntry(agentMint) {
|
|
6080
|
-
return {
|
|
6081
|
-
agentId: agentMint,
|
|
6082
|
-
agentRegistry: `${SATI_CHAIN_ID}:${SATI_PROGRAM_ID}`
|
|
6083
|
-
};
|
|
6084
|
-
}
|
|
6085
|
-
/**
|
|
6086
|
-
* Check if a registration file contains a SATI registration.
|
|
6087
|
-
*
|
|
6088
|
-
* @param file - Registration file to check
|
|
6089
|
-
* @returns true if file contains at least one SATI registration
|
|
6090
|
-
*/
|
|
6091
|
-
function hasSatiRegistration(file) {
|
|
6092
|
-
return file.registrations?.some((r) => typeof r.agentRegistry === "string" && r.agentRegistry.startsWith(SATI_CHAIN_ID)) ?? false;
|
|
6093
|
-
}
|
|
6094
|
-
/**
|
|
6095
|
-
* Find SATI agent IDs from a registration file.
|
|
6096
|
-
*
|
|
6097
|
-
* @param file - Registration file to search
|
|
6098
|
-
* @returns Array of SATI agent mint addresses
|
|
6099
|
-
*/
|
|
6100
|
-
function getSatiAgentIds(file) {
|
|
6101
|
-
return file.registrations?.filter((r) => typeof r.agentRegistry === "string" && r.agentRegistry.startsWith(SATI_CHAIN_ID)).map((r) => String(r.agentId)) ?? [];
|
|
6102
|
-
}
|
|
6103
|
-
|
|
6104
6184
|
//#endregion
|
|
6105
6185
|
//#region src/errors.ts
|
|
6106
6186
|
/** Base class for all SATI SDK errors */
|
|
@@ -6398,6 +6478,7 @@ exports.computeReputationNonce = computeReputationNonce;
|
|
|
6398
6478
|
exports.createBatchEd25519Instruction = createBatchEd25519Instruction;
|
|
6399
6479
|
exports.createEd25519Instruction = createEd25519Instruction;
|
|
6400
6480
|
exports.createJsonContent = createJsonContent;
|
|
6481
|
+
exports.createPinataUploader = createPinataUploader;
|
|
6401
6482
|
exports.createSATILightClient = createSATILightClient;
|
|
6402
6483
|
exports.decodeAgentIndex = decodeAgentIndex;
|
|
6403
6484
|
exports.decodeRegistryConfig = decodeRegistryConfig;
|