@openserv-labs/client 2.0.2 → 2.1.2
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 +142 -2
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -0
- package/dist/erc8004-abi.d.ts +113 -0
- package/dist/erc8004-abi.d.ts.map +1 -0
- package/dist/erc8004-abi.js +123 -0
- package/dist/erc8004-api.d.ts +189 -0
- package/dist/erc8004-api.d.ts.map +1 -0
- package/dist/erc8004-api.js +491 -0
- package/dist/erc8004-contracts.d.ts +92 -0
- package/dist/erc8004-contracts.d.ts.map +1 -0
- package/dist/erc8004-contracts.js +250 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/triggers-api.d.ts +27 -5
- package/dist/triggers-api.d.ts.map +1 -1
- package/dist/triggers-api.js +26 -4
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workflows-api.d.ts +110 -1
- package/dist/workflows-api.d.ts.map +1 -1
- package/dist/workflows-api.js +112 -0
- package/package.json +2 -1
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Erc8004API = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
const accounts_1 = require("viem/accounts");
|
|
6
|
+
const pinata_1 = require("pinata");
|
|
7
|
+
const erc8004_abi_js_1 = require("./erc8004-abi.js");
|
|
8
|
+
const erc8004_contracts_js_1 = require("./erc8004-contracts.js");
|
|
9
|
+
/**
|
|
10
|
+
* API for ERC-8004 agent identity operations on the OpenServ platform.
|
|
11
|
+
*
|
|
12
|
+
* ERC-8004 is an on-chain agent identity standard. This API enables:
|
|
13
|
+
* - Deploying workspace agents to the blockchain as ERC-8004 tokens
|
|
14
|
+
* - Managing web3 wallets associated with workspaces
|
|
15
|
+
* - Presigning IPFS URLs for uploading agent registration files
|
|
16
|
+
* - Querying callable triggers for on-chain service registration
|
|
17
|
+
* - Signing feedback auth for reputation interactions
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const client = new PlatformClient({ apiKey: 'your-key' });
|
|
22
|
+
*
|
|
23
|
+
* // Generate a web3 wallet for the workspace
|
|
24
|
+
* const wallet = await client.workflows.generateWallet({ id: 123 });
|
|
25
|
+
*
|
|
26
|
+
* // Get a presigned IPFS URL for uploading the agent card
|
|
27
|
+
* const { url } = await client.erc8004.presignIpfsUrl({ workflowId: 123 });
|
|
28
|
+
*
|
|
29
|
+
* // Deploy to ERC-8004
|
|
30
|
+
* await client.erc8004.deploy({
|
|
31
|
+
* workflowId: 123,
|
|
32
|
+
* erc8004AgentId: '8453:42',
|
|
33
|
+
* stringifiedAgentCard: JSON.stringify(registrationFile),
|
|
34
|
+
* walletAddress: '0x...',
|
|
35
|
+
* network: 'base',
|
|
36
|
+
* chainId: 8453,
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
class Erc8004API {
|
|
41
|
+
client;
|
|
42
|
+
constructor(client) {
|
|
43
|
+
this.client = client;
|
|
44
|
+
}
|
|
45
|
+
// ===========================================================================
|
|
46
|
+
// ERC-8004 Deployment
|
|
47
|
+
// ===========================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Deploy a workspace agent to the ERC-8004 identity registry.
|
|
50
|
+
*
|
|
51
|
+
* This records the deployment details in the platform database. The actual
|
|
52
|
+
* on-chain registration should be performed separately using the agent0 SDK.
|
|
53
|
+
* Call this method both before and after blockchain registration to keep
|
|
54
|
+
* the platform in sync.
|
|
55
|
+
*
|
|
56
|
+
* If the deploy fails (e.g. insufficient ETH for gas), the error is
|
|
57
|
+
* enriched with the workspace wallet address and instructions for funding
|
|
58
|
+
* it so you can retry.
|
|
59
|
+
*
|
|
60
|
+
* @param params - Deployment parameters
|
|
61
|
+
* @param params.workflowId - The workflow (workspace) ID to deploy
|
|
62
|
+
* @param params.erc8004AgentId - Agent ID in format "chainId:tokenId"
|
|
63
|
+
* @param params.stringifiedAgentCard - JSON-stringified registration file
|
|
64
|
+
* @param params.latestDeploymentTransactionHash - Transaction hash from on-chain registration
|
|
65
|
+
* @param params.latestDeploymentTimestamp - Timestamp of the deployment
|
|
66
|
+
* @param params.walletAddress - Wallet address that performed the deployment
|
|
67
|
+
* @param params.network - Network name (e.g., "base")
|
|
68
|
+
* @param params.chainId - Chain ID (e.g., 8453 for Base mainnet)
|
|
69
|
+
* @param params.rpcUrl - RPC URL for the chain
|
|
70
|
+
* @param params.swap - If true, swap USDC in the wallet to ETH for gas before deploying (not yet implemented)
|
|
71
|
+
* @returns The updated workflow data
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // Before blockchain registration (save initial state)
|
|
76
|
+
* await client.erc8004.deploy({
|
|
77
|
+
* workflowId: 123,
|
|
78
|
+
* erc8004AgentId: '',
|
|
79
|
+
* stringifiedAgentCard: JSON.stringify(registrationFile),
|
|
80
|
+
* walletAddress: '0x...',
|
|
81
|
+
* network: 'base',
|
|
82
|
+
* chainId: 8453,
|
|
83
|
+
* rpcUrl: 'https://mainnet.base.org',
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* // ... perform on-chain registration ...
|
|
87
|
+
*
|
|
88
|
+
* // After blockchain registration (save final state with tx hash)
|
|
89
|
+
* await client.erc8004.deploy({
|
|
90
|
+
* workflowId: 123,
|
|
91
|
+
* erc8004AgentId: '8453:42',
|
|
92
|
+
* stringifiedAgentCard: JSON.stringify(updatedRegistrationFile),
|
|
93
|
+
* latestDeploymentTransactionHash: '0xabc...',
|
|
94
|
+
* latestDeploymentTimestamp: new Date(),
|
|
95
|
+
* walletAddress: '0x...',
|
|
96
|
+
* network: 'base',
|
|
97
|
+
* chainId: 8453,
|
|
98
|
+
* rpcUrl: 'https://mainnet.base.org',
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
async deploy(params) {
|
|
103
|
+
const { workflowId, swap, ...body } = params;
|
|
104
|
+
if (swap) {
|
|
105
|
+
throw new Error("USDC-to-ETH swap for gas is not yet implemented. " +
|
|
106
|
+
"This feature is coming soon. In the meantime, fund the workspace " +
|
|
107
|
+
"wallet with ETH directly and retry without the swap option.");
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
return await this.client.put(`/workspaces/${workflowId}/erc-8004/deploy`, body);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// Enrich the error with wallet info so the user knows where to send ETH
|
|
114
|
+
throw await this.enrichDeployError(error, workflowId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Enrich a deploy error with the workspace wallet address and funding
|
|
119
|
+
* instructions. Called automatically when deploy() fails.
|
|
120
|
+
*/
|
|
121
|
+
async enrichDeployError(originalError, workflowId) {
|
|
122
|
+
const originalMessage = originalError instanceof Error
|
|
123
|
+
? originalError.message
|
|
124
|
+
: String(originalError);
|
|
125
|
+
let walletAddress = null;
|
|
126
|
+
try {
|
|
127
|
+
const wallet = await this.client.workflows.getWallet({ id: workflowId });
|
|
128
|
+
walletAddress = wallet.address;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Wallet may not exist yet
|
|
132
|
+
}
|
|
133
|
+
const fundingHint = walletAddress
|
|
134
|
+
? `\n\nWorkspace wallet address: ${walletAddress}\n` +
|
|
135
|
+
`Send ETH to this address to cover gas fees, then retry.` +
|
|
136
|
+
`\n\nNote: automatic USDC-to-ETH swap is coming soon. ` +
|
|
137
|
+
`Once available, pass swap: true to convert USDC in the wallet to ETH for gas.`
|
|
138
|
+
: `\n\nNo wallet found for this workspace. ` +
|
|
139
|
+
`Generate one first with client.workflows.generateWallet({ id: ${workflowId} }), ` +
|
|
140
|
+
`then fund it with ETH and retry.`;
|
|
141
|
+
return new Error(originalMessage + fundingHint);
|
|
142
|
+
}
|
|
143
|
+
// ===========================================================================
|
|
144
|
+
// Full On-Chain Registration
|
|
145
|
+
// ===========================================================================
|
|
146
|
+
/**
|
|
147
|
+
* Register (or re-deploy) a workspace agent on-chain as an ERC-8004 identity.
|
|
148
|
+
*
|
|
149
|
+
* This is a high-level method that orchestrates the entire deployment:
|
|
150
|
+
* 1. Reads workspace wallet and callable triggers
|
|
151
|
+
* 2. Builds the ERC-8004 agent card JSON
|
|
152
|
+
* 3. Uploads the agent card to IPFS via a presigned Pinata URL
|
|
153
|
+
* 4. Registers on-chain (first deploy) or updates the URI (re-deploy)
|
|
154
|
+
* 5. Saves the deployment state to the platform
|
|
155
|
+
*
|
|
156
|
+
* @param params.workflowId - The workflow (workspace) ID
|
|
157
|
+
* @param params.privateKey - Funded private key for on-chain transactions (must have ETH for gas)
|
|
158
|
+
* @param params.chainId - Chain ID (default: 8453 for Base mainnet)
|
|
159
|
+
* @param params.rpcUrl - RPC URL (default: "https://mainnet.base.org")
|
|
160
|
+
* @param params.name - Agent name override (defaults: single trigger → trigger name, else workspace name)
|
|
161
|
+
* @param params.description - Agent description override (defaults: single trigger → trigger description, else workspace goal + service list)
|
|
162
|
+
* @param params.image - Agent image URL (optional)
|
|
163
|
+
* @returns Deployment result with agentId, IPFS CID, transaction hash, and URLs
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const result = await client.erc8004.registerOnChain({
|
|
168
|
+
* workflowId: 123,
|
|
169
|
+
* privateKey: '0x...',
|
|
170
|
+
* name: 'My AI Agent',
|
|
171
|
+
* description: 'An agent that does amazing things',
|
|
172
|
+
* });
|
|
173
|
+
* console.log(result.agentId); // "8453:42"
|
|
174
|
+
* console.log(result.txHash); // "0xabc..."
|
|
175
|
+
* console.log(result.ipfsCid); // "bafkrei..."
|
|
176
|
+
* console.log(result.blockExplorerUrl); // "https://basescan.org/tx/0xabc..."
|
|
177
|
+
* console.log(result.scanUrl); // "https://www.8004scan.io/agents/base/42"
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
async registerOnChain(params) {
|
|
181
|
+
const { workflowId, privateKey, chainId = 8453, rpcUrl = "https://mainnet.base.org", image, } = params;
|
|
182
|
+
// 1. Get wallet, callable triggers, and workspace data
|
|
183
|
+
const [wallet, callableTriggers, workspace] = await Promise.all([
|
|
184
|
+
this.client.workflows.getWallet({ id: workflowId }),
|
|
185
|
+
this.client.triggers.getCallableTriggers({ workflowId }),
|
|
186
|
+
this.client.workflows.get({ id: workflowId }),
|
|
187
|
+
]);
|
|
188
|
+
// Derive name and description from triggers/workspace (matching monorepo logic)
|
|
189
|
+
let name = params.name;
|
|
190
|
+
let description = params.description;
|
|
191
|
+
if (!name || !description) {
|
|
192
|
+
if (callableTriggers.length === 1 && callableTriggers[0]) {
|
|
193
|
+
const t = callableTriggers[0];
|
|
194
|
+
if (!name)
|
|
195
|
+
name = t.name || workspace.name;
|
|
196
|
+
if (!description)
|
|
197
|
+
description = t.description || workspace.goal || "Default";
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
if (!name)
|
|
201
|
+
name = workspace.name;
|
|
202
|
+
if (!description) {
|
|
203
|
+
const triggerBullets = callableTriggers
|
|
204
|
+
.filter((t) => t.description)
|
|
205
|
+
.map((t) => `- ${t.name}: ${t.description}`)
|
|
206
|
+
.join("\n");
|
|
207
|
+
description = [
|
|
208
|
+
workspace.goal || "Default",
|
|
209
|
+
...(triggerBullets ? [`\nServices:\n${triggerBullets}`] : []),
|
|
210
|
+
].join("");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// 2. Build ERC-8004 agent card (matches monorepo addRegistrationFile format)
|
|
215
|
+
const baseUrl = this.client.rawClient.defaults.baseURL || "https://api.openserv.ai";
|
|
216
|
+
const services = [];
|
|
217
|
+
// MCP endpoint (machine-facing, aggregates all x402 triggers)
|
|
218
|
+
const mcpEndpoint = `${baseUrl}/workspaces/${workflowId}/trigger-x402-mcp/mcp`;
|
|
219
|
+
services.push({
|
|
220
|
+
name: "MCP",
|
|
221
|
+
endpoint: mcpEndpoint,
|
|
222
|
+
version: "2025-06-18",
|
|
223
|
+
});
|
|
224
|
+
for (const t of callableTriggers) {
|
|
225
|
+
const meta = {};
|
|
226
|
+
if (t.name)
|
|
227
|
+
meta.triggerName = t.name;
|
|
228
|
+
if (t.description)
|
|
229
|
+
meta.description = t.description;
|
|
230
|
+
// WEB endpoint (human-facing paywall URL)
|
|
231
|
+
services.push({
|
|
232
|
+
name: "web",
|
|
233
|
+
endpoint: t.webEndpoint,
|
|
234
|
+
...(Object.keys(meta).length > 0 ? meta : {}),
|
|
235
|
+
});
|
|
236
|
+
// HTTP endpoint (machine-facing x402 URL)
|
|
237
|
+
if (t.httpEndpoint) {
|
|
238
|
+
const httpMeta = { ...meta };
|
|
239
|
+
if (t.inputSchema)
|
|
240
|
+
httpMeta.inputSchema = t.inputSchema;
|
|
241
|
+
services.push({
|
|
242
|
+
name: "http",
|
|
243
|
+
endpoint: t.httpEndpoint,
|
|
244
|
+
...(Object.keys(httpMeta).length > 0 ? httpMeta : {}),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Add walletAddress as an endpoint (deduplicate first)
|
|
249
|
+
if (wallet.address) {
|
|
250
|
+
const filtered = services.filter((s) => s.name !== "agentWallet" && s.name !== "wallet");
|
|
251
|
+
services.length = 0;
|
|
252
|
+
services.push(...filtered);
|
|
253
|
+
services.push({
|
|
254
|
+
name: "agentWallet",
|
|
255
|
+
endpoint: `eip155:${chainId}:${wallet.address}`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// Build registrations array (populated on re-deploy)
|
|
259
|
+
const existingAgentId = wallet.erc8004AgentId;
|
|
260
|
+
const registrations = [];
|
|
261
|
+
if (existingAgentId) {
|
|
262
|
+
const [, tokenId] = existingAgentId.split(":");
|
|
263
|
+
if (tokenId) {
|
|
264
|
+
const contracts = (0, erc8004_contracts_js_1.getErc8004Contracts)(chainId);
|
|
265
|
+
registrations.push({
|
|
266
|
+
agentId: Number.parseInt(tokenId, 10),
|
|
267
|
+
agentRegistry: `eip155:${chainId}:${contracts.IDENTITY_REGISTRY}`,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const agentCard = {
|
|
272
|
+
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
273
|
+
name,
|
|
274
|
+
description,
|
|
275
|
+
...(image && { image }),
|
|
276
|
+
services,
|
|
277
|
+
...(registrations.length > 0 && { registrations }),
|
|
278
|
+
active: true,
|
|
279
|
+
x402support: true,
|
|
280
|
+
};
|
|
281
|
+
const agentCardJson = JSON.stringify(agentCard);
|
|
282
|
+
// 3. Detect first deploy vs re-deploy
|
|
283
|
+
const isRedeploy = !!existingAgentId;
|
|
284
|
+
// 4. Save initial state
|
|
285
|
+
await this.deploy({
|
|
286
|
+
workflowId,
|
|
287
|
+
erc8004AgentId: existingAgentId ?? "",
|
|
288
|
+
stringifiedAgentCard: agentCardJson,
|
|
289
|
+
...(wallet.address ? { walletAddress: wallet.address } : {}),
|
|
290
|
+
network: "base",
|
|
291
|
+
chainId,
|
|
292
|
+
rpcUrl,
|
|
293
|
+
});
|
|
294
|
+
// 5. Upload to IPFS
|
|
295
|
+
const { url: presignedUrl } = await this.presignIpfsUrl({ workflowId });
|
|
296
|
+
const ipfsCid = await this.uploadToIpfs(agentCardJson, presignedUrl);
|
|
297
|
+
// 6. On-chain registration
|
|
298
|
+
const contracts = (0, erc8004_contracts_js_1.getErc8004Contracts)(chainId);
|
|
299
|
+
const chainConfig = (0, erc8004_contracts_js_1.getErc8004Chain)(chainId);
|
|
300
|
+
const registryAddress = contracts.IDENTITY_REGISTRY;
|
|
301
|
+
// Build a viem Chain from the ERC-8004 chain config
|
|
302
|
+
const viemChain = chainConfig
|
|
303
|
+
? (0, viem_1.defineChain)({
|
|
304
|
+
id: chainConfig.chainId,
|
|
305
|
+
name: chainConfig.name,
|
|
306
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
307
|
+
rpcUrls: {
|
|
308
|
+
default: { http: [rpcUrl] },
|
|
309
|
+
},
|
|
310
|
+
blockExplorers: {
|
|
311
|
+
default: {
|
|
312
|
+
name: chainConfig.name,
|
|
313
|
+
url: chainConfig.blockExplorerUrl,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
testnet: chainConfig.testnet,
|
|
317
|
+
})
|
|
318
|
+
: (0, viem_1.defineChain)({
|
|
319
|
+
id: chainId,
|
|
320
|
+
name: `Chain ${chainId}`,
|
|
321
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
322
|
+
rpcUrls: {
|
|
323
|
+
default: { http: [rpcUrl] },
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
const normalizedKey = (privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`);
|
|
327
|
+
const account = (0, accounts_1.privateKeyToAccount)(normalizedKey);
|
|
328
|
+
const publicClient = (0, viem_1.createPublicClient)({
|
|
329
|
+
chain: viemChain,
|
|
330
|
+
transport: (0, viem_1.http)(rpcUrl),
|
|
331
|
+
});
|
|
332
|
+
const walletClient = (0, viem_1.createWalletClient)({
|
|
333
|
+
account,
|
|
334
|
+
chain: viemChain,
|
|
335
|
+
transport: (0, viem_1.http)(rpcUrl),
|
|
336
|
+
});
|
|
337
|
+
let agentId;
|
|
338
|
+
let txHash;
|
|
339
|
+
if (isRedeploy) {
|
|
340
|
+
// Re-deploy: just update the URI
|
|
341
|
+
const tokenId = existingAgentId.split(":")[1];
|
|
342
|
+
if (!tokenId) {
|
|
343
|
+
throw new Error(`Invalid existing agentId format: ${existingAgentId}`);
|
|
344
|
+
}
|
|
345
|
+
const hash = await walletClient.writeContract({
|
|
346
|
+
address: registryAddress,
|
|
347
|
+
abi: erc8004_abi_js_1.IDENTITY_REGISTRY_ABI,
|
|
348
|
+
functionName: "setAgentURI",
|
|
349
|
+
args: [BigInt(tokenId), `ipfs://${ipfsCid}`],
|
|
350
|
+
});
|
|
351
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
352
|
+
txHash = hash;
|
|
353
|
+
agentId = existingAgentId;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
// First deploy: register then set URI
|
|
357
|
+
const registerHash = await walletClient.writeContract({
|
|
358
|
+
address: registryAddress,
|
|
359
|
+
abi: erc8004_abi_js_1.IDENTITY_REGISTRY_ABI,
|
|
360
|
+
functionName: "register",
|
|
361
|
+
args: [],
|
|
362
|
+
});
|
|
363
|
+
const registerReceipt = await publicClient.waitForTransactionReceipt({
|
|
364
|
+
hash: registerHash,
|
|
365
|
+
});
|
|
366
|
+
// Extract agentId from Registered or Transfer event
|
|
367
|
+
const tokenId = this.extractAgentIdFromReceipt(registerReceipt.logs, registryAddress);
|
|
368
|
+
agentId = `${chainId}:${tokenId}`;
|
|
369
|
+
// Set the IPFS URI
|
|
370
|
+
const uriHash = await walletClient.writeContract({
|
|
371
|
+
address: registryAddress,
|
|
372
|
+
abi: erc8004_abi_js_1.IDENTITY_REGISTRY_ABI,
|
|
373
|
+
functionName: "setAgentURI",
|
|
374
|
+
args: [BigInt(tokenId), `ipfs://${ipfsCid}`],
|
|
375
|
+
});
|
|
376
|
+
await publicClient.waitForTransactionReceipt({ hash: uriHash });
|
|
377
|
+
txHash = uriHash;
|
|
378
|
+
}
|
|
379
|
+
// 7. Save final state
|
|
380
|
+
await this.deploy({
|
|
381
|
+
workflowId,
|
|
382
|
+
erc8004AgentId: agentId,
|
|
383
|
+
stringifiedAgentCard: agentCardJson,
|
|
384
|
+
latestDeploymentTransactionHash: txHash,
|
|
385
|
+
latestDeploymentTimestamp: new Date(),
|
|
386
|
+
...(wallet.address ? { walletAddress: wallet.address } : {}),
|
|
387
|
+
network: "base",
|
|
388
|
+
chainId,
|
|
389
|
+
rpcUrl,
|
|
390
|
+
});
|
|
391
|
+
const blockExplorerUrl = chainConfig
|
|
392
|
+
? `${chainConfig.blockExplorerUrl}/tx/${txHash}`
|
|
393
|
+
: `https://basescan.org/tx/${txHash}`;
|
|
394
|
+
const network = chainConfig?.network ?? "base";
|
|
395
|
+
const tokenId = agentId.split(":")[1];
|
|
396
|
+
const scanUrl = `https://www.8004scan.io/agents/${network}/${tokenId}`;
|
|
397
|
+
return {
|
|
398
|
+
agentId,
|
|
399
|
+
ipfsCid,
|
|
400
|
+
txHash,
|
|
401
|
+
agentCardUrl: `https://gateway.pinata.cloud/ipfs/${ipfsCid}`,
|
|
402
|
+
blockExplorerUrl,
|
|
403
|
+
scanUrl,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Upload a JSON string to IPFS using a presigned Pinata URL.
|
|
408
|
+
*/
|
|
409
|
+
async uploadToIpfs(jsonContent, presignedUrl) {
|
|
410
|
+
const pinata = new pinata_1.PinataSDK({
|
|
411
|
+
pinataJwt: "",
|
|
412
|
+
pinataGateway: "gateway.pinata.cloud",
|
|
413
|
+
});
|
|
414
|
+
const file = new File([jsonContent], "registration.json", {
|
|
415
|
+
type: "application/json",
|
|
416
|
+
});
|
|
417
|
+
const response = await pinata.upload.public.file(file).url(presignedUrl);
|
|
418
|
+
const cid = response.cid;
|
|
419
|
+
if (!cid) {
|
|
420
|
+
throw new Error(`No CID returned from Pinata. Response: ${JSON.stringify(response)}`);
|
|
421
|
+
}
|
|
422
|
+
return cid;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Extract the agent token ID from a registration transaction receipt.
|
|
426
|
+
* Looks for the Registered event first, then falls back to Transfer event.
|
|
427
|
+
*/
|
|
428
|
+
extractAgentIdFromReceipt(logs, registryAddress) {
|
|
429
|
+
// Try Registered event first
|
|
430
|
+
for (const log of logs) {
|
|
431
|
+
if (log.address.toLowerCase() !== registryAddress.toLowerCase())
|
|
432
|
+
continue;
|
|
433
|
+
try {
|
|
434
|
+
const decoded = (0, viem_1.decodeEventLog)({
|
|
435
|
+
abi: erc8004_abi_js_1.IDENTITY_REGISTRY_ABI,
|
|
436
|
+
data: log.data,
|
|
437
|
+
topics: log.topics,
|
|
438
|
+
});
|
|
439
|
+
if (decoded.eventName === "Registered") {
|
|
440
|
+
const args = decoded.args;
|
|
441
|
+
return args.agentId.toString();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// Not this event, try next
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Fallback to Transfer event (ERC-721 mint: from=0x0)
|
|
449
|
+
for (const log of logs) {
|
|
450
|
+
if (log.address.toLowerCase() !== registryAddress.toLowerCase())
|
|
451
|
+
continue;
|
|
452
|
+
try {
|
|
453
|
+
const decoded = (0, viem_1.decodeEventLog)({
|
|
454
|
+
abi: erc8004_abi_js_1.IDENTITY_REGISTRY_ABI,
|
|
455
|
+
data: log.data,
|
|
456
|
+
topics: log.topics,
|
|
457
|
+
});
|
|
458
|
+
if (decoded.eventName === "Transfer") {
|
|
459
|
+
const args = decoded.args;
|
|
460
|
+
return args.tokenId.toString();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
// Not this event, try next
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
throw new Error("Could not extract agent ID from transaction receipt. " +
|
|
468
|
+
"No Registered or Transfer event found.");
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Get a presigned IPFS URL for uploading an agent registration file.
|
|
472
|
+
*
|
|
473
|
+
* The returned URL is a signed Pinata upload URL that expires in 60 seconds.
|
|
474
|
+
* Use it to upload the agent's registration file (agent card) to IPFS before
|
|
475
|
+
* on-chain registration.
|
|
476
|
+
*
|
|
477
|
+
* @param params - Parameters
|
|
478
|
+
* @param params.workflowId - The workflow (workspace) ID
|
|
479
|
+
* @returns Object containing the presigned IPFS upload URL
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* const { url } = await client.erc8004.presignIpfsUrl({ workflowId: 123 });
|
|
484
|
+
* // Use the URL to upload agent card to IPFS within 60 seconds
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
async presignIpfsUrl(params) {
|
|
488
|
+
return this.client.put(`/workspaces/${params.workflowId}/erc-8004/presign-ipfs-url`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
exports.Erc8004API = Erc8004API;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-8004 contract addresses and chain configuration.
|
|
3
|
+
*
|
|
4
|
+
* Contract addresses sourced from the official erc-8004-contracts repository:
|
|
5
|
+
* https://github.com/erc-8004/erc-8004-contracts
|
|
6
|
+
*
|
|
7
|
+
* All mainnets share the same contract addresses.
|
|
8
|
+
* All testnets share the same contract addresses.
|
|
9
|
+
*/
|
|
10
|
+
/** Contract addresses shared across all ERC-8004 mainnets */
|
|
11
|
+
export declare const ERC8004_MAINNET_CONTRACTS: {
|
|
12
|
+
readonly IDENTITY_REGISTRY: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
|
|
13
|
+
readonly REPUTATION_REGISTRY: "0x8004BAa17C55a88189AE136b182e5fdA19dE9b63";
|
|
14
|
+
};
|
|
15
|
+
/** Contract addresses shared across all ERC-8004 testnets */
|
|
16
|
+
export declare const ERC8004_TESTNET_CONTRACTS: {
|
|
17
|
+
readonly IDENTITY_REGISTRY: "0x8004A818BFB912233c491871b3d84c89A494BD9e";
|
|
18
|
+
readonly REPUTATION_REGISTRY: "0x8004B663056A597Dffe9eCcC1965A193B7388713";
|
|
19
|
+
};
|
|
20
|
+
/** Configuration for an ERC-8004 supported chain */
|
|
21
|
+
export interface Erc8004ChainConfig {
|
|
22
|
+
/** Numeric chain ID */
|
|
23
|
+
chainId: number;
|
|
24
|
+
/** Human-readable network name */
|
|
25
|
+
network: string;
|
|
26
|
+
/** Display name */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Whether this is a testnet */
|
|
29
|
+
testnet: boolean;
|
|
30
|
+
/** Default public RPC URL */
|
|
31
|
+
rpcUrl: string;
|
|
32
|
+
/** Block explorer base URL */
|
|
33
|
+
blockExplorerUrl: string;
|
|
34
|
+
/** ERC-8004 contract addresses */
|
|
35
|
+
contracts: {
|
|
36
|
+
IDENTITY_REGISTRY: string;
|
|
37
|
+
REPUTATION_REGISTRY: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* All chains where ERC-8004 contracts are deployed.
|
|
42
|
+
*
|
|
43
|
+
* Source: https://github.com/erc-8004/erc-8004-contracts
|
|
44
|
+
*/
|
|
45
|
+
export declare const ERC8004_CHAINS: Record<number, Erc8004ChainConfig>;
|
|
46
|
+
/**
|
|
47
|
+
* Get the ERC-8004 chain configuration for a given chain ID.
|
|
48
|
+
*
|
|
49
|
+
* @param chainId - The numeric chain ID
|
|
50
|
+
* @returns The chain configuration, or undefined if the chain is not supported
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const baseConfig = getErc8004Chain(8453);
|
|
55
|
+
* console.log(baseConfig?.contracts.IDENTITY_REGISTRY);
|
|
56
|
+
* // "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432"
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function getErc8004Chain(chainId: number): Erc8004ChainConfig | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Get the ERC-8004 contract addresses for a given chain ID.
|
|
62
|
+
*
|
|
63
|
+
* @param chainId - The numeric chain ID
|
|
64
|
+
* @returns The contract addresses
|
|
65
|
+
* @throws Error if the chain ID is not supported
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const contracts = getErc8004Contracts(8453);
|
|
70
|
+
* console.log(contracts.IDENTITY_REGISTRY);
|
|
71
|
+
* // "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432"
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function getErc8004Contracts(chainId: number): {
|
|
75
|
+
IDENTITY_REGISTRY: string;
|
|
76
|
+
REPUTATION_REGISTRY: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* List all supported ERC-8004 chain IDs.
|
|
80
|
+
*
|
|
81
|
+
* @param filter - Optional filter: "mainnet", "testnet", or undefined for all
|
|
82
|
+
* @returns Array of supported chain IDs
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const allChains = listErc8004ChainIds();
|
|
87
|
+
* const mainnets = listErc8004ChainIds("mainnet");
|
|
88
|
+
* const testnets = listErc8004ChainIds("testnet");
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function listErc8004ChainIds(filter?: "mainnet" | "testnet"): number[];
|
|
92
|
+
//# sourceMappingURL=erc8004-contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"erc8004-contracts.d.ts","sourceRoot":"","sources":["../src/erc8004-contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB;;;CAG5B,CAAC;AAEX,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB;;;CAG5B,CAAC;AAMX,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,SAAS,EAAE;QACT,iBAAiB,EAAE,MAAM,CAAC;QAC1B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAwJpD,CAAC;AAMX;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,SAAS,CAEhC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG;IACpD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CASA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,EAAE,CAQ5E"}
|