@andrewkimjoseph/celina-sdk 0.2.12 → 0.3.1
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 +3 -0
- package/build/abis/self-registry.d.ts +194 -0
- package/build/abis/self-registry.js +120 -0
- package/build/abis/self-registry.js.map +1 -0
- package/build/analytics/amplitude.d.ts +5 -0
- package/build/analytics/amplitude.js +56 -0
- package/build/analytics/amplitude.js.map +1 -0
- package/build/analytics/config.d.ts +7 -0
- package/build/analytics/config.js +30 -0
- package/build/analytics/config.js.map +1 -0
- package/build/analytics/mcp-tool-events.d.ts +5 -0
- package/build/analytics/mcp-tool-events.js +55 -0
- package/build/analytics/mcp-tool-events.js.map +1 -0
- package/build/analytics/wrap-service.d.ts +5 -0
- package/build/analytics/wrap-service.js +36 -0
- package/build/analytics/wrap-service.js.map +1 -0
- package/build/clients/carbon-rest.d.ts +17 -0
- package/build/clients/carbon-rest.js +59 -0
- package/build/clients/carbon-rest.js.map +1 -0
- package/build/clients/carbon-sdk.d.ts +31 -0
- package/build/clients/carbon-sdk.js +48 -0
- package/build/clients/carbon-sdk.js.map +1 -0
- package/build/clients/self-api.d.ts +82 -0
- package/build/clients/self-api.js +242 -0
- package/build/clients/self-api.js.map +1 -0
- package/build/config/carbon.d.ts +12 -0
- package/build/config/carbon.js +13 -0
- package/build/config/carbon.js.map +1 -0
- package/build/config/sdk-config.d.ts +14 -0
- package/build/config/sdk-config.js +16 -0
- package/build/config/sdk-config.js.map +1 -1
- package/build/config/self.d.ts +22 -0
- package/build/config/self.js +27 -0
- package/build/config/self.js.map +1 -0
- package/build/index.d.ts +16 -0
- package/build/index.js +27 -13
- package/build/index.js.map +1 -1
- package/build/services/carbon.service.d.ts +68 -0
- package/build/services/carbon.service.js +189 -0
- package/build/services/carbon.service.js.map +1 -0
- package/build/services/self-session-store.d.ts +19 -0
- package/build/services/self-session-store.js +28 -0
- package/build/services/self-session-store.js.map +1 -0
- package/build/services/self.service.d.ts +241 -0
- package/build/services/self.service.js +692 -0
- package/build/services/self.service.js.map +1 -0
- package/build/types/carbon.d.ts +31 -0
- package/build/types/carbon.js +2 -0
- package/build/types/carbon.js.map +1 -0
- package/build/utils/carbon-prepared-flow.d.ts +11 -0
- package/build/utils/carbon-prepared-flow.js +25 -0
- package/build/utils/carbon-prepared-flow.js.map +1 -0
- package/build/utils/carbon-rest-adapter.d.ts +8 -0
- package/build/utils/carbon-rest-adapter.js +72 -0
- package/build/utils/carbon-rest-adapter.js.map +1 -0
- package/build/utils/self-format.d.ts +28 -0
- package/build/utils/self-format.js +79 -0
- package/build/utils/self-format.js.map +1 -0
- package/build/utils/self-signing.d.ts +4 -0
- package/build/utils/self-signing.js +38 -0
- package/build/utils/self-signing.js.map +1 -0
- package/package.json +7 -2
- package/tests/catalog/domains/carbon.ts +409 -0
- package/tests/catalog/domains/self.ts +63 -4
- package/tests/catalog/operations.ts +2 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import * as ed from "@noble/ed25519";
|
|
2
|
+
import { concat, getAddress, hexToBytes, isAddressEqual, keccak256, pad, recoverMessageAddress, zeroAddress, } from "viem";
|
|
3
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
4
|
+
import { selfRegistryAbi } from "../abis/self-registry.js";
|
|
5
|
+
import { getAgentInfo, requestDeregistration, requestProofRefresh, requestRegistration, SelfApiError, SelfExpiredSessionError, } from "../clients/self-api.js";
|
|
6
|
+
import { SELF_DEFAULT_MAX_AGE_MS, SELF_DEMO_NETWORK, SELF_FETCH_MAX_BYTES, SELF_HEADERS, SELF_REGISTRY_ADDRESS, selfDemoUrl, } from "../config/self.js";
|
|
7
|
+
import { CELINA_DATA_SUFFIX } from "../config/celina-tag.js";
|
|
8
|
+
import { deleteSelfSession, getSelfSession, storeSelfSession, } from "./self-session-store.js";
|
|
9
|
+
import { formatAgentInfo, formatCredentialsSummary, proofExpiryFields, resolveSelfSessionLinks, truncateBody, } from "../utils/self-format.js";
|
|
10
|
+
import { computeSigningMessage } from "../utils/self-signing.js";
|
|
11
|
+
function agentKeyFromAddress(address) {
|
|
12
|
+
return pad(address, { size: 32 });
|
|
13
|
+
}
|
|
14
|
+
function buildSelfSessionPayload(session, extra = {}, apiBase) {
|
|
15
|
+
const { qr_code_url, deep_link } = resolveSelfSessionLinks({
|
|
16
|
+
sessionToken: session.sessionToken,
|
|
17
|
+
deepLink: session.deepLink,
|
|
18
|
+
scanUrl: session.scanUrl,
|
|
19
|
+
apiBase,
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
session_id: session.sessionToken,
|
|
23
|
+
qr_code_url,
|
|
24
|
+
qr_url: qr_code_url,
|
|
25
|
+
deep_link,
|
|
26
|
+
expires_at: session.expiresAt,
|
|
27
|
+
instructions: session.humanInstructions.join("\n"),
|
|
28
|
+
...extra,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function normalizeCredentials(raw) {
|
|
32
|
+
return {
|
|
33
|
+
nationality: raw.nationality || undefined,
|
|
34
|
+
older_than: Number(raw.olderThan),
|
|
35
|
+
ofac_clear: raw.ofac[0] === true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export class SelfService {
|
|
39
|
+
clientFactory;
|
|
40
|
+
config;
|
|
41
|
+
constructor(clientFactory, config) {
|
|
42
|
+
this.clientFactory = clientFactory;
|
|
43
|
+
this.config = config;
|
|
44
|
+
}
|
|
45
|
+
get apiBase() {
|
|
46
|
+
return this.config.selfApiBase;
|
|
47
|
+
}
|
|
48
|
+
resolveAgentPrivateKey() {
|
|
49
|
+
if (this.config.selfAgentPrivateKey) {
|
|
50
|
+
return this.config.selfAgentPrivateKey;
|
|
51
|
+
}
|
|
52
|
+
throw new Error("No Self agent key configured. Set SELF_AGENT_PRIVATE_KEY or pass selfAgentPrivateKey to createCelinaClient().");
|
|
53
|
+
}
|
|
54
|
+
registryClient() {
|
|
55
|
+
return this.clientFactory.getClients().public;
|
|
56
|
+
}
|
|
57
|
+
async assertProofRefreshEligible(agentId) {
|
|
58
|
+
const client = this.registryClient();
|
|
59
|
+
const agentIdBigInt = BigInt(agentId);
|
|
60
|
+
const [isProofFresh, proofExpiresAtRaw] = await Promise.all([
|
|
61
|
+
client.readContract({
|
|
62
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
63
|
+
abi: selfRegistryAbi,
|
|
64
|
+
functionName: "isProofFresh",
|
|
65
|
+
args: [agentIdBigInt],
|
|
66
|
+
}),
|
|
67
|
+
client.readContract({
|
|
68
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
69
|
+
abi: selfRegistryAbi,
|
|
70
|
+
functionName: "proofExpiresAt",
|
|
71
|
+
args: [agentIdBigInt],
|
|
72
|
+
}),
|
|
73
|
+
]);
|
|
74
|
+
const expiry = proofExpiryFields(proofExpiresAtRaw);
|
|
75
|
+
const proofExpiresAtSecs = Number(proofExpiresAtRaw);
|
|
76
|
+
const isExpired = !isProofFresh && proofExpiresAtSecs > 0;
|
|
77
|
+
if (isProofFresh) {
|
|
78
|
+
const expiringHint = expiry.is_expiring_soon
|
|
79
|
+
? ` Proof expires in ${expiry.days_until_expiry} days — refresh will be available after expiry.`
|
|
80
|
+
: "";
|
|
81
|
+
throw new Error(`Proof refresh is not available yet for agent #${agentId}. ` +
|
|
82
|
+
`The human proof is still fresh until ${expiry.proof_expires_at ?? "unknown"} ` +
|
|
83
|
+
`(${expiry.days_until_expiry} days remaining).${expiringHint} ` +
|
|
84
|
+
`Self only supports refresh after on-chain proof expiry (isProofFresh is false). ` +
|
|
85
|
+
`Until then, use get_self_identity to monitor expiry; after expiry call refresh_self_proof, ` +
|
|
86
|
+
`or deregister_self_agent then register_self_agent per Self SDK guidance.`);
|
|
87
|
+
}
|
|
88
|
+
if (proofExpiresAtSecs <= 0) {
|
|
89
|
+
throw new Error(`Agent #${agentId} has no on-chain human proof to refresh.`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...expiry,
|
|
93
|
+
is_expired: isExpired,
|
|
94
|
+
is_proof_fresh: isProofFresh,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async verifyAgent(params) {
|
|
98
|
+
const { agentAddress, requireAge = 0, requireOfac = false, requireSelfProvider = true, } = params;
|
|
99
|
+
const agentKey = agentKeyFromAddress(agentAddress);
|
|
100
|
+
const client = this.registryClient();
|
|
101
|
+
const isVerified = await client.readContract({
|
|
102
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
103
|
+
abi: selfRegistryAbi,
|
|
104
|
+
functionName: "isVerifiedAgent",
|
|
105
|
+
args: [agentKey],
|
|
106
|
+
});
|
|
107
|
+
if (!isVerified) {
|
|
108
|
+
return {
|
|
109
|
+
verified: false,
|
|
110
|
+
agent_address: agentAddress,
|
|
111
|
+
reason: "Agent is not registered or not verified on-chain.",
|
|
112
|
+
network: "mainnet",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const agentId = await client.readContract({
|
|
116
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
117
|
+
abi: selfRegistryAbi,
|
|
118
|
+
functionName: "getAgentId",
|
|
119
|
+
args: [agentKey],
|
|
120
|
+
});
|
|
121
|
+
const nullifier = await client.readContract({
|
|
122
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
123
|
+
abi: selfRegistryAbi,
|
|
124
|
+
functionName: "getHumanNullifier",
|
|
125
|
+
args: [agentId],
|
|
126
|
+
});
|
|
127
|
+
const [agentCount, proofProvider, selfProvider, registeredAt, proofExpiresAtRaw, isProofFresh, siblingAgentIds, rawCredentials,] = await Promise.all([
|
|
128
|
+
client.readContract({
|
|
129
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
130
|
+
abi: selfRegistryAbi,
|
|
131
|
+
functionName: "getAgentCountForHuman",
|
|
132
|
+
args: [nullifier],
|
|
133
|
+
}),
|
|
134
|
+
client.readContract({
|
|
135
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
136
|
+
abi: selfRegistryAbi,
|
|
137
|
+
functionName: "getProofProvider",
|
|
138
|
+
args: [agentId],
|
|
139
|
+
}),
|
|
140
|
+
client.readContract({
|
|
141
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
142
|
+
abi: selfRegistryAbi,
|
|
143
|
+
functionName: "selfProofProvider",
|
|
144
|
+
}),
|
|
145
|
+
client.readContract({
|
|
146
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
147
|
+
abi: selfRegistryAbi,
|
|
148
|
+
functionName: "agentRegisteredAt",
|
|
149
|
+
args: [agentId],
|
|
150
|
+
}),
|
|
151
|
+
client.readContract({
|
|
152
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
153
|
+
abi: selfRegistryAbi,
|
|
154
|
+
functionName: "proofExpiresAt",
|
|
155
|
+
args: [agentId],
|
|
156
|
+
}),
|
|
157
|
+
client.readContract({
|
|
158
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
159
|
+
abi: selfRegistryAbi,
|
|
160
|
+
functionName: "isProofFresh",
|
|
161
|
+
args: [agentId],
|
|
162
|
+
}),
|
|
163
|
+
client.readContract({
|
|
164
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
165
|
+
abi: selfRegistryAbi,
|
|
166
|
+
functionName: "getAgentsForNullifier",
|
|
167
|
+
args: [nullifier],
|
|
168
|
+
}),
|
|
169
|
+
client.readContract({
|
|
170
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
171
|
+
abi: selfRegistryAbi,
|
|
172
|
+
functionName: "getAgentCredentials",
|
|
173
|
+
args: [agentId],
|
|
174
|
+
}),
|
|
175
|
+
]);
|
|
176
|
+
const credentials = normalizeCredentials(rawCredentials);
|
|
177
|
+
const expiryFields = {
|
|
178
|
+
...proofExpiryFields(proofExpiresAtRaw),
|
|
179
|
+
sibling_agent_ids: siblingAgentIds.map((id) => Number(id)),
|
|
180
|
+
};
|
|
181
|
+
const failures = [];
|
|
182
|
+
if (requireSelfProvider &&
|
|
183
|
+
!isAddressEqual(proofProvider, selfProvider)) {
|
|
184
|
+
failures.push(`Agent's proof provider (${proofProvider}) does not match Self Protocol provider (${selfProvider}).`);
|
|
185
|
+
}
|
|
186
|
+
if (requireAge > 0 && credentials.older_than < requireAge) {
|
|
187
|
+
failures.push(`Agent's verified age (${credentials.older_than}+) does not meet minimum age requirement (${requireAge}+).`);
|
|
188
|
+
}
|
|
189
|
+
if (requireOfac && !credentials.ofac_clear) {
|
|
190
|
+
failures.push("Agent has not passed OFAC screening.");
|
|
191
|
+
}
|
|
192
|
+
const isSelfProvider = isAddressEqual(proofProvider, selfProvider);
|
|
193
|
+
let verificationStrength = "unknown";
|
|
194
|
+
if (isSelfProvider) {
|
|
195
|
+
verificationStrength = "self-protocol";
|
|
196
|
+
}
|
|
197
|
+
else if (!isAddressEqual(proofProvider, zeroAddress)) {
|
|
198
|
+
verificationStrength = "third-party";
|
|
199
|
+
}
|
|
200
|
+
if (failures.length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
verified: false,
|
|
203
|
+
agent_address: agentAddress,
|
|
204
|
+
agent_id: Number(agentId),
|
|
205
|
+
reason: failures.join(" "),
|
|
206
|
+
credentials,
|
|
207
|
+
...expiryFields,
|
|
208
|
+
verification_strength: verificationStrength,
|
|
209
|
+
network: "mainnet",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
verified: true,
|
|
214
|
+
agent_address: agentAddress,
|
|
215
|
+
agent_id: Number(agentId),
|
|
216
|
+
credentials,
|
|
217
|
+
...expiryFields,
|
|
218
|
+
verification_strength: verificationStrength,
|
|
219
|
+
registered_at: Number(registeredAt),
|
|
220
|
+
agent_count: Number(agentCount),
|
|
221
|
+
is_proof_fresh: isProofFresh,
|
|
222
|
+
network: "mainnet",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
async lookupAgent(agentId) {
|
|
226
|
+
const info = await getAgentInfo(agentId, this.apiBase);
|
|
227
|
+
const formatted = formatAgentInfo(info);
|
|
228
|
+
const credentialsSummary = formatCredentialsSummary(info.credentials);
|
|
229
|
+
const client = this.registryClient();
|
|
230
|
+
const [proofExpiresAtRaw, isProofFresh] = await Promise.all([
|
|
231
|
+
client.readContract({
|
|
232
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
233
|
+
abi: selfRegistryAbi,
|
|
234
|
+
functionName: "proofExpiresAt",
|
|
235
|
+
args: [BigInt(agentId)],
|
|
236
|
+
}),
|
|
237
|
+
client.readContract({
|
|
238
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
239
|
+
abi: selfRegistryAbi,
|
|
240
|
+
functionName: "isProofFresh",
|
|
241
|
+
args: [BigInt(agentId)],
|
|
242
|
+
}),
|
|
243
|
+
]);
|
|
244
|
+
const proofExpiresAtSecs = Number(proofExpiresAtRaw);
|
|
245
|
+
return {
|
|
246
|
+
...formatted,
|
|
247
|
+
credentialsSummary,
|
|
248
|
+
...proofExpiryFields(proofExpiresAtRaw),
|
|
249
|
+
is_expired: !isProofFresh && proofExpiresAtSecs > 0,
|
|
250
|
+
network: "mainnet",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
async verifyRequest(params) {
|
|
254
|
+
const { agentSignature, agentTimestamp, method, path, body, keytype, agentKey, } = params;
|
|
255
|
+
const ts = parseInt(agentTimestamp, 10);
|
|
256
|
+
if (Number.isNaN(ts) || Math.abs(Date.now() - ts) > SELF_DEFAULT_MAX_AGE_MS) {
|
|
257
|
+
return {
|
|
258
|
+
valid: false,
|
|
259
|
+
reason: "Timestamp expired or invalid",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const message = computeSigningMessage(agentTimestamp, method, path, body);
|
|
263
|
+
let signerAddress;
|
|
264
|
+
let derivedAgentKey;
|
|
265
|
+
if (keytype === "ed25519") {
|
|
266
|
+
if (!agentKey) {
|
|
267
|
+
return { valid: false, reason: "Missing agent key for Ed25519 verification" };
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const valid = await ed.verifyAsync(hexToBytes(agentSignature), hexToBytes(message), hexToBytes(agentKey));
|
|
271
|
+
if (!valid) {
|
|
272
|
+
return { valid: false, reason: "Invalid Ed25519 signature" };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return { valid: false, reason: "Invalid Ed25519 signature" };
|
|
277
|
+
}
|
|
278
|
+
derivedAgentKey = agentKey;
|
|
279
|
+
const hash = keccak256(hexToBytes(agentKey));
|
|
280
|
+
signerAddress = getAddress(`0x${hash.slice(-40)}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
try {
|
|
284
|
+
signerAddress = await recoverMessageAddress({
|
|
285
|
+
message: { raw: hexToBytes(message) },
|
|
286
|
+
signature: agentSignature,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return { valid: false, reason: "Invalid signature" };
|
|
291
|
+
}
|
|
292
|
+
derivedAgentKey = agentKeyFromAddress(signerAddress);
|
|
293
|
+
}
|
|
294
|
+
const client = this.registryClient();
|
|
295
|
+
const isVerified = await client.readContract({
|
|
296
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
297
|
+
abi: selfRegistryAbi,
|
|
298
|
+
functionName: "isVerifiedAgent",
|
|
299
|
+
args: [derivedAgentKey],
|
|
300
|
+
});
|
|
301
|
+
if (!isVerified) {
|
|
302
|
+
return {
|
|
303
|
+
valid: false,
|
|
304
|
+
agent_address: signerAddress,
|
|
305
|
+
reason: "Agent not verified on-chain",
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const agentId = await client.readContract({
|
|
309
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
310
|
+
abi: selfRegistryAbi,
|
|
311
|
+
functionName: "getAgentId",
|
|
312
|
+
args: [derivedAgentKey],
|
|
313
|
+
});
|
|
314
|
+
const nullifier = await client.readContract({
|
|
315
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
316
|
+
abi: selfRegistryAbi,
|
|
317
|
+
functionName: "getHumanNullifier",
|
|
318
|
+
args: [agentId],
|
|
319
|
+
});
|
|
320
|
+
const [isProofFresh, agentCount, proofProvider, selfProvider, rawCredentials] = await Promise.all([
|
|
321
|
+
client.readContract({
|
|
322
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
323
|
+
abi: selfRegistryAbi,
|
|
324
|
+
functionName: "isProofFresh",
|
|
325
|
+
args: [agentId],
|
|
326
|
+
}),
|
|
327
|
+
client.readContract({
|
|
328
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
329
|
+
abi: selfRegistryAbi,
|
|
330
|
+
functionName: "getAgentCountForHuman",
|
|
331
|
+
args: [nullifier],
|
|
332
|
+
}),
|
|
333
|
+
client.readContract({
|
|
334
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
335
|
+
abi: selfRegistryAbi,
|
|
336
|
+
functionName: "getProofProvider",
|
|
337
|
+
args: [agentId],
|
|
338
|
+
}),
|
|
339
|
+
client.readContract({
|
|
340
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
341
|
+
abi: selfRegistryAbi,
|
|
342
|
+
functionName: "selfProofProvider",
|
|
343
|
+
}),
|
|
344
|
+
client.readContract({
|
|
345
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
346
|
+
abi: selfRegistryAbi,
|
|
347
|
+
functionName: "getAgentCredentials",
|
|
348
|
+
args: [agentId],
|
|
349
|
+
}),
|
|
350
|
+
]);
|
|
351
|
+
if (!isProofFresh) {
|
|
352
|
+
return {
|
|
353
|
+
valid: false,
|
|
354
|
+
agent_address: signerAddress,
|
|
355
|
+
agent_id: Number(agentId),
|
|
356
|
+
reason: "Agent's human proof has expired",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (!isAddressEqual(proofProvider, selfProvider)) {
|
|
360
|
+
return {
|
|
361
|
+
valid: false,
|
|
362
|
+
agent_address: signerAddress,
|
|
363
|
+
agent_id: Number(agentId),
|
|
364
|
+
reason: "Agent was not verified by Self — proof provider mismatch",
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const credentials = normalizeCredentials(rawCredentials);
|
|
368
|
+
return {
|
|
369
|
+
valid: true,
|
|
370
|
+
agent_address: signerAddress,
|
|
371
|
+
agent_id: Number(agentId),
|
|
372
|
+
agent_count: Number(agentCount),
|
|
373
|
+
nullifier: Number(nullifier),
|
|
374
|
+
credentials,
|
|
375
|
+
note: "Replay protection is not enforced at the MCP layer. If you are building a service, implement your own nonce or replay cache.",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async getAgentInfoFromKey(privateKey) {
|
|
379
|
+
const account = privateKeyToAccount(privateKey);
|
|
380
|
+
const agentKey = agentKeyFromAddress(account.address);
|
|
381
|
+
const client = this.registryClient();
|
|
382
|
+
const agentId = await client.readContract({
|
|
383
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
384
|
+
abi: selfRegistryAbi,
|
|
385
|
+
functionName: "getAgentId",
|
|
386
|
+
args: [agentKey],
|
|
387
|
+
});
|
|
388
|
+
if (agentId === 0n) {
|
|
389
|
+
return {
|
|
390
|
+
registered: false,
|
|
391
|
+
address: account.address,
|
|
392
|
+
network: "mainnet",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const nullifier = await client.readContract({
|
|
396
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
397
|
+
abi: selfRegistryAbi,
|
|
398
|
+
functionName: "getHumanNullifier",
|
|
399
|
+
args: [agentId],
|
|
400
|
+
});
|
|
401
|
+
const [isVerified, agentCount, proofExpiresAt, isProofFresh, siblingAgentIds, proofProvider, selfProvider, rawCredentials,] = await Promise.all([
|
|
402
|
+
client.readContract({
|
|
403
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
404
|
+
abi: selfRegistryAbi,
|
|
405
|
+
functionName: "hasHumanProof",
|
|
406
|
+
args: [agentId],
|
|
407
|
+
}),
|
|
408
|
+
client.readContract({
|
|
409
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
410
|
+
abi: selfRegistryAbi,
|
|
411
|
+
functionName: "getAgentCountForHuman",
|
|
412
|
+
args: [nullifier],
|
|
413
|
+
}),
|
|
414
|
+
client.readContract({
|
|
415
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
416
|
+
abi: selfRegistryAbi,
|
|
417
|
+
functionName: "proofExpiresAt",
|
|
418
|
+
args: [agentId],
|
|
419
|
+
}),
|
|
420
|
+
client.readContract({
|
|
421
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
422
|
+
abi: selfRegistryAbi,
|
|
423
|
+
functionName: "isProofFresh",
|
|
424
|
+
args: [agentId],
|
|
425
|
+
}),
|
|
426
|
+
client.readContract({
|
|
427
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
428
|
+
abi: selfRegistryAbi,
|
|
429
|
+
functionName: "getAgentsForNullifier",
|
|
430
|
+
args: [nullifier],
|
|
431
|
+
}),
|
|
432
|
+
client.readContract({
|
|
433
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
434
|
+
abi: selfRegistryAbi,
|
|
435
|
+
functionName: "getProofProvider",
|
|
436
|
+
args: [agentId],
|
|
437
|
+
}),
|
|
438
|
+
client.readContract({
|
|
439
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
440
|
+
abi: selfRegistryAbi,
|
|
441
|
+
functionName: "selfProofProvider",
|
|
442
|
+
}),
|
|
443
|
+
client.readContract({
|
|
444
|
+
address: SELF_REGISTRY_ADDRESS,
|
|
445
|
+
abi: selfRegistryAbi,
|
|
446
|
+
functionName: "getAgentCredentials",
|
|
447
|
+
args: [agentId],
|
|
448
|
+
}),
|
|
449
|
+
]);
|
|
450
|
+
const credentialsSummary = formatCredentialsSummary({
|
|
451
|
+
nationality: rawCredentials.nationality,
|
|
452
|
+
olderThan: rawCredentials.olderThan,
|
|
453
|
+
ofac: [...rawCredentials.ofac],
|
|
454
|
+
});
|
|
455
|
+
const expiry = proofExpiryFields(proofExpiresAt);
|
|
456
|
+
const proofExpiresAtSecs = Number(proofExpiresAt);
|
|
457
|
+
const isExpired = !isProofFresh && proofExpiresAtSecs > 0;
|
|
458
|
+
let verificationStrength = "unknown";
|
|
459
|
+
if (isAddressEqual(proofProvider, selfProvider)) {
|
|
460
|
+
verificationStrength = "self-protocol";
|
|
461
|
+
}
|
|
462
|
+
else if (!isAddressEqual(proofProvider, zeroAddress)) {
|
|
463
|
+
verificationStrength = "third-party";
|
|
464
|
+
}
|
|
465
|
+
let expiryWarning;
|
|
466
|
+
if (isExpired) {
|
|
467
|
+
expiryWarning =
|
|
468
|
+
"Your proof has EXPIRED. Call refresh_self_proof to renew it, or deregister_self_agent then register_self_agent.";
|
|
469
|
+
}
|
|
470
|
+
else if (expiry.is_expiring_soon) {
|
|
471
|
+
expiryWarning = `Your proof expires in ${expiry.days_until_expiry} days. After expiry, call refresh_self_proof or deregister and re-register.`;
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
registered: true,
|
|
475
|
+
address: account.address,
|
|
476
|
+
agentKey,
|
|
477
|
+
agentId: Number(agentId),
|
|
478
|
+
isVerified,
|
|
479
|
+
nullifier: Number(nullifier),
|
|
480
|
+
agentCount: Number(agentCount),
|
|
481
|
+
verificationStrength,
|
|
482
|
+
credentials_summary: credentialsSummary,
|
|
483
|
+
...expiry,
|
|
484
|
+
is_expired: isExpired,
|
|
485
|
+
sibling_agent_ids: siblingAgentIds.map((id) => Number(id)),
|
|
486
|
+
network: "mainnet",
|
|
487
|
+
...(expiryWarning ? { expiry_warning: expiryWarning } : {}),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
async getIdentity() {
|
|
491
|
+
const privateKey = this.resolveAgentPrivateKey();
|
|
492
|
+
const info = await this.getAgentInfoFromKey(privateKey);
|
|
493
|
+
if (!info.registered) {
|
|
494
|
+
return {
|
|
495
|
+
...info,
|
|
496
|
+
message: "This agent address is not registered on-chain. Use register_self_agent to register, or lookup_self_agent to check a different agent.",
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return info;
|
|
500
|
+
}
|
|
501
|
+
async registerAgent(params = {}) {
|
|
502
|
+
const disclosures = {
|
|
503
|
+
...(params.minimumAge != null ? { minimumAge: params.minimumAge } : {}),
|
|
504
|
+
...(params.ofac != null ? { ofac: params.ofac } : {}),
|
|
505
|
+
};
|
|
506
|
+
const session = await requestRegistration({
|
|
507
|
+
mode: params.mode,
|
|
508
|
+
disclosures,
|
|
509
|
+
humanAddress: params.humanAddress,
|
|
510
|
+
agentName: params.agentName,
|
|
511
|
+
agentDescription: params.agentDescription,
|
|
512
|
+
apiBase: this.apiBase,
|
|
513
|
+
});
|
|
514
|
+
storeSelfSession(session.sessionToken, {
|
|
515
|
+
kind: "registration",
|
|
516
|
+
session,
|
|
517
|
+
createdAt: Date.now(),
|
|
518
|
+
});
|
|
519
|
+
return buildSelfSessionPayload(session, {
|
|
520
|
+
agent_address: session.agentAddress,
|
|
521
|
+
next_step: "Have the human scan the QR/deep link, then call check_self_registration with this session_id. The private key will be returned only after verification completes.",
|
|
522
|
+
}, this.apiBase);
|
|
523
|
+
}
|
|
524
|
+
async checkRegistration(sessionId) {
|
|
525
|
+
const entry = getSelfSession(sessionId);
|
|
526
|
+
if (!entry) {
|
|
527
|
+
throw new Error(`No active session found for "${sessionId}". Sessions expire after 10 minutes and are lost when the server restarts. Use register_self_agent or refresh_self_proof to start a new session.`);
|
|
528
|
+
}
|
|
529
|
+
const isRefresh = entry.kind === "refresh";
|
|
530
|
+
const isDeregister = entry.kind === "deregistration";
|
|
531
|
+
try {
|
|
532
|
+
const result = await entry.session.waitForCompletion({
|
|
533
|
+
timeoutMs: 5000,
|
|
534
|
+
pollIntervalMs: 2000,
|
|
535
|
+
});
|
|
536
|
+
if (isDeregister) {
|
|
537
|
+
deleteSelfSession(sessionId);
|
|
538
|
+
return {
|
|
539
|
+
status: "deregistered",
|
|
540
|
+
message: "Agent deregistered successfully.",
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
if (isRefresh) {
|
|
544
|
+
deleteSelfSession(sessionId);
|
|
545
|
+
const refreshResult = result;
|
|
546
|
+
return {
|
|
547
|
+
status: "refreshed",
|
|
548
|
+
proof_expires_at: refreshResult.proofExpiresAt.toISOString(),
|
|
549
|
+
message: "Proof refreshed successfully! The agent's human proof has been renewed.",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const registrationSession = entry.session;
|
|
553
|
+
let privateKeyHex;
|
|
554
|
+
try {
|
|
555
|
+
privateKeyHex = await registrationSession.exportKey();
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
throw new Error(`Registration completed, but key export is not available yet: ${error instanceof Error ? error.message : String(error)}. Call check_self_registration again in a few seconds.`);
|
|
559
|
+
}
|
|
560
|
+
deleteSelfSession(sessionId);
|
|
561
|
+
const registrationResult = result;
|
|
562
|
+
return {
|
|
563
|
+
status: "verified",
|
|
564
|
+
agent_id: registrationResult.agentId,
|
|
565
|
+
agent_address: registrationResult.agentAddress,
|
|
566
|
+
private_key_hex: privateKeyHex,
|
|
567
|
+
tx_hash: registrationResult.txHash,
|
|
568
|
+
message: "Agent registered successfully! Set SELF_AGENT_PRIVATE_KEY to private_key_hex to use this identity.",
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
573
|
+
const lowerMessage = message.toLowerCase();
|
|
574
|
+
if (lowerMessage.includes("timeout") ||
|
|
575
|
+
lowerMessage.includes("did not complete within")) {
|
|
576
|
+
return {
|
|
577
|
+
status: "pending",
|
|
578
|
+
message: isDeregister
|
|
579
|
+
? "Deregistration not yet complete. The human has not scanned the QR code yet. Call check_self_registration again to keep polling."
|
|
580
|
+
: isRefresh
|
|
581
|
+
? "Proof refresh not yet complete. The human has not scanned the QR code yet. Call check_self_registration again to keep polling."
|
|
582
|
+
: "Registration not yet complete. The human has not scanned the QR code yet. Call check_self_registration again to keep polling.",
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
if (error instanceof SelfExpiredSessionError) {
|
|
586
|
+
deleteSelfSession(sessionId);
|
|
587
|
+
return {
|
|
588
|
+
status: "expired",
|
|
589
|
+
message: isDeregister
|
|
590
|
+
? "Deregistration session expired. Use deregister_self_agent to start again."
|
|
591
|
+
: isRefresh
|
|
592
|
+
? "Refresh session expired. Use refresh_self_proof to start a new refresh."
|
|
593
|
+
: "Registration session expired. Use register_self_agent to start a new registration.",
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (error instanceof SelfApiError) {
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
throw error instanceof Error ? error : new Error(message);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async refreshProof(params = {}) {
|
|
603
|
+
let agentId = params.agentId;
|
|
604
|
+
if (agentId == null) {
|
|
605
|
+
const privateKey = this.resolveAgentPrivateKey();
|
|
606
|
+
const info = await this.getAgentInfoFromKey(privateKey);
|
|
607
|
+
if (!info.registered) {
|
|
608
|
+
throw new Error("Configured agent is not registered on-chain. Provide agent_id explicitly.");
|
|
609
|
+
}
|
|
610
|
+
agentId = info.agentId;
|
|
611
|
+
}
|
|
612
|
+
const proofStatus = await this.assertProofRefreshEligible(agentId);
|
|
613
|
+
const session = await requestProofRefresh({ agentId, apiBase: this.apiBase });
|
|
614
|
+
storeSelfSession(session.sessionToken, {
|
|
615
|
+
kind: "refresh",
|
|
616
|
+
session,
|
|
617
|
+
createdAt: Date.now(),
|
|
618
|
+
});
|
|
619
|
+
return buildSelfSessionPayload(session, {
|
|
620
|
+
agent_id: agentId,
|
|
621
|
+
...proofStatus,
|
|
622
|
+
next_step: "Have the human scan the QR/deep link with the Self app, then call check_self_registration with this session_id to poll for completion.",
|
|
623
|
+
}, this.apiBase);
|
|
624
|
+
}
|
|
625
|
+
async deregisterAgent() {
|
|
626
|
+
const privateKey = this.resolveAgentPrivateKey();
|
|
627
|
+
const account = privateKeyToAccount(privateKey);
|
|
628
|
+
const session = await requestDeregistration({
|
|
629
|
+
agentAddress: account.address,
|
|
630
|
+
apiBase: this.apiBase,
|
|
631
|
+
});
|
|
632
|
+
storeSelfSession(session.sessionToken, {
|
|
633
|
+
kind: "deregistration",
|
|
634
|
+
session,
|
|
635
|
+
createdAt: Date.now(),
|
|
636
|
+
});
|
|
637
|
+
return buildSelfSessionPayload(session, {
|
|
638
|
+
warning: "WARNING: Deregistration is IRREVERSIBLE. The agent's on-chain identity will be permanently revoked. The human owner must scan the QR code with the Self app to confirm.",
|
|
639
|
+
next_step: "After the human scans the QR code, call check_self_registration with this session_id to poll for completion.",
|
|
640
|
+
}, this.apiBase);
|
|
641
|
+
}
|
|
642
|
+
async signRequest(params) {
|
|
643
|
+
const privateKey = this.resolveAgentPrivateKey();
|
|
644
|
+
const account = privateKeyToAccount(privateKey);
|
|
645
|
+
const timestamp = Date.now().toString();
|
|
646
|
+
const message = computeSigningMessage(timestamp, params.method, params.url, params.body);
|
|
647
|
+
const signature = await account.signMessage({
|
|
648
|
+
message: { raw: hexToBytes(message) },
|
|
649
|
+
});
|
|
650
|
+
return {
|
|
651
|
+
headers: {
|
|
652
|
+
[SELF_HEADERS.ADDRESS]: account.address,
|
|
653
|
+
[SELF_HEADERS.SIGNATURE]: signature,
|
|
654
|
+
[SELF_HEADERS.TIMESTAMP]: timestamp,
|
|
655
|
+
},
|
|
656
|
+
instructions: `Add these three headers to your HTTP request. The receiving service can verify them with verify_self_request to confirm this agent is backed by a real human. For Self demo APIs use ?network=${SELF_DEMO_NETWORK} (e.g. POST ${selfDemoUrl("/api/demo/verify")}).`,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
async authenticatedFetch(params) {
|
|
660
|
+
const privateKey = this.resolveAgentPrivateKey();
|
|
661
|
+
const account = privateKeyToAccount(privateKey);
|
|
662
|
+
const method = params.method.toUpperCase();
|
|
663
|
+
const body = params.body;
|
|
664
|
+
const timestamp = Date.now().toString();
|
|
665
|
+
const message = computeSigningMessage(timestamp, method, params.url, body);
|
|
666
|
+
const signature = await account.signMessage({
|
|
667
|
+
message: { raw: hexToBytes(message) },
|
|
668
|
+
});
|
|
669
|
+
const response = await fetch(params.url, {
|
|
670
|
+
method,
|
|
671
|
+
body,
|
|
672
|
+
headers: {
|
|
673
|
+
...(params.contentType ? { "Content-Type": params.contentType } : {}),
|
|
674
|
+
[SELF_HEADERS.ADDRESS]: account.address,
|
|
675
|
+
[SELF_HEADERS.SIGNATURE]: signature,
|
|
676
|
+
[SELF_HEADERS.TIMESTAMP]: timestamp,
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
const rawBody = await response.text();
|
|
680
|
+
const { body: truncatedBody, truncated } = truncateBody(rawBody, SELF_FETCH_MAX_BYTES);
|
|
681
|
+
return {
|
|
682
|
+
status: response.status,
|
|
683
|
+
body: truncatedBody,
|
|
684
|
+
truncated,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
/** Reserved for future on-chain Self writes (e.g. metadata updates). */
|
|
688
|
+
taggedCalldata(data) {
|
|
689
|
+
return concat([data, CELINA_DATA_SUFFIX]);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
//# sourceMappingURL=self.service.js.map
|