kontext-sdk 0.8.0 → 0.10.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/README.md +119 -0
- package/dist/index.d.mts +2067 -215
- package/dist/index.d.ts +2067 -215
- package/dist/index.js +3900 -115
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3866 -114
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var crypto$1 = require('crypto');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var fs5 = require('fs');
|
|
5
|
+
var path5 = require('path');
|
|
6
6
|
|
|
7
7
|
function _interopNamespace(e) {
|
|
8
8
|
if (e && e.__esModule) return e;
|
|
@@ -22,15 +22,322 @@ function _interopNamespace(e) {
|
|
|
22
22
|
return Object.freeze(n);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
var
|
|
26
|
-
var
|
|
25
|
+
var fs5__namespace = /*#__PURE__*/_interopNamespace(fs5);
|
|
26
|
+
var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
|
|
27
27
|
|
|
28
|
+
var __defProp = Object.defineProperty;
|
|
29
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
28
30
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
29
31
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
30
32
|
}) : x)(function(x) {
|
|
31
33
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
32
34
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
33
35
|
});
|
|
36
|
+
var __esm = (fn, res) => function __init() {
|
|
37
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
38
|
+
};
|
|
39
|
+
var __export = (target, all) => {
|
|
40
|
+
for (var name in all)
|
|
41
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/integrations/erc8021.ts
|
|
45
|
+
var erc8021_exports = {};
|
|
46
|
+
__export(erc8021_exports, {
|
|
47
|
+
KONTEXT_BUILDER_CODE: () => exports.KONTEXT_BUILDER_CODE,
|
|
48
|
+
encodeERC8021Suffix: () => encodeERC8021Suffix,
|
|
49
|
+
fetchTransactionAttribution: () => fetchTransactionAttribution,
|
|
50
|
+
parseERC8021Suffix: () => parseERC8021Suffix
|
|
51
|
+
});
|
|
52
|
+
function encodeERC8021Suffix(codes) {
|
|
53
|
+
if (codes.length === 0) {
|
|
54
|
+
throw new Error("ERC-8021: at least one builder code is required");
|
|
55
|
+
}
|
|
56
|
+
const codesStr = codes.join(",");
|
|
57
|
+
let codesHex = "";
|
|
58
|
+
for (let i = 0; i < codesStr.length; i++) {
|
|
59
|
+
codesHex += codesStr.charCodeAt(i).toString(16).padStart(2, "0");
|
|
60
|
+
}
|
|
61
|
+
const codesLength = codesStr.length;
|
|
62
|
+
if (codesLength > 255) {
|
|
63
|
+
throw new Error("ERC-8021: combined codes length exceeds 255 bytes");
|
|
64
|
+
}
|
|
65
|
+
const codesLengthHex = codesLength.toString(16).padStart(2, "0");
|
|
66
|
+
const schemaId = "00";
|
|
67
|
+
return codesLengthHex + codesHex + schemaId + ERC_8021_MARKER;
|
|
68
|
+
}
|
|
69
|
+
function parseERC8021Suffix(calldata) {
|
|
70
|
+
const clean = calldata.startsWith("0x") ? calldata.slice(2) : calldata;
|
|
71
|
+
if (clean.length < 38) return null;
|
|
72
|
+
const markerStart = clean.length - 32;
|
|
73
|
+
const marker = clean.slice(markerStart);
|
|
74
|
+
if (marker !== ERC_8021_MARKER) return null;
|
|
75
|
+
const schemaIdStart = markerStart - 2;
|
|
76
|
+
const schemaId = parseInt(clean.slice(schemaIdStart, markerStart), 16);
|
|
77
|
+
if (isNaN(schemaId)) return null;
|
|
78
|
+
let codesLength = 0;
|
|
79
|
+
let codesLengthPos = 0;
|
|
80
|
+
for (let L = 1; L <= 255; L++) {
|
|
81
|
+
const candidatePos = schemaIdStart - L * 2 - 2;
|
|
82
|
+
if (candidatePos < 0) break;
|
|
83
|
+
const candidate = parseInt(clean.slice(candidatePos, candidatePos + 2), 16);
|
|
84
|
+
if (candidate === L) {
|
|
85
|
+
codesLength = L;
|
|
86
|
+
codesLengthPos = candidatePos;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (codesLength === 0) return null;
|
|
91
|
+
const codesHex = clean.slice(codesLengthPos + 2, codesLengthPos + 2 + codesLength * 2);
|
|
92
|
+
let codesStr = "";
|
|
93
|
+
for (let i = 0; i < codesHex.length; i += 2) {
|
|
94
|
+
codesStr += String.fromCharCode(parseInt(codesHex.slice(i, i + 2), 16));
|
|
95
|
+
}
|
|
96
|
+
const codes = codesStr.split(",").filter((c) => c.length > 0);
|
|
97
|
+
if (codes.length === 0) return null;
|
|
98
|
+
const rawSuffix = "0x" + clean.slice(codesLengthPos);
|
|
99
|
+
return { codes, schemaId, rawSuffix };
|
|
100
|
+
}
|
|
101
|
+
async function fetchTransactionAttribution(rpcUrl, txHash) {
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(rpcUrl, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
jsonrpc: "2.0",
|
|
108
|
+
id: 1,
|
|
109
|
+
method: "eth_getTransactionByHash",
|
|
110
|
+
params: [txHash]
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
const json = await res.json();
|
|
114
|
+
if (json.error || !json.result?.input) return null;
|
|
115
|
+
return parseERC8021Suffix(json.result.input);
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
var ERC_8021_MARKER; exports.KONTEXT_BUILDER_CODE = void 0;
|
|
121
|
+
var init_erc8021 = __esm({
|
|
122
|
+
"src/integrations/erc8021.ts"() {
|
|
123
|
+
ERC_8021_MARKER = "80218021802180218021802180218021";
|
|
124
|
+
exports.KONTEXT_BUILDER_CODE = "kontext";
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// src/onchain.ts
|
|
129
|
+
var onchain_exports = {};
|
|
130
|
+
__export(onchain_exports, {
|
|
131
|
+
OnChainExporter: () => exports.OnChainExporter,
|
|
132
|
+
anchorDigest: () => anchorDigest,
|
|
133
|
+
getAnchor: () => getAnchor,
|
|
134
|
+
verifyAnchor: () => verifyAnchor
|
|
135
|
+
});
|
|
136
|
+
async function rpcCall(rpcUrl, method, params) {
|
|
137
|
+
const res = await fetch(rpcUrl, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: { "Content-Type": "application/json" },
|
|
140
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
141
|
+
});
|
|
142
|
+
const json = await res.json();
|
|
143
|
+
if (json.error) throw new Error(`RPC error: ${json.error.message}`);
|
|
144
|
+
return json.result;
|
|
145
|
+
}
|
|
146
|
+
function encodeBytes32(hex) {
|
|
147
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
148
|
+
return clean.padStart(64, "0");
|
|
149
|
+
}
|
|
150
|
+
function decodeUint256(hex) {
|
|
151
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
152
|
+
return parseInt(clean, 16);
|
|
153
|
+
}
|
|
154
|
+
function decodeAddress(hex) {
|
|
155
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
156
|
+
return "0x" + clean.slice(24);
|
|
157
|
+
}
|
|
158
|
+
async function verifyAnchor(rpcUrl, contractAddress, digest) {
|
|
159
|
+
const calldata = SEL_VERIFY + encodeBytes32(digest);
|
|
160
|
+
const result = await rpcCall(rpcUrl, "eth_call", [
|
|
161
|
+
{ to: contractAddress, data: calldata },
|
|
162
|
+
"latest"
|
|
163
|
+
]);
|
|
164
|
+
const anchored = decodeUint256(result) !== 0;
|
|
165
|
+
return { anchored, digest };
|
|
166
|
+
}
|
|
167
|
+
async function getAnchor(rpcUrl, contractAddress, digest) {
|
|
168
|
+
const calldata = SEL_GET_ANCHOR + encodeBytes32(digest);
|
|
169
|
+
try {
|
|
170
|
+
const result = await rpcCall(rpcUrl, "eth_call", [
|
|
171
|
+
{ to: contractAddress, data: calldata },
|
|
172
|
+
"latest"
|
|
173
|
+
]);
|
|
174
|
+
const clean = result.startsWith("0x") ? result.slice(2) : result;
|
|
175
|
+
if (clean.length < 192) return null;
|
|
176
|
+
const anchorer = decodeAddress(clean.slice(0, 64));
|
|
177
|
+
const projectHash = "0x" + clean.slice(64, 128);
|
|
178
|
+
const timestamp = decodeUint256("0x" + clean.slice(128, 192));
|
|
179
|
+
return { anchorer, projectHash, timestamp };
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function anchorDigest(config, digest, projectId) {
|
|
185
|
+
let viem;
|
|
186
|
+
let viemChains;
|
|
187
|
+
let viemAccounts;
|
|
188
|
+
try {
|
|
189
|
+
viem = await import('viem');
|
|
190
|
+
viemChains = await import('viem/chains');
|
|
191
|
+
viemAccounts = await import('viem/accounts');
|
|
192
|
+
} catch {
|
|
193
|
+
throw new Error(
|
|
194
|
+
"On-chain anchoring requires viem. Install it: npm install viem"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (!config.privateKey) {
|
|
198
|
+
throw new Error("privateKey is required for on-chain anchoring");
|
|
199
|
+
}
|
|
200
|
+
const account = viemAccounts.privateKeyToAccount(config.privateKey);
|
|
201
|
+
const chain = config.rpcUrl.includes("sepolia") ? viemChains.baseSepolia : viemChains.base;
|
|
202
|
+
const client = viem.createWalletClient({
|
|
203
|
+
account,
|
|
204
|
+
chain,
|
|
205
|
+
transport: viem.http(config.rpcUrl)
|
|
206
|
+
});
|
|
207
|
+
const publicClient = viem.createPublicClient({
|
|
208
|
+
chain,
|
|
209
|
+
transport: viem.http(config.rpcUrl)
|
|
210
|
+
});
|
|
211
|
+
const projectHash = viem.keccak256(viem.toBytes(projectId));
|
|
212
|
+
const digestBytes32 = digest.startsWith("0x") ? digest : `0x${digest}`;
|
|
213
|
+
const abi = [
|
|
214
|
+
{
|
|
215
|
+
name: "anchor",
|
|
216
|
+
type: "function",
|
|
217
|
+
stateMutability: "nonpayable",
|
|
218
|
+
inputs: [
|
|
219
|
+
{ name: "digest", type: "bytes32" },
|
|
220
|
+
{ name: "projectHash", type: "bytes32" }
|
|
221
|
+
],
|
|
222
|
+
outputs: []
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
const { encodeERC8021Suffix: encodeERC8021Suffix2, KONTEXT_BUILDER_CODE: KONTEXT_BUILDER_CODE2 } = await Promise.resolve().then(() => (init_erc8021(), erc8021_exports));
|
|
226
|
+
const calldata = viem.encodeFunctionData({
|
|
227
|
+
abi,
|
|
228
|
+
functionName: "anchor",
|
|
229
|
+
args: [digestBytes32, projectHash]
|
|
230
|
+
});
|
|
231
|
+
const builderCode = config.builderCode ?? KONTEXT_BUILDER_CODE2;
|
|
232
|
+
const suffix = encodeERC8021Suffix2([builderCode]);
|
|
233
|
+
const txHash = await client.sendTransaction({
|
|
234
|
+
to: config.contractAddress,
|
|
235
|
+
data: calldata + suffix
|
|
236
|
+
});
|
|
237
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
238
|
+
return {
|
|
239
|
+
digest,
|
|
240
|
+
txHash,
|
|
241
|
+
blockNumber: Number(receipt.blockNumber),
|
|
242
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
243
|
+
contractAddress: config.contractAddress,
|
|
244
|
+
chain: chain.name.toLowerCase()
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
var SEL_VERIFY, SEL_GET_ANCHOR; exports.OnChainExporter = void 0;
|
|
248
|
+
var init_onchain = __esm({
|
|
249
|
+
"src/onchain.ts"() {
|
|
250
|
+
SEL_VERIFY = "0x75e36616";
|
|
251
|
+
SEL_GET_ANCHOR = "0x7feb51d9";
|
|
252
|
+
exports.OnChainExporter = class {
|
|
253
|
+
config;
|
|
254
|
+
projectId;
|
|
255
|
+
batchSize;
|
|
256
|
+
getTerminalDigest;
|
|
257
|
+
eventCount = 0;
|
|
258
|
+
constructor(config, projectId, getTerminalDigest) {
|
|
259
|
+
this.config = config;
|
|
260
|
+
this.projectId = projectId;
|
|
261
|
+
this.batchSize = 10;
|
|
262
|
+
this.getTerminalDigest = getTerminalDigest;
|
|
263
|
+
}
|
|
264
|
+
async export(events) {
|
|
265
|
+
this.eventCount += events.length;
|
|
266
|
+
if (this.eventCount >= this.batchSize) {
|
|
267
|
+
const digest = this.getTerminalDigest();
|
|
268
|
+
await anchorDigest(this.config, digest, this.projectId);
|
|
269
|
+
this.eventCount = 0;
|
|
270
|
+
}
|
|
271
|
+
return { success: true, exportedCount: events.length };
|
|
272
|
+
}
|
|
273
|
+
async flush() {
|
|
274
|
+
if (this.eventCount > 0) {
|
|
275
|
+
const digest = this.getTerminalDigest();
|
|
276
|
+
await anchorDigest(this.config, digest, this.projectId);
|
|
277
|
+
this.eventCount = 0;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async shutdown() {
|
|
281
|
+
await this.flush();
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// src/attestation.ts
|
|
288
|
+
var attestation_exports = {};
|
|
289
|
+
__export(attestation_exports, {
|
|
290
|
+
exchangeAttestation: () => exchangeAttestation,
|
|
291
|
+
fetchAgentCard: () => fetchAgentCard
|
|
292
|
+
});
|
|
293
|
+
async function fetchAgentCard(endpoint, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
294
|
+
const url = `${endpoint.replace(/\/$/, "")}/.well-known/kontext.json`;
|
|
295
|
+
const response = await fetch(url, {
|
|
296
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
297
|
+
headers: { Accept: "application/json" }
|
|
298
|
+
});
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
throw new Error(`Failed to fetch agent card from ${url}: ${response.status}`);
|
|
301
|
+
}
|
|
302
|
+
return response.json();
|
|
303
|
+
}
|
|
304
|
+
async function exchangeAttestation(config, request) {
|
|
305
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
306
|
+
const card = await fetchAgentCard(config.endpoint, timeoutMs);
|
|
307
|
+
if (config.agentId && card.agentId !== config.agentId) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Agent ID mismatch: expected ${config.agentId}, got ${card.agentId}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (!card.capabilities.includes("attest")) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Counterparty ${card.agentId} does not support attestation`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const attestUrl = `${config.endpoint.replace(/\/$/, "")}${card.attestEndpoint}`;
|
|
318
|
+
const response = await fetch(attestUrl, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: { "Content-Type": "application/json" },
|
|
321
|
+
body: JSON.stringify(request),
|
|
322
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
323
|
+
});
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
throw new Error(`Attestation request failed: ${response.status}`);
|
|
326
|
+
}
|
|
327
|
+
const result = await response.json();
|
|
328
|
+
return {
|
|
329
|
+
attested: result.attested,
|
|
330
|
+
digest: result.receiverDigest,
|
|
331
|
+
agentId: result.receiverAgentId,
|
|
332
|
+
timestamp: result.timestamp
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
var DEFAULT_TIMEOUT_MS;
|
|
336
|
+
var init_attestation = __esm({
|
|
337
|
+
"src/attestation.ts"() {
|
|
338
|
+
DEFAULT_TIMEOUT_MS = 1e4;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
34
341
|
|
|
35
342
|
// src/utils.ts
|
|
36
343
|
function generateId() {
|
|
@@ -935,13 +1242,17 @@ var STORAGE_KEYS = {
|
|
|
935
1242
|
actions: "kontext:actions",
|
|
936
1243
|
transactions: "kontext:transactions",
|
|
937
1244
|
tasks: "kontext:tasks",
|
|
938
|
-
anomalies: "kontext:anomalies"
|
|
1245
|
+
anomalies: "kontext:anomalies",
|
|
1246
|
+
sessions: "kontext:sessions",
|
|
1247
|
+
checkpoints: "kontext:checkpoints"
|
|
939
1248
|
};
|
|
940
1249
|
var KontextStore = class {
|
|
941
1250
|
actions = [];
|
|
942
1251
|
transactions = [];
|
|
943
1252
|
tasks = /* @__PURE__ */ new Map();
|
|
944
1253
|
anomalies = [];
|
|
1254
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1255
|
+
checkpoints = /* @__PURE__ */ new Map();
|
|
945
1256
|
storageAdapter = null;
|
|
946
1257
|
maxEntries;
|
|
947
1258
|
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
@@ -974,11 +1285,15 @@ var KontextStore = class {
|
|
|
974
1285
|
const transactionsSnapshot = [...this.transactions];
|
|
975
1286
|
const tasksSnapshot = Array.from(this.tasks.entries());
|
|
976
1287
|
const anomaliesSnapshot = [...this.anomalies];
|
|
1288
|
+
const sessionsSnapshot = Array.from(this.sessions.entries());
|
|
1289
|
+
const checkpointsSnapshot = Array.from(this.checkpoints.entries());
|
|
977
1290
|
await Promise.all([
|
|
978
1291
|
this.storageAdapter.save(STORAGE_KEYS.actions, actionsSnapshot),
|
|
979
1292
|
this.storageAdapter.save(STORAGE_KEYS.transactions, transactionsSnapshot),
|
|
980
1293
|
this.storageAdapter.save(STORAGE_KEYS.tasks, tasksSnapshot),
|
|
981
|
-
this.storageAdapter.save(STORAGE_KEYS.anomalies, anomaliesSnapshot)
|
|
1294
|
+
this.storageAdapter.save(STORAGE_KEYS.anomalies, anomaliesSnapshot),
|
|
1295
|
+
this.storageAdapter.save(STORAGE_KEYS.sessions, sessionsSnapshot),
|
|
1296
|
+
this.storageAdapter.save(STORAGE_KEYS.checkpoints, checkpointsSnapshot)
|
|
982
1297
|
]);
|
|
983
1298
|
}
|
|
984
1299
|
/**
|
|
@@ -988,11 +1303,13 @@ var KontextStore = class {
|
|
|
988
1303
|
*/
|
|
989
1304
|
async restore() {
|
|
990
1305
|
if (!this.storageAdapter) return;
|
|
991
|
-
const [actions, transactions, tasksEntries, anomalies] = await Promise.all([
|
|
1306
|
+
const [actions, transactions, tasksEntries, anomalies, sessionsEntries, checkpointsEntries] = await Promise.all([
|
|
992
1307
|
this.storageAdapter.load(STORAGE_KEYS.actions),
|
|
993
1308
|
this.storageAdapter.load(STORAGE_KEYS.transactions),
|
|
994
1309
|
this.storageAdapter.load(STORAGE_KEYS.tasks),
|
|
995
|
-
this.storageAdapter.load(STORAGE_KEYS.anomalies)
|
|
1310
|
+
this.storageAdapter.load(STORAGE_KEYS.anomalies),
|
|
1311
|
+
this.storageAdapter.load(STORAGE_KEYS.sessions),
|
|
1312
|
+
this.storageAdapter.load(STORAGE_KEYS.checkpoints)
|
|
996
1313
|
]);
|
|
997
1314
|
if (Array.isArray(actions)) {
|
|
998
1315
|
this.actions = actions;
|
|
@@ -1006,6 +1323,12 @@ var KontextStore = class {
|
|
|
1006
1323
|
if (Array.isArray(anomalies)) {
|
|
1007
1324
|
this.anomalies = anomalies;
|
|
1008
1325
|
}
|
|
1326
|
+
if (Array.isArray(sessionsEntries)) {
|
|
1327
|
+
this.sessions = new Map(sessionsEntries);
|
|
1328
|
+
}
|
|
1329
|
+
if (Array.isArray(checkpointsEntries)) {
|
|
1330
|
+
this.checkpoints = new Map(checkpointsEntries);
|
|
1331
|
+
}
|
|
1009
1332
|
}
|
|
1010
1333
|
// --------------------------------------------------------------------------
|
|
1011
1334
|
// Actions
|
|
@@ -1105,6 +1428,60 @@ var KontextStore = class {
|
|
|
1105
1428
|
return this.anomalies.filter(predicate);
|
|
1106
1429
|
}
|
|
1107
1430
|
// --------------------------------------------------------------------------
|
|
1431
|
+
// Sessions (Provenance Layer 1)
|
|
1432
|
+
// --------------------------------------------------------------------------
|
|
1433
|
+
/** Store a session. */
|
|
1434
|
+
addSession(session) {
|
|
1435
|
+
this.sessions.set(session.sessionId, session);
|
|
1436
|
+
}
|
|
1437
|
+
/** Retrieve a session by ID. */
|
|
1438
|
+
getSession(sessionId) {
|
|
1439
|
+
return this.sessions.get(sessionId);
|
|
1440
|
+
}
|
|
1441
|
+
/** Update a session. */
|
|
1442
|
+
updateSession(sessionId, updates) {
|
|
1443
|
+
const existing = this.sessions.get(sessionId);
|
|
1444
|
+
if (!existing) return void 0;
|
|
1445
|
+
const updated = { ...existing, ...updates };
|
|
1446
|
+
this.sessions.set(sessionId, updated);
|
|
1447
|
+
return updated;
|
|
1448
|
+
}
|
|
1449
|
+
/** Retrieve all sessions. */
|
|
1450
|
+
getSessions() {
|
|
1451
|
+
return Array.from(this.sessions.values());
|
|
1452
|
+
}
|
|
1453
|
+
/** Retrieve sessions filtered by a predicate. */
|
|
1454
|
+
querySessions(predicate) {
|
|
1455
|
+
return Array.from(this.sessions.values()).filter(predicate);
|
|
1456
|
+
}
|
|
1457
|
+
// --------------------------------------------------------------------------
|
|
1458
|
+
// Checkpoints (Provenance Layer 3)
|
|
1459
|
+
// --------------------------------------------------------------------------
|
|
1460
|
+
/** Store a checkpoint. */
|
|
1461
|
+
addCheckpoint(checkpoint) {
|
|
1462
|
+
this.checkpoints.set(checkpoint.id, checkpoint);
|
|
1463
|
+
}
|
|
1464
|
+
/** Retrieve a checkpoint by ID. */
|
|
1465
|
+
getCheckpoint(checkpointId) {
|
|
1466
|
+
return this.checkpoints.get(checkpointId);
|
|
1467
|
+
}
|
|
1468
|
+
/** Update a checkpoint. */
|
|
1469
|
+
updateCheckpoint(checkpointId, updates) {
|
|
1470
|
+
const existing = this.checkpoints.get(checkpointId);
|
|
1471
|
+
if (!existing) return void 0;
|
|
1472
|
+
const updated = { ...existing, ...updates };
|
|
1473
|
+
this.checkpoints.set(checkpointId, updated);
|
|
1474
|
+
return updated;
|
|
1475
|
+
}
|
|
1476
|
+
/** Retrieve all checkpoints. */
|
|
1477
|
+
getCheckpoints() {
|
|
1478
|
+
return Array.from(this.checkpoints.values());
|
|
1479
|
+
}
|
|
1480
|
+
/** Retrieve checkpoints filtered by a predicate. */
|
|
1481
|
+
queryCheckpoints(predicate) {
|
|
1482
|
+
return Array.from(this.checkpoints.values()).filter(predicate);
|
|
1483
|
+
}
|
|
1484
|
+
// --------------------------------------------------------------------------
|
|
1108
1485
|
// Utilities
|
|
1109
1486
|
// --------------------------------------------------------------------------
|
|
1110
1487
|
/** Get total record counts across all stores. */
|
|
@@ -1113,7 +1490,9 @@ var KontextStore = class {
|
|
|
1113
1490
|
actions: this.actions.length,
|
|
1114
1491
|
transactions: this.transactions.length,
|
|
1115
1492
|
tasks: this.tasks.size,
|
|
1116
|
-
anomalies: this.anomalies.length
|
|
1493
|
+
anomalies: this.anomalies.length,
|
|
1494
|
+
sessions: this.sessions.size,
|
|
1495
|
+
checkpoints: this.checkpoints.size
|
|
1117
1496
|
};
|
|
1118
1497
|
}
|
|
1119
1498
|
/** Clear all stored data. Useful for testing. */
|
|
@@ -1122,6 +1501,8 @@ var KontextStore = class {
|
|
|
1122
1501
|
this.transactions = [];
|
|
1123
1502
|
this.tasks.clear();
|
|
1124
1503
|
this.anomalies = [];
|
|
1504
|
+
this.sessions.clear();
|
|
1505
|
+
this.checkpoints.clear();
|
|
1125
1506
|
}
|
|
1126
1507
|
};
|
|
1127
1508
|
var DIGEST_EXCLUDED_FIELDS = /* @__PURE__ */ new Set(["digest", "priorDigest"]);
|
|
@@ -1593,13 +1974,13 @@ var ActionLogger = class {
|
|
|
1593
1974
|
}
|
|
1594
1975
|
flushToFile(actions) {
|
|
1595
1976
|
const outputDir = this.config.localOutputDir ?? ".kontext";
|
|
1596
|
-
const logDir =
|
|
1977
|
+
const logDir = path5__namespace.join(outputDir, "logs");
|
|
1597
1978
|
try {
|
|
1598
|
-
|
|
1979
|
+
fs5__namespace.mkdirSync(logDir, { recursive: true });
|
|
1599
1980
|
const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
|
|
1600
|
-
const filePath =
|
|
1981
|
+
const filePath = path5__namespace.join(logDir, filename);
|
|
1601
1982
|
const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
|
|
1602
|
-
|
|
1983
|
+
fs5__namespace.appendFileSync(filePath, lines, "utf-8");
|
|
1603
1984
|
} catch (error) {
|
|
1604
1985
|
this.emitLog("warn", "Failed to write log file", { error });
|
|
1605
1986
|
}
|
|
@@ -2139,17 +2520,9 @@ var AuditExporter = class {
|
|
|
2139
2520
|
return sections.join("\n\n");
|
|
2140
2521
|
}
|
|
2141
2522
|
};
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
2146
|
-
arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
2147
|
-
optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
2148
|
-
// Arc (Circle's stablecoin-native blockchain) -- placeholder address, update when Arc mainnet launches
|
|
2149
|
-
arc: "0xa0c0000000000000000000000000000000000001"
|
|
2150
|
-
};
|
|
2151
|
-
var SANCTIONED_ADDRESSES = [
|
|
2152
|
-
// --- ACTIVELY SANCTIONED (SDN) ---
|
|
2523
|
+
|
|
2524
|
+
// src/integrations/data/ofac-addresses.ts
|
|
2525
|
+
var OFAC_SDN_ACTIVE_ADDRESSES = [
|
|
2153
2526
|
// Lazarus Group / DPRK (Ronin Bridge hack)
|
|
2154
2527
|
"0x098B716B8Aaf21512996dC57EB0615e2383E2f96",
|
|
2155
2528
|
"0xa0e1c89Ef1a489c9C7dE96311eD5Ce5D32c20E4B",
|
|
@@ -2173,8 +2546,9 @@ var SANCTIONED_ADDRESSES = [
|
|
|
2173
2546
|
"0x931546D9e66836AbF687d2bc64B30407bAc8C568",
|
|
2174
2547
|
"0x43fa21d92141BA9db43052492E0DeEE5aa5f0A93",
|
|
2175
2548
|
// Zedcex / Zedxion (IRGC-linked, sanctioned June 2024)
|
|
2176
|
-
"0xaeAAc358560e11f52454D997AAFF2c5731B6f8a6"
|
|
2177
|
-
|
|
2549
|
+
"0xaeAAc358560e11f52454D997AAFF2c5731B6f8a6"
|
|
2550
|
+
];
|
|
2551
|
+
var OFAC_SDN_DELISTED_ADDRESSES = [
|
|
2178
2552
|
// Tornado Cash contracts (sanctioned Aug 2022, DELISTED March 21, 2025)
|
|
2179
2553
|
"0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b",
|
|
2180
2554
|
"0xd96f2B1c14Db8458374d9Aca76E26c3D18364307",
|
|
@@ -2196,15 +2570,29 @@ var SANCTIONED_ADDRESSES = [
|
|
|
2196
2570
|
"0x722122dF12D4e14e13Ac3b6895a86e84145b6967"
|
|
2197
2571
|
// Tornado Cash / Sinbad.io
|
|
2198
2572
|
];
|
|
2573
|
+
var OFAC_SDN_ADDRESSES = [
|
|
2574
|
+
...OFAC_SDN_ACTIVE_ADDRESSES,
|
|
2575
|
+
...OFAC_SDN_DELISTED_ADDRESSES
|
|
2576
|
+
];
|
|
2577
|
+
var USDC_CONTRACTS = {
|
|
2578
|
+
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
2579
|
+
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2580
|
+
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
2581
|
+
arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
2582
|
+
optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
2583
|
+
// Arc (Circle's stablecoin-native blockchain) -- placeholder address, update when Arc mainnet launches
|
|
2584
|
+
arc: "0xa0c0000000000000000000000000000000000001"
|
|
2585
|
+
};
|
|
2586
|
+
var SANCTIONED_ADDRESSES = [...OFAC_SDN_ADDRESSES];
|
|
2199
2587
|
var SANCTIONED_SET = new Set(
|
|
2200
2588
|
SANCTIONED_ADDRESSES.map((addr) => addr.toLowerCase())
|
|
2201
2589
|
);
|
|
2202
2590
|
function loadCachedSDN() {
|
|
2203
2591
|
try {
|
|
2204
2592
|
const dataDir = process.env["KONTEXT_DATA_DIR"] || ".kontext";
|
|
2205
|
-
const cachePath =
|
|
2206
|
-
if (
|
|
2207
|
-
const cache = JSON.parse(
|
|
2593
|
+
const cachePath = path5__namespace.join(dataDir, "ofac-sdn-cache.json");
|
|
2594
|
+
if (fs5__namespace.existsSync(cachePath)) {
|
|
2595
|
+
const cache = JSON.parse(fs5__namespace.readFileSync(cachePath, "utf-8"));
|
|
2208
2596
|
if (Array.isArray(cache.addresses)) {
|
|
2209
2597
|
for (const addr of cache.addresses) {
|
|
2210
2598
|
SANCTIONED_SET.add(String(addr).toLowerCase());
|
|
@@ -2691,11 +3079,301 @@ var PaymentCompliance = class _PaymentCompliance {
|
|
|
2691
3079
|
}
|
|
2692
3080
|
};
|
|
2693
3081
|
|
|
3082
|
+
// src/integrations/screening-provider.ts
|
|
3083
|
+
var ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
3084
|
+
function isBlockchainAddress(query) {
|
|
3085
|
+
return ETH_ADDRESS_RE.test(query);
|
|
3086
|
+
}
|
|
3087
|
+
function providerSupportsQuery(provider, query) {
|
|
3088
|
+
const isAddr = isBlockchainAddress(query);
|
|
3089
|
+
if (isAddr) {
|
|
3090
|
+
return provider.queryTypes.includes("address") || provider.queryTypes.includes("both");
|
|
3091
|
+
}
|
|
3092
|
+
return provider.queryTypes.includes("entity_name") || provider.queryTypes.includes("both");
|
|
3093
|
+
}
|
|
3094
|
+
var TOKEN_REQUIRED_LISTS = {
|
|
3095
|
+
USDC: ["OFAC_SDN"],
|
|
3096
|
+
EURC: ["EU_CONSOLIDATED"],
|
|
3097
|
+
USDT: ["OFAC_SDN", "EU_CONSOLIDATED"],
|
|
3098
|
+
DAI: ["OFAC_SDN", "EU_CONSOLIDATED"],
|
|
3099
|
+
USDP: ["OFAC_SDN"],
|
|
3100
|
+
USDG: ["OFAC_SDN"]
|
|
3101
|
+
};
|
|
3102
|
+
var CURRENCY_REQUIRED_LISTS = {
|
|
3103
|
+
USD: ["OFAC_SDN"],
|
|
3104
|
+
EUR: ["EU_CONSOLIDATED"],
|
|
3105
|
+
GBP: ["UK_OFSI"],
|
|
3106
|
+
AED: ["UAE_LOCAL", "UN_SECURITY_COUNCIL"],
|
|
3107
|
+
INR: ["UN_SECURITY_COUNCIL", "INDIA_DOMESTIC"],
|
|
3108
|
+
SGD: ["MAS_TFS", "UN_SECURITY_COUNCIL"],
|
|
3109
|
+
CNY: ["UN_SECURITY_COUNCIL"],
|
|
3110
|
+
CNH: ["UN_SECURITY_COUNCIL"],
|
|
3111
|
+
HKD: ["HK_UNSO", "UN_SECURITY_COUNCIL"],
|
|
3112
|
+
NZD: ["UN_SECURITY_COUNCIL", "NZ_DESIGNATED"],
|
|
3113
|
+
KRW: ["UN_SECURITY_COUNCIL", "KOFIU_DOMESTIC"],
|
|
3114
|
+
MYR: ["BNM_DOMESTIC", "UN_SECURITY_COUNCIL"],
|
|
3115
|
+
THB: ["UN_SECURITY_COUNCIL", "AMLO_DOMESTIC"]
|
|
3116
|
+
};
|
|
3117
|
+
var DEFAULT_REQUIRED_LISTS = [
|
|
3118
|
+
"OFAC_SDN",
|
|
3119
|
+
"EU_CONSOLIDATED",
|
|
3120
|
+
"UN_SECURITY_COUNCIL"
|
|
3121
|
+
];
|
|
3122
|
+
function getRequiredLists(context) {
|
|
3123
|
+
if (!context) return DEFAULT_REQUIRED_LISTS;
|
|
3124
|
+
if (context.token && typeof context.token === "string") {
|
|
3125
|
+
const tokenLists = TOKEN_REQUIRED_LISTS[context.token];
|
|
3126
|
+
if (tokenLists) return tokenLists;
|
|
3127
|
+
}
|
|
3128
|
+
if (context.currency && typeof context.currency === "string") {
|
|
3129
|
+
const currencyLists = CURRENCY_REQUIRED_LISTS[context.currency];
|
|
3130
|
+
if (currencyLists) return currencyLists;
|
|
3131
|
+
}
|
|
3132
|
+
return DEFAULT_REQUIRED_LISTS;
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
// src/integrations/screening-aggregator.ts
|
|
3136
|
+
var ScreeningAggregator = class {
|
|
3137
|
+
providers;
|
|
3138
|
+
consensus;
|
|
3139
|
+
blocklistSet;
|
|
3140
|
+
allowlistSet;
|
|
3141
|
+
continueOnError;
|
|
3142
|
+
providerTimeoutMs;
|
|
3143
|
+
onEvent;
|
|
3144
|
+
constructor(config) {
|
|
3145
|
+
this.providers = config.providers;
|
|
3146
|
+
this.consensus = config.consensus ?? "ANY_MATCH";
|
|
3147
|
+
this.blocklistSet = new Set(
|
|
3148
|
+
(config.blocklist ?? []).map((s) => s.toLowerCase())
|
|
3149
|
+
);
|
|
3150
|
+
this.allowlistSet = new Set(
|
|
3151
|
+
(config.allowlist ?? []).map((s) => s.toLowerCase())
|
|
3152
|
+
);
|
|
3153
|
+
this.continueOnError = config.continueOnError ?? true;
|
|
3154
|
+
this.providerTimeoutMs = config.providerTimeoutMs;
|
|
3155
|
+
this.onEvent = config.onEvent;
|
|
3156
|
+
}
|
|
3157
|
+
/**
|
|
3158
|
+
* Screen a single query (address or entity name).
|
|
3159
|
+
*/
|
|
3160
|
+
async screen(query, context) {
|
|
3161
|
+
const start = Date.now();
|
|
3162
|
+
const queryLower = query.toLowerCase();
|
|
3163
|
+
const queryType = isBlockchainAddress(query) ? "address" : "entity_name";
|
|
3164
|
+
this.onEvent?.();
|
|
3165
|
+
if (this.allowlistSet.has(queryLower)) {
|
|
3166
|
+
return this.buildResult({
|
|
3167
|
+
queryType,
|
|
3168
|
+
start,
|
|
3169
|
+
allowlisted: true,
|
|
3170
|
+
context
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
if (this.blocklistSet.has(queryLower)) {
|
|
3174
|
+
return this.buildResult({
|
|
3175
|
+
queryType,
|
|
3176
|
+
start,
|
|
3177
|
+
blocklisted: true,
|
|
3178
|
+
context
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
const compatibleProviders = this.providers.filter(
|
|
3182
|
+
(p) => p.isAvailable() && providerSupportsQuery(p, query)
|
|
3183
|
+
);
|
|
3184
|
+
if (compatibleProviders.length === 0) {
|
|
3185
|
+
return this.buildResult({
|
|
3186
|
+
queryType,
|
|
3187
|
+
start,
|
|
3188
|
+
providerResults: [],
|
|
3189
|
+
context
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
const results = [];
|
|
3193
|
+
const errors = [];
|
|
3194
|
+
const promises = compatibleProviders.map(async (provider) => {
|
|
3195
|
+
try {
|
|
3196
|
+
const result = await this.runWithTimeout(provider, query, context);
|
|
3197
|
+
return { type: "success", result };
|
|
3198
|
+
} catch (err) {
|
|
3199
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3200
|
+
return { type: "error", providerId: provider.id, error: errorMsg };
|
|
3201
|
+
}
|
|
3202
|
+
});
|
|
3203
|
+
const settled = await Promise.all(promises);
|
|
3204
|
+
for (const outcome of settled) {
|
|
3205
|
+
if (outcome.type === "success") {
|
|
3206
|
+
results.push(outcome.result);
|
|
3207
|
+
} else {
|
|
3208
|
+
if (!this.continueOnError) {
|
|
3209
|
+
throw new Error(outcome.error);
|
|
3210
|
+
}
|
|
3211
|
+
errors.push({ providerId: outcome.providerId, error: outcome.error });
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
return this.buildResult({
|
|
3215
|
+
queryType,
|
|
3216
|
+
start,
|
|
3217
|
+
providerResults: results,
|
|
3218
|
+
errors,
|
|
3219
|
+
context
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
/**
|
|
3223
|
+
* Screen multiple queries in batch.
|
|
3224
|
+
*/
|
|
3225
|
+
async screenBatch(queries, context) {
|
|
3226
|
+
const results = /* @__PURE__ */ new Map();
|
|
3227
|
+
const promises = queries.map(async (query) => {
|
|
3228
|
+
const result = await this.screen(query, context);
|
|
3229
|
+
return { query, result };
|
|
3230
|
+
});
|
|
3231
|
+
const settled = await Promise.all(promises);
|
|
3232
|
+
for (const { query, result } of settled) {
|
|
3233
|
+
results.set(query, result);
|
|
3234
|
+
}
|
|
3235
|
+
return results;
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Get available providers, optionally filtered by query type.
|
|
3239
|
+
*/
|
|
3240
|
+
getAvailableProviders(queryType) {
|
|
3241
|
+
return this.providers.filter((p) => {
|
|
3242
|
+
if (!p.isAvailable()) return false;
|
|
3243
|
+
if (!queryType) return true;
|
|
3244
|
+
return p.queryTypes.includes(queryType) || p.queryTypes.includes("both");
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Get the union of all sanctions lists covered by available providers.
|
|
3249
|
+
*/
|
|
3250
|
+
getCoveredLists() {
|
|
3251
|
+
const lists = /* @__PURE__ */ new Set();
|
|
3252
|
+
for (const p of this.providers) {
|
|
3253
|
+
if (p.isAvailable()) {
|
|
3254
|
+
for (const list of p.lists) {
|
|
3255
|
+
lists.add(list);
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
return Array.from(lists);
|
|
3260
|
+
}
|
|
3261
|
+
// --------------------------------------------------------------------------
|
|
3262
|
+
// Private
|
|
3263
|
+
// --------------------------------------------------------------------------
|
|
3264
|
+
async runWithTimeout(provider, query, context) {
|
|
3265
|
+
if (!this.providerTimeoutMs) {
|
|
3266
|
+
return provider.screen(query, context);
|
|
3267
|
+
}
|
|
3268
|
+
return Promise.race([
|
|
3269
|
+
provider.screen(query, context),
|
|
3270
|
+
new Promise(
|
|
3271
|
+
(_, reject) => setTimeout(
|
|
3272
|
+
() => reject(new Error(`Provider ${provider.id} timed out after ${this.providerTimeoutMs}ms`)),
|
|
3273
|
+
this.providerTimeoutMs
|
|
3274
|
+
)
|
|
3275
|
+
)
|
|
3276
|
+
]);
|
|
3277
|
+
}
|
|
3278
|
+
buildResult(opts) {
|
|
3279
|
+
const {
|
|
3280
|
+
queryType,
|
|
3281
|
+
start,
|
|
3282
|
+
providerResults = [],
|
|
3283
|
+
errors = [],
|
|
3284
|
+
blocklisted = false,
|
|
3285
|
+
allowlisted = false,
|
|
3286
|
+
context
|
|
3287
|
+
} = opts;
|
|
3288
|
+
if (blocklisted) {
|
|
3289
|
+
return {
|
|
3290
|
+
providerId: "aggregator",
|
|
3291
|
+
hit: true,
|
|
3292
|
+
matches: [],
|
|
3293
|
+
listsChecked: [],
|
|
3294
|
+
entriesSearched: 0,
|
|
3295
|
+
durationMs: Date.now() - start,
|
|
3296
|
+
queryType,
|
|
3297
|
+
totalProviders: 0,
|
|
3298
|
+
hitCount: 0,
|
|
3299
|
+
consensus: this.consensus,
|
|
3300
|
+
blocklisted: true,
|
|
3301
|
+
errors: [],
|
|
3302
|
+
uncoveredLists: [],
|
|
3303
|
+
providerResults: []
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
if (allowlisted) {
|
|
3307
|
+
return {
|
|
3308
|
+
providerId: "aggregator",
|
|
3309
|
+
hit: false,
|
|
3310
|
+
matches: [],
|
|
3311
|
+
listsChecked: [],
|
|
3312
|
+
entriesSearched: 0,
|
|
3313
|
+
durationMs: Date.now() - start,
|
|
3314
|
+
queryType,
|
|
3315
|
+
totalProviders: 0,
|
|
3316
|
+
hitCount: 0,
|
|
3317
|
+
consensus: this.consensus,
|
|
3318
|
+
allowlisted: true,
|
|
3319
|
+
errors: [],
|
|
3320
|
+
uncoveredLists: [],
|
|
3321
|
+
providerResults: []
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
const allMatches = [];
|
|
3325
|
+
const allListsChecked = /* @__PURE__ */ new Set();
|
|
3326
|
+
let totalEntries = 0;
|
|
3327
|
+
for (const r of providerResults) {
|
|
3328
|
+
allMatches.push(...r.matches);
|
|
3329
|
+
for (const list of r.listsChecked) {
|
|
3330
|
+
allListsChecked.add(list);
|
|
3331
|
+
}
|
|
3332
|
+
totalEntries += r.entriesSearched;
|
|
3333
|
+
}
|
|
3334
|
+
const hitCount = providerResults.filter((r) => r.hit).length;
|
|
3335
|
+
const totalProviders = providerResults.length;
|
|
3336
|
+
let hit;
|
|
3337
|
+
switch (this.consensus) {
|
|
3338
|
+
case "ALL_MATCH":
|
|
3339
|
+
hit = totalProviders > 0 && hitCount === totalProviders;
|
|
3340
|
+
break;
|
|
3341
|
+
case "MAJORITY":
|
|
3342
|
+
hit = totalProviders > 0 && hitCount > totalProviders / 2;
|
|
3343
|
+
break;
|
|
3344
|
+
case "ANY_MATCH":
|
|
3345
|
+
default:
|
|
3346
|
+
hit = hitCount > 0;
|
|
3347
|
+
break;
|
|
3348
|
+
}
|
|
3349
|
+
const requiredLists = getRequiredLists(context);
|
|
3350
|
+
const coveredLists = allListsChecked;
|
|
3351
|
+
const uncoveredLists = requiredLists.filter(
|
|
3352
|
+
(l) => !coveredLists.has(l)
|
|
3353
|
+
);
|
|
3354
|
+
return {
|
|
3355
|
+
providerId: "aggregator",
|
|
3356
|
+
hit,
|
|
3357
|
+
matches: allMatches,
|
|
3358
|
+
listsChecked: Array.from(allListsChecked),
|
|
3359
|
+
entriesSearched: totalEntries,
|
|
3360
|
+
durationMs: Date.now() - start,
|
|
3361
|
+
queryType,
|
|
3362
|
+
totalProviders,
|
|
3363
|
+
hitCount,
|
|
3364
|
+
consensus: this.consensus,
|
|
3365
|
+
errors,
|
|
3366
|
+
uncoveredLists,
|
|
3367
|
+
providerResults
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
3371
|
+
|
|
2694
3372
|
// src/plans.ts
|
|
2695
3373
|
var PLAN_LIMITS = {
|
|
2696
3374
|
free: 2e4,
|
|
2697
|
-
pro:
|
|
2698
|
-
//
|
|
3375
|
+
pro: Infinity,
|
|
3376
|
+
// usage-based: $2/1K events above 20K free
|
|
2699
3377
|
enterprise: Infinity
|
|
2700
3378
|
};
|
|
2701
3379
|
var DEFAULT_WARNING_THRESHOLD = 0.8;
|
|
@@ -2742,10 +3420,7 @@ var PlanManager = class _PlanManager {
|
|
|
2742
3420
|
}
|
|
2743
3421
|
/** Get the event limit for the current plan (Pro is multiplied by seats) */
|
|
2744
3422
|
getLimit() {
|
|
2745
|
-
|
|
2746
|
-
if (base === Infinity) return Infinity;
|
|
2747
|
-
if (this.tier === "pro") return base * this.seats;
|
|
2748
|
-
return base;
|
|
3423
|
+
return PLAN_LIMITS[this.tier];
|
|
2749
3424
|
}
|
|
2750
3425
|
/** Get the current number of seats */
|
|
2751
3426
|
getSeats() {
|
|
@@ -2950,12 +3625,7 @@ var PlanManager = class _PlanManager {
|
|
|
2950
3625
|
logLimitMessage() {
|
|
2951
3626
|
if (this.tier === "free") {
|
|
2952
3627
|
console.warn(
|
|
2953
|
-
`You've reached the 20,000 event limit on the Free plan. Upgrade to Pro
|
|
2954
|
-
);
|
|
2955
|
-
} else if (this.tier === "pro") {
|
|
2956
|
-
const effectiveLimit = (1e5 * this.seats).toLocaleString();
|
|
2957
|
-
console.warn(
|
|
2958
|
-
`You've reached the ${effectiveLimit} event limit on Pro (${this.seats} seat${this.seats !== 1 ? "s" : ""}). Add seats or contact us for Enterprise pricing \u2192 ${this.enterpriseContactUrl}`
|
|
3628
|
+
`You've reached the 20,000 event limit on the Free plan. Upgrade to Pro ($2/1K events above 20K free) \u2192 ${this.upgradeUrl}`
|
|
2959
3629
|
);
|
|
2960
3630
|
}
|
|
2961
3631
|
}
|
|
@@ -3043,7 +3713,7 @@ var JsonFileExporter = class {
|
|
|
3043
3713
|
buffer = [];
|
|
3044
3714
|
bufferSize;
|
|
3045
3715
|
constructor(options) {
|
|
3046
|
-
this.outputDir =
|
|
3716
|
+
this.outputDir = path5__namespace.resolve(options?.outputDir ?? ".kontext/exports");
|
|
3047
3717
|
this.bufferSize = options?.bufferSize ?? 1;
|
|
3048
3718
|
}
|
|
3049
3719
|
async export(events) {
|
|
@@ -3058,11 +3728,11 @@ var JsonFileExporter = class {
|
|
|
3058
3728
|
const toWrite = [...this.buffer];
|
|
3059
3729
|
this.buffer = [];
|
|
3060
3730
|
try {
|
|
3061
|
-
|
|
3731
|
+
fs5__namespace.mkdirSync(this.outputDir, { recursive: true });
|
|
3062
3732
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3063
|
-
const filePath =
|
|
3733
|
+
const filePath = path5__namespace.join(this.outputDir, `events-${date}.jsonl`);
|
|
3064
3734
|
const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
3065
|
-
|
|
3735
|
+
fs5__namespace.appendFileSync(filePath, lines, "utf-8");
|
|
3066
3736
|
} catch (error) {
|
|
3067
3737
|
console.warn("[Kontext JsonFileExporter] Failed to write events:", error);
|
|
3068
3738
|
}
|
|
@@ -3232,61 +3902,1820 @@ function extractDocumentId(resourceName) {
|
|
|
3232
3902
|
const parts = resourceName.split("/");
|
|
3233
3903
|
return parts[parts.length - 1] ?? resourceName;
|
|
3234
3904
|
}
|
|
3905
|
+
var CONFIG_FILENAME = "kontext.config.json";
|
|
3906
|
+
function loadConfigFile(startDir) {
|
|
3907
|
+
const dir = startDir ?? process.cwd();
|
|
3908
|
+
const filePath = findConfigFile(dir);
|
|
3909
|
+
if (!filePath) return null;
|
|
3910
|
+
try {
|
|
3911
|
+
const raw = fs5__namespace.readFileSync(filePath, "utf-8");
|
|
3912
|
+
const parsed = JSON.parse(raw);
|
|
3913
|
+
if (!parsed.projectId || typeof parsed.projectId !== "string") {
|
|
3914
|
+
return null;
|
|
3915
|
+
}
|
|
3916
|
+
return parsed;
|
|
3917
|
+
} catch {
|
|
3918
|
+
return null;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
function findConfigFile(dir) {
|
|
3922
|
+
let current = path5__namespace.resolve(dir);
|
|
3923
|
+
const root = path5__namespace.parse(current).root;
|
|
3924
|
+
while (true) {
|
|
3925
|
+
const candidate = path5__namespace.join(current, CONFIG_FILENAME);
|
|
3926
|
+
if (fs5__namespace.existsSync(candidate)) {
|
|
3927
|
+
return candidate;
|
|
3928
|
+
}
|
|
3929
|
+
const parent = path5__namespace.dirname(current);
|
|
3930
|
+
if (parent === current || current === root) {
|
|
3931
|
+
return null;
|
|
3932
|
+
}
|
|
3933
|
+
current = parent;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3235
3936
|
|
|
3236
|
-
// src/
|
|
3237
|
-
var
|
|
3238
|
-
|
|
3937
|
+
// src/integrations/data/stablecoin-contracts.ts
|
|
3938
|
+
var STABLECOIN_CONTRACTS = {
|
|
3939
|
+
// USDC (6 decimals)
|
|
3940
|
+
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { token: "USDC", chain: "ethereum", decimals: 6 },
|
|
3941
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { token: "USDC", chain: "base", decimals: 6 },
|
|
3942
|
+
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359": { token: "USDC", chain: "polygon", decimals: 6 },
|
|
3943
|
+
"0xaf88d065e77c8cc2239327c5edb3a432268e5831": { token: "USDC", chain: "arbitrum", decimals: 6 },
|
|
3944
|
+
"0x0b2c639c533813f4aa9d7837caf62653d097ff85": { token: "USDC", chain: "optimism", decimals: 6 },
|
|
3945
|
+
"0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": { token: "USDC", chain: "avalanche", decimals: 6 },
|
|
3946
|
+
// USDT (6 decimals)
|
|
3947
|
+
"0xdac17f958d2ee523a2206206994597c13d831ec7": { token: "USDT", chain: "ethereum", decimals: 6 },
|
|
3948
|
+
"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9": { token: "USDT", chain: "arbitrum", decimals: 6 },
|
|
3949
|
+
"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58": { token: "USDT", chain: "optimism", decimals: 6 },
|
|
3950
|
+
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f": { token: "USDT", chain: "polygon", decimals: 6 },
|
|
3951
|
+
// DAI (18 decimals)
|
|
3952
|
+
"0x6b175474e89094c44da98b954eedeac495271d0f": { token: "DAI", chain: "ethereum", decimals: 18 },
|
|
3953
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": { token: "DAI", chain: "base", decimals: 18 },
|
|
3954
|
+
// EURC (6 decimals)
|
|
3955
|
+
"0x1abaea1f7c830bd89acc67ec4af516284b1bc33c": { token: "EURC", chain: "ethereum", decimals: 6 },
|
|
3956
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": { token: "EURC", chain: "base", decimals: 6 }
|
|
3957
|
+
};
|
|
3958
|
+
new Set(Object.keys(STABLECOIN_CONTRACTS));
|
|
3959
|
+
var CHAIN_ID_MAP = {
|
|
3960
|
+
1: "ethereum",
|
|
3961
|
+
8453: "base",
|
|
3962
|
+
137: "polygon",
|
|
3963
|
+
42161: "arbitrum",
|
|
3964
|
+
10: "optimism",
|
|
3965
|
+
43114: "avalanche"
|
|
3966
|
+
};
|
|
3967
|
+
var TRANSFER_SELECTOR = "0xa9059cbb";
|
|
3968
|
+
var TRANSFER_FROM_SELECTOR = "0x23b872dd";
|
|
3969
|
+
var TRANSFER_EVENT_ABI = {
|
|
3970
|
+
type: "event",
|
|
3971
|
+
name: "Transfer",
|
|
3972
|
+
inputs: [
|
|
3973
|
+
{ type: "address", name: "from", indexed: true },
|
|
3974
|
+
{ type: "address", name: "to", indexed: true },
|
|
3975
|
+
{ type: "uint256", name: "value", indexed: false }
|
|
3976
|
+
]
|
|
3977
|
+
};
|
|
3978
|
+
|
|
3979
|
+
// src/integrations/wallet-monitor.ts
|
|
3980
|
+
var WalletMonitor = class {
|
|
3981
|
+
kontext;
|
|
3239
3982
|
config;
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
constructor(config) {
|
|
3983
|
+
agentId;
|
|
3984
|
+
tokens;
|
|
3985
|
+
unwatchers = [];
|
|
3986
|
+
running = false;
|
|
3987
|
+
/** Shared dedup set — tracks recently verified txHashes (populated by both layers) */
|
|
3988
|
+
verifiedTxHashes = /* @__PURE__ */ new Set();
|
|
3989
|
+
cleanupTimer = null;
|
|
3990
|
+
txTimestamps = /* @__PURE__ */ new Map();
|
|
3991
|
+
constructor(kontext, config, options) {
|
|
3992
|
+
this.kontext = kontext;
|
|
3251
3993
|
this.config = config;
|
|
3252
|
-
this.
|
|
3253
|
-
this.
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3994
|
+
this.agentId = options?.agentId ?? "wallet-monitor";
|
|
3995
|
+
this.tokens = options?.tokens ? new Set(options.tokens) : null;
|
|
3996
|
+
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Mark a txHash as already verified (called by the viem interceptor layer).
|
|
3999
|
+
* The monitor will skip this tx if it later sees it on-chain.
|
|
4000
|
+
*/
|
|
4001
|
+
markVerified(txHash) {
|
|
4002
|
+
const lower = txHash.toLowerCase();
|
|
4003
|
+
this.verifiedTxHashes.add(lower);
|
|
4004
|
+
this.txTimestamps.set(lower, Date.now());
|
|
4005
|
+
}
|
|
4006
|
+
/**
|
|
4007
|
+
* Start watching all configured chains for stablecoin transfers.
|
|
4008
|
+
* Dynamically imports viem — requires viem as a peer dependency.
|
|
4009
|
+
*/
|
|
4010
|
+
async start() {
|
|
4011
|
+
if (this.running) return;
|
|
4012
|
+
let viem;
|
|
4013
|
+
try {
|
|
4014
|
+
viem = await import('viem');
|
|
4015
|
+
} catch {
|
|
4016
|
+
throw new Error(
|
|
4017
|
+
"Wallet monitoring requires viem. Install it: npm install viem"
|
|
3258
4018
|
);
|
|
3259
4019
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
const
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
if (config.anomalyRules && config.anomalyRules.length > 0) {
|
|
3276
|
-
const advancedRules = ["newDestination", "offHoursActivity", "rapidSuccession", "roundAmount"];
|
|
3277
|
-
const hasAdvanced = config.anomalyRules.some((r) => advancedRules.includes(r));
|
|
3278
|
-
if (hasAdvanced) {
|
|
3279
|
-
requirePlan("advanced-anomaly-rules", planTier);
|
|
3280
|
-
}
|
|
3281
|
-
this.anomalyDetector.enableAnomalyDetection({
|
|
3282
|
-
rules: config.anomalyRules,
|
|
3283
|
-
thresholds: config.anomalyThresholds
|
|
4020
|
+
const { createPublicClient, http } = viem;
|
|
4021
|
+
const wallets = this.config.wallets.map((w) => w.toLowerCase());
|
|
4022
|
+
const pollingInterval = this.config.pollingIntervalMs ?? 12e3;
|
|
4023
|
+
const contractsByChain = /* @__PURE__ */ new Map();
|
|
4024
|
+
for (const [address, info] of Object.entries(STABLECOIN_CONTRACTS)) {
|
|
4025
|
+
if (this.tokens && !this.tokens.has(info.token)) continue;
|
|
4026
|
+
const existing = contractsByChain.get(info.chain) ?? [];
|
|
4027
|
+
existing.push({ address, info });
|
|
4028
|
+
contractsByChain.set(info.chain, existing);
|
|
4029
|
+
}
|
|
4030
|
+
for (const [chain, contracts] of contractsByChain) {
|
|
4031
|
+
const rpcUrl = this.config.rpcEndpoints[chain];
|
|
4032
|
+
if (!rpcUrl) continue;
|
|
4033
|
+
const client = createPublicClient({
|
|
4034
|
+
transport: http(rpcUrl)
|
|
3284
4035
|
});
|
|
4036
|
+
for (const { address, info } of contracts) {
|
|
4037
|
+
const unwatch = client.watchEvent({
|
|
4038
|
+
address,
|
|
4039
|
+
event: TRANSFER_EVENT_ABI,
|
|
4040
|
+
args: { from: wallets.length === 1 ? wallets[0] : wallets },
|
|
4041
|
+
poll: true,
|
|
4042
|
+
pollingInterval,
|
|
4043
|
+
onLogs: (logs) => {
|
|
4044
|
+
for (const log of logs) {
|
|
4045
|
+
this.handleTransferLog(log, info);
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
});
|
|
4049
|
+
this.unwatchers.push(unwatch);
|
|
4050
|
+
}
|
|
3285
4051
|
}
|
|
4052
|
+
this.cleanupTimer = setInterval(() => {
|
|
4053
|
+
const cutoff = Date.now() - 5 * 60 * 1e3;
|
|
4054
|
+
for (const [hash, ts] of this.txTimestamps) {
|
|
4055
|
+
if (ts < cutoff) {
|
|
4056
|
+
this.verifiedTxHashes.delete(hash);
|
|
4057
|
+
this.txTimestamps.delete(hash);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
}, 6e4);
|
|
4061
|
+
this.running = true;
|
|
3286
4062
|
}
|
|
3287
|
-
/**
|
|
3288
|
-
|
|
3289
|
-
|
|
4063
|
+
/** Stop all watchers and cleanup */
|
|
4064
|
+
stop() {
|
|
4065
|
+
for (const unwatch of this.unwatchers) {
|
|
4066
|
+
try {
|
|
4067
|
+
unwatch();
|
|
4068
|
+
} catch {
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
this.unwatchers.length = 0;
|
|
4072
|
+
if (this.cleanupTimer) {
|
|
4073
|
+
clearInterval(this.cleanupTimer);
|
|
4074
|
+
this.cleanupTimer = null;
|
|
4075
|
+
}
|
|
4076
|
+
this.running = false;
|
|
4077
|
+
}
|
|
4078
|
+
isRunning() {
|
|
4079
|
+
return this.running;
|
|
4080
|
+
}
|
|
4081
|
+
handleTransferLog(log, contractInfo) {
|
|
4082
|
+
const txHash = log.transactionHash;
|
|
4083
|
+
if (!txHash) return;
|
|
4084
|
+
const lowerHash = txHash.toLowerCase();
|
|
4085
|
+
if (this.verifiedTxHashes.has(lowerHash)) return;
|
|
4086
|
+
this.markVerified(txHash);
|
|
4087
|
+
const from = log.args?.from?.toLowerCase() ?? "";
|
|
4088
|
+
const to = log.args?.to?.toLowerCase() ?? "";
|
|
4089
|
+
const value = log.args?.value;
|
|
4090
|
+
if (!from || !to || value === void 0) return;
|
|
4091
|
+
const amount = formatTokenAmount(value, contractInfo.decimals);
|
|
4092
|
+
const verifyInput = {
|
|
4093
|
+
txHash,
|
|
4094
|
+
chain: contractInfo.chain,
|
|
4095
|
+
amount,
|
|
4096
|
+
token: contractInfo.token,
|
|
4097
|
+
from,
|
|
4098
|
+
to,
|
|
4099
|
+
agentId: this.agentId,
|
|
4100
|
+
metadata: {
|
|
4101
|
+
source: "wallet-monitor",
|
|
4102
|
+
contractAddress: log.address
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
this.kontext.verify(verifyInput).catch(() => {
|
|
4106
|
+
});
|
|
4107
|
+
}
|
|
4108
|
+
};
|
|
4109
|
+
function formatTokenAmount(amount, decimals) {
|
|
4110
|
+
const divisor = BigInt(10 ** decimals);
|
|
4111
|
+
const whole = amount / divisor;
|
|
4112
|
+
const fraction = amount % divisor;
|
|
4113
|
+
if (fraction === 0n) return whole.toString();
|
|
4114
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
4115
|
+
return `${whole}.${fractionStr}`;
|
|
4116
|
+
}
|
|
4117
|
+
var ProvenanceManager = class {
|
|
4118
|
+
store;
|
|
4119
|
+
logger;
|
|
4120
|
+
constructor(store, logger) {
|
|
4121
|
+
this.store = store;
|
|
4122
|
+
this.logger = logger;
|
|
4123
|
+
}
|
|
4124
|
+
// --------------------------------------------------------------------------
|
|
4125
|
+
// Layer 1: Session Delegation
|
|
4126
|
+
// --------------------------------------------------------------------------
|
|
4127
|
+
/**
|
|
4128
|
+
* Create a delegated agent session. Records the delegation in the
|
|
4129
|
+
* tamper-evident digest chain as the session's genesis event.
|
|
4130
|
+
*/
|
|
4131
|
+
async createSession(input) {
|
|
4132
|
+
if (!input.agentId) {
|
|
4133
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "agentId is required");
|
|
4134
|
+
}
|
|
4135
|
+
if (!input.delegatedBy) {
|
|
4136
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "delegatedBy is required");
|
|
4137
|
+
}
|
|
4138
|
+
if (!input.scope || input.scope.length === 0) {
|
|
4139
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "scope must contain at least one capability");
|
|
4140
|
+
}
|
|
4141
|
+
const sessionId = generateId();
|
|
4142
|
+
const createdAt = now();
|
|
4143
|
+
const constraints = input.constraints ? {
|
|
4144
|
+
...input.constraints,
|
|
4145
|
+
...input.constraints.allowedRecipients ? { allowedRecipients: input.constraints.allowedRecipients.map((a) => a.toLowerCase()) } : {}
|
|
4146
|
+
} : void 0;
|
|
4147
|
+
const session = {
|
|
4148
|
+
sessionId,
|
|
4149
|
+
agentId: input.agentId,
|
|
4150
|
+
delegatedBy: input.delegatedBy,
|
|
4151
|
+
scope: [...input.scope],
|
|
4152
|
+
...constraints ? { constraints } : {},
|
|
4153
|
+
status: "active",
|
|
4154
|
+
createdAt,
|
|
4155
|
+
...input.expiresIn ? { expiresAt: new Date(Date.now() + input.expiresIn).toISOString() } : {},
|
|
4156
|
+
metadata: input.metadata ? { ...input.metadata } : {}
|
|
4157
|
+
};
|
|
4158
|
+
const action = await this.logger.log({
|
|
4159
|
+
type: "session-start",
|
|
4160
|
+
description: `Session created: ${input.agentId} delegated by ${input.delegatedBy}`,
|
|
4161
|
+
agentId: input.agentId,
|
|
4162
|
+
sessionId,
|
|
4163
|
+
metadata: {
|
|
4164
|
+
delegatedBy: input.delegatedBy,
|
|
4165
|
+
scope: input.scope,
|
|
4166
|
+
...constraints ? { constraints } : {}
|
|
4167
|
+
}
|
|
4168
|
+
});
|
|
4169
|
+
session.digest = action.digest;
|
|
4170
|
+
session.priorDigest = action.priorDigest;
|
|
4171
|
+
this.store.addSession(session);
|
|
4172
|
+
return { ...session, scope: [...session.scope] };
|
|
4173
|
+
}
|
|
4174
|
+
/**
|
|
4175
|
+
* Get an agent session by ID. Automatically marks expired sessions.
|
|
4176
|
+
*/
|
|
4177
|
+
getSession(sessionId) {
|
|
4178
|
+
const session = this.store.getSession(sessionId);
|
|
4179
|
+
if (!session) return void 0;
|
|
4180
|
+
if (session.status === "active" && session.expiresAt && new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
4181
|
+
this.store.updateSession(sessionId, { status: "expired" });
|
|
4182
|
+
return { ...session, status: "expired", scope: [...session.scope] };
|
|
4183
|
+
}
|
|
4184
|
+
return { ...session, scope: [...session.scope] };
|
|
4185
|
+
}
|
|
4186
|
+
/**
|
|
4187
|
+
* Get all agent sessions.
|
|
4188
|
+
*/
|
|
4189
|
+
getSessions() {
|
|
4190
|
+
return this.store.getSessions().map((s) => {
|
|
4191
|
+
if (s.status === "active" && s.expiresAt && new Date(s.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
4192
|
+
this.store.updateSession(s.sessionId, { status: "expired" });
|
|
4193
|
+
return { ...s, status: "expired", scope: [...s.scope] };
|
|
4194
|
+
}
|
|
4195
|
+
return { ...s, scope: [...s.scope] };
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
/**
|
|
4199
|
+
* End an active agent session. Records the termination in the digest chain.
|
|
4200
|
+
*/
|
|
4201
|
+
async endSession(sessionId) {
|
|
4202
|
+
const session = this.store.getSession(sessionId);
|
|
4203
|
+
if (!session) {
|
|
4204
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session not found: ${sessionId}`);
|
|
4205
|
+
}
|
|
4206
|
+
if (session.status !== "active") {
|
|
4207
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session is already ${session.status}: ${sessionId}`);
|
|
4208
|
+
}
|
|
4209
|
+
const endedAt = now();
|
|
4210
|
+
await this.logger.log({
|
|
4211
|
+
type: "session-end",
|
|
4212
|
+
description: `Session ended: ${session.agentId}`,
|
|
4213
|
+
agentId: session.agentId,
|
|
4214
|
+
sessionId,
|
|
4215
|
+
metadata: { delegatedBy: session.delegatedBy }
|
|
4216
|
+
});
|
|
4217
|
+
const updated = this.store.updateSession(sessionId, { status: "ended", endedAt });
|
|
4218
|
+
if (!updated) {
|
|
4219
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Failed to update session: ${sessionId}`);
|
|
4220
|
+
}
|
|
4221
|
+
return { ...updated, scope: [...updated.scope] };
|
|
4222
|
+
}
|
|
4223
|
+
/**
|
|
4224
|
+
* Check whether an action is within a session's delegated scope.
|
|
4225
|
+
*/
|
|
4226
|
+
validateScope(sessionId, action) {
|
|
4227
|
+
const session = this.getSession(sessionId);
|
|
4228
|
+
if (!session || session.status !== "active") return false;
|
|
4229
|
+
return session.scope.includes(action);
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* Check whether a transaction meets session constraints.
|
|
4233
|
+
*/
|
|
4234
|
+
validateConstraints(sessionId, input) {
|
|
4235
|
+
const session = this.getSession(sessionId);
|
|
4236
|
+
if (!session || session.status !== "active") return false;
|
|
4237
|
+
if (!session.constraints) return true;
|
|
4238
|
+
const c = session.constraints;
|
|
4239
|
+
if (c.maxAmount && input.amount) {
|
|
4240
|
+
if (parseAmount(input.amount) > parseAmount(c.maxAmount)) return false;
|
|
4241
|
+
}
|
|
4242
|
+
if (c.allowedChains && input.chain) {
|
|
4243
|
+
if (!c.allowedChains.includes(input.chain)) return false;
|
|
4244
|
+
}
|
|
4245
|
+
if (c.allowedTokens && input.token) {
|
|
4246
|
+
if (!c.allowedTokens.includes(input.token)) return false;
|
|
4247
|
+
}
|
|
4248
|
+
if (c.allowedRecipients && input.to) {
|
|
4249
|
+
if (!c.allowedRecipients.includes(input.to.toLowerCase())) return false;
|
|
4250
|
+
}
|
|
4251
|
+
return true;
|
|
4252
|
+
}
|
|
4253
|
+
// --------------------------------------------------------------------------
|
|
4254
|
+
// Layer 3: Checkpoints & Human Attestation
|
|
4255
|
+
// --------------------------------------------------------------------------
|
|
4256
|
+
/**
|
|
4257
|
+
* Create a provenance checkpoint -- a review point where a human
|
|
4258
|
+
* can attest to a batch of agent actions.
|
|
4259
|
+
*/
|
|
4260
|
+
async createCheckpoint(input) {
|
|
4261
|
+
const session = this.getSession(input.sessionId);
|
|
4262
|
+
if (!session) {
|
|
4263
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session not found: ${input.sessionId}`);
|
|
4264
|
+
}
|
|
4265
|
+
if (session.status !== "active") {
|
|
4266
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session is ${session.status}: ${input.sessionId}`);
|
|
4267
|
+
}
|
|
4268
|
+
if (!input.actionIds || input.actionIds.length === 0) {
|
|
4269
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "actionIds must contain at least one action");
|
|
4270
|
+
}
|
|
4271
|
+
if (!input.summary) {
|
|
4272
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "summary is required");
|
|
4273
|
+
}
|
|
4274
|
+
const sessionActions = this.store.getActionsBySession(input.sessionId);
|
|
4275
|
+
const sessionActionIds = new Set(sessionActions.map((a) => a.id));
|
|
4276
|
+
for (const actionId of input.actionIds) {
|
|
4277
|
+
if (!sessionActionIds.has(actionId)) {
|
|
4278
|
+
throw new KontextError(
|
|
4279
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
4280
|
+
`Action ${actionId} not found in session ${input.sessionId}`
|
|
4281
|
+
);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
const actionDigests = input.actionIds.map((id) => {
|
|
4285
|
+
const action = sessionActions.find((a) => a.id === id);
|
|
4286
|
+
return action?.digest ?? "";
|
|
4287
|
+
}).sort();
|
|
4288
|
+
const hash = crypto$1.createHash("sha256");
|
|
4289
|
+
hash.update(actionDigests.join(""));
|
|
4290
|
+
const actionsDigest = hash.digest("hex");
|
|
4291
|
+
const checkpointId = generateId();
|
|
4292
|
+
const createdAt = now();
|
|
4293
|
+
const checkpoint = {
|
|
4294
|
+
id: checkpointId,
|
|
4295
|
+
sessionId: input.sessionId,
|
|
4296
|
+
actionIds: [...input.actionIds],
|
|
4297
|
+
summary: input.summary,
|
|
4298
|
+
actionsDigest,
|
|
4299
|
+
status: "pending",
|
|
4300
|
+
createdAt,
|
|
4301
|
+
...input.expiresIn ? { expiresAt: new Date(Date.now() + input.expiresIn).toISOString() } : {}
|
|
4302
|
+
};
|
|
4303
|
+
await this.logger.log({
|
|
4304
|
+
type: "checkpoint-created",
|
|
4305
|
+
description: `Checkpoint created: ${input.summary}`,
|
|
4306
|
+
agentId: session.agentId,
|
|
4307
|
+
sessionId: input.sessionId,
|
|
4308
|
+
metadata: {
|
|
4309
|
+
checkpointId,
|
|
4310
|
+
actionCount: input.actionIds.length,
|
|
4311
|
+
actionsDigest
|
|
4312
|
+
}
|
|
4313
|
+
});
|
|
4314
|
+
this.store.addCheckpoint(checkpoint);
|
|
4315
|
+
return { ...checkpoint, actionIds: [...checkpoint.actionIds] };
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
4318
|
+
* Attach an externally-produced human attestation to a checkpoint.
|
|
4319
|
+
* The attestation includes a cryptographic signature that the agent
|
|
4320
|
+
* never touches -- key separation is the critical security property.
|
|
4321
|
+
*/
|
|
4322
|
+
async attachAttestation(checkpointId, attestation) {
|
|
4323
|
+
const checkpoint = this.store.getCheckpoint(checkpointId);
|
|
4324
|
+
if (!checkpoint) {
|
|
4325
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Checkpoint not found: ${checkpointId}`);
|
|
4326
|
+
}
|
|
4327
|
+
if (checkpoint.status === "pending" && checkpoint.expiresAt && new Date(checkpoint.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
4328
|
+
this.store.updateCheckpoint(checkpointId, { status: "expired" });
|
|
4329
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Checkpoint expired: ${checkpointId}`);
|
|
4330
|
+
}
|
|
4331
|
+
if (checkpoint.status !== "pending") {
|
|
4332
|
+
throw new KontextError(
|
|
4333
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
4334
|
+
`Checkpoint is already ${checkpoint.status}: ${checkpointId}`
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
if (attestation.checkpointId !== checkpointId) {
|
|
4338
|
+
throw new KontextError(
|
|
4339
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
4340
|
+
`Attestation checkpointId mismatch: expected ${checkpointId}, got ${attestation.checkpointId}`
|
|
4341
|
+
);
|
|
4342
|
+
}
|
|
4343
|
+
const newStatus = attestation.decision === "approved" ? "attested" : "rejected";
|
|
4344
|
+
const session = this.store.getSession(checkpoint.sessionId);
|
|
4345
|
+
const agentId = session?.agentId ?? "unknown";
|
|
4346
|
+
await this.logger.log({
|
|
4347
|
+
type: `checkpoint-${newStatus}`,
|
|
4348
|
+
description: `Checkpoint ${newStatus} by ${attestation.reviewerId}`,
|
|
4349
|
+
agentId,
|
|
4350
|
+
sessionId: checkpoint.sessionId,
|
|
4351
|
+
metadata: {
|
|
4352
|
+
checkpointId,
|
|
4353
|
+
reviewerId: attestation.reviewerId,
|
|
4354
|
+
decision: attestation.decision,
|
|
4355
|
+
attestationId: attestation.attestationId
|
|
4356
|
+
}
|
|
4357
|
+
});
|
|
4358
|
+
const updated = this.store.updateCheckpoint(checkpointId, {
|
|
4359
|
+
status: newStatus,
|
|
4360
|
+
attestation
|
|
4361
|
+
});
|
|
4362
|
+
if (!updated) {
|
|
4363
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Failed to update checkpoint: ${checkpointId}`);
|
|
4364
|
+
}
|
|
4365
|
+
return { ...updated, actionIds: [...updated.actionIds] };
|
|
4366
|
+
}
|
|
4367
|
+
/**
|
|
4368
|
+
* Get a checkpoint by ID. Automatically marks expired checkpoints.
|
|
4369
|
+
*/
|
|
4370
|
+
getCheckpoint(checkpointId) {
|
|
4371
|
+
const cp = this.store.getCheckpoint(checkpointId);
|
|
4372
|
+
if (!cp) return void 0;
|
|
4373
|
+
if (cp.status === "pending" && cp.expiresAt && new Date(cp.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
4374
|
+
this.store.updateCheckpoint(checkpointId, { status: "expired" });
|
|
4375
|
+
return { ...cp, status: "expired", actionIds: [...cp.actionIds] };
|
|
4376
|
+
}
|
|
4377
|
+
return { ...cp, actionIds: [...cp.actionIds] };
|
|
4378
|
+
}
|
|
4379
|
+
/**
|
|
4380
|
+
* Get all checkpoints, optionally filtered by session.
|
|
4381
|
+
*/
|
|
4382
|
+
getCheckpoints(sessionId) {
|
|
4383
|
+
const all = sessionId ? this.store.queryCheckpoints((cp) => cp.sessionId === sessionId) : this.store.getCheckpoints();
|
|
4384
|
+
return all.map((cp) => {
|
|
4385
|
+
if (cp.status === "pending" && cp.expiresAt && new Date(cp.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
4386
|
+
this.store.updateCheckpoint(cp.id, { status: "expired" });
|
|
4387
|
+
return { ...cp, status: "expired", actionIds: [...cp.actionIds] };
|
|
4388
|
+
}
|
|
4389
|
+
return { ...cp, actionIds: [...cp.actionIds] };
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
// --------------------------------------------------------------------------
|
|
4393
|
+
// Provenance Bundle Export
|
|
4394
|
+
// --------------------------------------------------------------------------
|
|
4395
|
+
/**
|
|
4396
|
+
* Export the full provenance bundle for a session.
|
|
4397
|
+
*/
|
|
4398
|
+
getProvenanceBundle(sessionId) {
|
|
4399
|
+
const session = this.getSession(sessionId);
|
|
4400
|
+
if (!session) {
|
|
4401
|
+
throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session not found: ${sessionId}`);
|
|
4402
|
+
}
|
|
4403
|
+
const sessionActions = this.store.getActionsBySession(sessionId);
|
|
4404
|
+
const checkpoints = this.getCheckpoints(sessionId);
|
|
4405
|
+
const actions = sessionActions.filter((a) => a.digest && a.priorDigest).map((a) => ({
|
|
4406
|
+
actionId: a.id,
|
|
4407
|
+
type: a.type,
|
|
4408
|
+
digest: a.digest,
|
|
4409
|
+
priorDigest: a.priorDigest,
|
|
4410
|
+
sessionId,
|
|
4411
|
+
timestamp: a.timestamp
|
|
4412
|
+
}));
|
|
4413
|
+
const attestedCheckpoints = checkpoints.filter((cp) => cp.status === "attested");
|
|
4414
|
+
const attestedActionIds = /* @__PURE__ */ new Set();
|
|
4415
|
+
for (const cp of attestedCheckpoints) {
|
|
4416
|
+
for (const actionId of cp.actionIds) {
|
|
4417
|
+
attestedActionIds.add(actionId);
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
const userActions = sessionActions.filter(
|
|
4421
|
+
(a) => !a.type.startsWith("session-") && !a.type.startsWith("checkpoint-")
|
|
4422
|
+
);
|
|
4423
|
+
const verification = {
|
|
4424
|
+
digestChainValid: this.logger.verifyChain(this.store.getActions()).valid,
|
|
4425
|
+
totalActions: userActions.length,
|
|
4426
|
+
humanAttested: userActions.filter((a) => attestedActionIds.has(a.id)).length,
|
|
4427
|
+
sessionScoped: userActions.length,
|
|
4428
|
+
unattested: userActions.filter((a) => !attestedActionIds.has(a.id)).length
|
|
4429
|
+
};
|
|
4430
|
+
return {
|
|
4431
|
+
session,
|
|
4432
|
+
actions,
|
|
4433
|
+
checkpoints,
|
|
4434
|
+
verification,
|
|
4435
|
+
generatedAt: now()
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
};
|
|
4439
|
+
|
|
4440
|
+
// src/kya/identity-registry.ts
|
|
4441
|
+
var AgentIdentityRegistry = class {
|
|
4442
|
+
/** agentId -> AgentIdentity */
|
|
4443
|
+
identities = /* @__PURE__ */ new Map();
|
|
4444
|
+
/** normalized address -> agentId (reverse index) */
|
|
4445
|
+
walletIndex = /* @__PURE__ */ new Map();
|
|
4446
|
+
/**
|
|
4447
|
+
* Register a new agent identity.
|
|
4448
|
+
*/
|
|
4449
|
+
register(input) {
|
|
4450
|
+
if (this.identities.has(input.agentId)) {
|
|
4451
|
+
throw new Error(`Identity already registered for agent: ${input.agentId}`);
|
|
4452
|
+
}
|
|
4453
|
+
const timestamp = now();
|
|
4454
|
+
const wallets = (input.wallets ?? []).map((w) => ({
|
|
4455
|
+
address: w.address.toLowerCase(),
|
|
4456
|
+
chain: w.chain,
|
|
4457
|
+
verified: false,
|
|
4458
|
+
addedAt: timestamp,
|
|
4459
|
+
label: w.label
|
|
4460
|
+
}));
|
|
4461
|
+
const identity = {
|
|
4462
|
+
agentId: input.agentId,
|
|
4463
|
+
displayName: input.displayName,
|
|
4464
|
+
entityType: input.entityType ?? "unknown",
|
|
4465
|
+
wallets,
|
|
4466
|
+
kycReferences: [],
|
|
4467
|
+
contactUri: input.contactUri,
|
|
4468
|
+
metadata: input.metadata ?? {},
|
|
4469
|
+
createdAt: timestamp,
|
|
4470
|
+
updatedAt: timestamp
|
|
4471
|
+
};
|
|
4472
|
+
this.identities.set(input.agentId, identity);
|
|
4473
|
+
for (const wallet of wallets) {
|
|
4474
|
+
this.walletIndex.set(wallet.address, input.agentId);
|
|
4475
|
+
}
|
|
4476
|
+
return { ...identity, wallets: [...wallets] };
|
|
4477
|
+
}
|
|
4478
|
+
/**
|
|
4479
|
+
* Update an existing agent identity.
|
|
4480
|
+
*/
|
|
4481
|
+
update(agentId, input) {
|
|
4482
|
+
const existing = this.identities.get(agentId);
|
|
4483
|
+
if (!existing) {
|
|
4484
|
+
throw new Error(`Identity not found for agent: ${agentId}`);
|
|
4485
|
+
}
|
|
4486
|
+
const updated = {
|
|
4487
|
+
...existing,
|
|
4488
|
+
updatedAt: now()
|
|
4489
|
+
};
|
|
4490
|
+
if (input.displayName !== void 0) updated.displayName = input.displayName;
|
|
4491
|
+
if (input.entityType !== void 0) updated.entityType = input.entityType;
|
|
4492
|
+
if (input.contactUri !== void 0) updated.contactUri = input.contactUri;
|
|
4493
|
+
if (input.metadata !== void 0) {
|
|
4494
|
+
updated.metadata = { ...existing.metadata, ...input.metadata };
|
|
4495
|
+
}
|
|
4496
|
+
this.identities.set(agentId, updated);
|
|
4497
|
+
return { ...updated, wallets: [...updated.wallets] };
|
|
4498
|
+
}
|
|
4499
|
+
/**
|
|
4500
|
+
* Get an agent identity by agent ID.
|
|
4501
|
+
*/
|
|
4502
|
+
get(agentId) {
|
|
4503
|
+
const identity = this.identities.get(agentId);
|
|
4504
|
+
if (!identity) return void 0;
|
|
4505
|
+
return { ...identity, wallets: [...identity.wallets] };
|
|
4506
|
+
}
|
|
4507
|
+
/**
|
|
4508
|
+
* Remove an agent identity and all wallet index entries.
|
|
4509
|
+
*/
|
|
4510
|
+
remove(agentId) {
|
|
4511
|
+
const identity = this.identities.get(agentId);
|
|
4512
|
+
if (!identity) return false;
|
|
4513
|
+
for (const wallet of identity.wallets) {
|
|
4514
|
+
this.walletIndex.delete(wallet.address);
|
|
4515
|
+
}
|
|
4516
|
+
this.identities.delete(agentId);
|
|
4517
|
+
return true;
|
|
4518
|
+
}
|
|
4519
|
+
/**
|
|
4520
|
+
* Get all registered identities.
|
|
4521
|
+
*/
|
|
4522
|
+
getAll() {
|
|
4523
|
+
return Array.from(this.identities.values()).map((id) => ({
|
|
4524
|
+
...id,
|
|
4525
|
+
wallets: [...id.wallets]
|
|
4526
|
+
}));
|
|
4527
|
+
}
|
|
4528
|
+
// --------------------------------------------------------------------------
|
|
4529
|
+
// Wallet Operations
|
|
4530
|
+
// --------------------------------------------------------------------------
|
|
4531
|
+
/**
|
|
4532
|
+
* Add a wallet to an existing agent identity.
|
|
4533
|
+
*/
|
|
4534
|
+
addWallet(agentId, wallet) {
|
|
4535
|
+
const identity = this.identities.get(agentId);
|
|
4536
|
+
if (!identity) {
|
|
4537
|
+
throw new Error(`Identity not found for agent: ${agentId}`);
|
|
4538
|
+
}
|
|
4539
|
+
const normalized = wallet.address.toLowerCase();
|
|
4540
|
+
const existingOwner = this.walletIndex.get(normalized);
|
|
4541
|
+
if (existingOwner && existingOwner !== agentId) {
|
|
4542
|
+
throw new Error(`Wallet ${normalized} is already registered to agent: ${existingOwner}`);
|
|
4543
|
+
}
|
|
4544
|
+
if (identity.wallets.some((w) => w.address === normalized)) {
|
|
4545
|
+
return { ...identity, wallets: [...identity.wallets] };
|
|
4546
|
+
}
|
|
4547
|
+
const mapping = {
|
|
4548
|
+
address: normalized,
|
|
4549
|
+
chain: wallet.chain,
|
|
4550
|
+
verified: false,
|
|
4551
|
+
addedAt: now(),
|
|
4552
|
+
label: wallet.label
|
|
4553
|
+
};
|
|
4554
|
+
identity.wallets.push(mapping);
|
|
4555
|
+
identity.updatedAt = now();
|
|
4556
|
+
this.walletIndex.set(normalized, agentId);
|
|
4557
|
+
return { ...identity, wallets: [...identity.wallets] };
|
|
4558
|
+
}
|
|
4559
|
+
/**
|
|
4560
|
+
* Remove a wallet from an agent identity.
|
|
4561
|
+
*/
|
|
4562
|
+
removeWallet(agentId, address) {
|
|
4563
|
+
const identity = this.identities.get(agentId);
|
|
4564
|
+
if (!identity) {
|
|
4565
|
+
throw new Error(`Identity not found for agent: ${agentId}`);
|
|
4566
|
+
}
|
|
4567
|
+
const normalized = address.toLowerCase();
|
|
4568
|
+
identity.wallets = identity.wallets.filter((w) => w.address !== normalized);
|
|
4569
|
+
identity.updatedAt = now();
|
|
4570
|
+
this.walletIndex.delete(normalized);
|
|
4571
|
+
return { ...identity, wallets: [...identity.wallets] };
|
|
4572
|
+
}
|
|
4573
|
+
/**
|
|
4574
|
+
* Look up an agent identity by wallet address.
|
|
4575
|
+
*/
|
|
4576
|
+
lookupByWallet(address) {
|
|
4577
|
+
const agentId = this.walletIndex.get(address.toLowerCase());
|
|
4578
|
+
if (!agentId) return void 0;
|
|
4579
|
+
return this.get(agentId);
|
|
4580
|
+
}
|
|
4581
|
+
// --------------------------------------------------------------------------
|
|
4582
|
+
// KYC Operations
|
|
4583
|
+
// --------------------------------------------------------------------------
|
|
4584
|
+
/**
|
|
4585
|
+
* Add a KYC provider reference to an agent identity.
|
|
4586
|
+
*/
|
|
4587
|
+
addKycReference(agentId, reference) {
|
|
4588
|
+
const identity = this.identities.get(agentId);
|
|
4589
|
+
if (!identity) {
|
|
4590
|
+
throw new Error(`Identity not found for agent: ${agentId}`);
|
|
4591
|
+
}
|
|
4592
|
+
identity.kycReferences.push(reference);
|
|
4593
|
+
identity.updatedAt = now();
|
|
4594
|
+
return { ...identity, wallets: [...identity.wallets] };
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* Get the overall KYC status for an agent.
|
|
4598
|
+
* Returns the best status from all references.
|
|
4599
|
+
*/
|
|
4600
|
+
getKycStatus(agentId) {
|
|
4601
|
+
const identity = this.identities.get(agentId);
|
|
4602
|
+
if (!identity || identity.kycReferences.length === 0) return "none";
|
|
4603
|
+
const statusPriority = {
|
|
4604
|
+
verified: 4,
|
|
4605
|
+
pending: 3,
|
|
4606
|
+
expired: 2,
|
|
4607
|
+
rejected: 1,
|
|
4608
|
+
none: 0
|
|
4609
|
+
};
|
|
4610
|
+
let best = "none";
|
|
4611
|
+
for (const ref of identity.kycReferences) {
|
|
4612
|
+
if (statusPriority[ref.status] > statusPriority[best]) {
|
|
4613
|
+
best = ref.status;
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
return best;
|
|
4617
|
+
}
|
|
4618
|
+
/**
|
|
4619
|
+
* Check if an agent has at least one verified and non-expired KYC reference.
|
|
4620
|
+
*/
|
|
4621
|
+
hasVerifiedKyc(agentId) {
|
|
4622
|
+
const identity = this.identities.get(agentId);
|
|
4623
|
+
if (!identity) return false;
|
|
4624
|
+
const currentTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
4625
|
+
return identity.kycReferences.some(
|
|
4626
|
+
(ref) => ref.status === "verified" && (ref.expiresAt === null || ref.expiresAt > currentTime)
|
|
4627
|
+
);
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
|
|
4631
|
+
// src/kya/wallet-clustering.ts
|
|
4632
|
+
var UnionFind = class {
|
|
4633
|
+
parent = /* @__PURE__ */ new Map();
|
|
4634
|
+
rank = /* @__PURE__ */ new Map();
|
|
4635
|
+
/**
|
|
4636
|
+
* Find the representative of the set containing x.
|
|
4637
|
+
* Uses path compression for amortized near-constant time.
|
|
4638
|
+
*/
|
|
4639
|
+
find(x) {
|
|
4640
|
+
if (!this.parent.has(x)) {
|
|
4641
|
+
this.parent.set(x, x);
|
|
4642
|
+
this.rank.set(x, 0);
|
|
4643
|
+
}
|
|
4644
|
+
let root = x;
|
|
4645
|
+
while (this.parent.get(root) !== root) {
|
|
4646
|
+
root = this.parent.get(root);
|
|
4647
|
+
}
|
|
4648
|
+
let current = x;
|
|
4649
|
+
while (current !== root) {
|
|
4650
|
+
const next = this.parent.get(current);
|
|
4651
|
+
this.parent.set(current, root);
|
|
4652
|
+
current = next;
|
|
4653
|
+
}
|
|
4654
|
+
return root;
|
|
4655
|
+
}
|
|
4656
|
+
/**
|
|
4657
|
+
* Union the sets containing x and y.
|
|
4658
|
+
* Uses union-by-rank for balanced trees.
|
|
4659
|
+
*/
|
|
4660
|
+
union(x, y) {
|
|
4661
|
+
const rootX = this.find(x);
|
|
4662
|
+
const rootY = this.find(y);
|
|
4663
|
+
if (rootX === rootY) return;
|
|
4664
|
+
const rankX = this.rank.get(rootX);
|
|
4665
|
+
const rankY = this.rank.get(rootY);
|
|
4666
|
+
if (rankX < rankY) {
|
|
4667
|
+
this.parent.set(rootX, rootY);
|
|
4668
|
+
} else if (rankX > rankY) {
|
|
4669
|
+
this.parent.set(rootY, rootX);
|
|
4670
|
+
} else {
|
|
4671
|
+
this.parent.set(rootY, rootX);
|
|
4672
|
+
this.rank.set(rootX, rankX + 1);
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
/**
|
|
4676
|
+
* Check if x and y are in the same set.
|
|
4677
|
+
*/
|
|
4678
|
+
connected(x, y) {
|
|
4679
|
+
return this.find(x) === this.find(y);
|
|
4680
|
+
}
|
|
4681
|
+
/**
|
|
4682
|
+
* Get all components as arrays of elements.
|
|
4683
|
+
*/
|
|
4684
|
+
getComponents() {
|
|
4685
|
+
const components = /* @__PURE__ */ new Map();
|
|
4686
|
+
for (const key of this.parent.keys()) {
|
|
4687
|
+
const root = this.find(key);
|
|
4688
|
+
if (!components.has(root)) {
|
|
4689
|
+
components.set(root, []);
|
|
4690
|
+
}
|
|
4691
|
+
components.get(root).push(key);
|
|
4692
|
+
}
|
|
4693
|
+
return Array.from(components.values());
|
|
4694
|
+
}
|
|
4695
|
+
/**
|
|
4696
|
+
* Get the component containing x.
|
|
4697
|
+
*/
|
|
4698
|
+
getComponentOf(x) {
|
|
4699
|
+
const root = this.find(x);
|
|
4700
|
+
const component = [];
|
|
4701
|
+
for (const key of this.parent.keys()) {
|
|
4702
|
+
if (this.find(key) === root) {
|
|
4703
|
+
component.push(key);
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
return component;
|
|
4707
|
+
}
|
|
4708
|
+
};
|
|
4709
|
+
var WalletClusterer = class {
|
|
4710
|
+
config;
|
|
4711
|
+
cachedClusters = null;
|
|
4712
|
+
constructor(config = {}) {
|
|
4713
|
+
this.config = {
|
|
4714
|
+
temporalWindowSeconds: config.temporalWindowSeconds ?? 60,
|
|
4715
|
+
minDestinationOverlap: config.minDestinationOverlap ?? 0.3,
|
|
4716
|
+
minGasSponsoredWallets: config.minGasSponsoredWallets ?? 3
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
/**
|
|
4720
|
+
* Analyze transactions from the store and registry to produce wallet clusters.
|
|
4721
|
+
*/
|
|
4722
|
+
analyzeFromStore(store, registry) {
|
|
4723
|
+
const uf = new UnionFind();
|
|
4724
|
+
const evidence = [];
|
|
4725
|
+
const transactions = store.getTransactions();
|
|
4726
|
+
const timestamp = now();
|
|
4727
|
+
for (const tx of transactions) {
|
|
4728
|
+
uf.find(tx.from.toLowerCase());
|
|
4729
|
+
uf.find(tx.to.toLowerCase());
|
|
4730
|
+
}
|
|
4731
|
+
this.applyCommonAgent(store, uf, evidence, timestamp);
|
|
4732
|
+
this.applyFundingChain(store, uf, evidence, timestamp);
|
|
4733
|
+
this.applyGasSponsorship(store, uf, evidence, timestamp);
|
|
4734
|
+
this.applyTemporalCoSpending(store, uf, evidence, timestamp);
|
|
4735
|
+
if (registry) {
|
|
4736
|
+
this.applyDeclaredWallets(registry, uf, evidence, timestamp);
|
|
4737
|
+
}
|
|
4738
|
+
const clusters = this.buildClusters(uf, evidence, store, registry, timestamp);
|
|
4739
|
+
this.cachedClusters = clusters;
|
|
4740
|
+
return clusters;
|
|
4741
|
+
}
|
|
4742
|
+
/**
|
|
4743
|
+
* Get cached clusters (or empty if not analyzed yet).
|
|
4744
|
+
*/
|
|
4745
|
+
getClusters() {
|
|
4746
|
+
return this.cachedClusters ?? [];
|
|
4747
|
+
}
|
|
4748
|
+
/**
|
|
4749
|
+
* Invalidate the cached clusters.
|
|
4750
|
+
*/
|
|
4751
|
+
invalidateCache() {
|
|
4752
|
+
this.cachedClusters = null;
|
|
4753
|
+
}
|
|
4754
|
+
// --------------------------------------------------------------------------
|
|
4755
|
+
// Heuristics
|
|
4756
|
+
// --------------------------------------------------------------------------
|
|
4757
|
+
/**
|
|
4758
|
+
* Heuristic 1: Common Agent (confidence 0.9)
|
|
4759
|
+
* Same agentId used multiple addresses -> union all from/to per agent.
|
|
4760
|
+
*/
|
|
4761
|
+
applyCommonAgent(store, uf, evidence, timestamp) {
|
|
4762
|
+
const transactions = store.getTransactions();
|
|
4763
|
+
const agentAddresses = /* @__PURE__ */ new Map();
|
|
4764
|
+
for (const tx of transactions) {
|
|
4765
|
+
const from = tx.from.toLowerCase();
|
|
4766
|
+
const agentId = tx.agentId;
|
|
4767
|
+
if (!agentAddresses.has(agentId)) {
|
|
4768
|
+
agentAddresses.set(agentId, /* @__PURE__ */ new Set());
|
|
4769
|
+
}
|
|
4770
|
+
agentAddresses.get(agentId).add(from);
|
|
4771
|
+
}
|
|
4772
|
+
for (const [, addresses] of agentAddresses) {
|
|
4773
|
+
const addrs = Array.from(addresses);
|
|
4774
|
+
for (let i = 1; i < addrs.length; i++) {
|
|
4775
|
+
if (!uf.connected(addrs[0], addrs[i])) {
|
|
4776
|
+
uf.union(addrs[0], addrs[i]);
|
|
4777
|
+
evidence.push({
|
|
4778
|
+
heuristic: "common-agent",
|
|
4779
|
+
confidence: 0.9,
|
|
4780
|
+
addresses: [addrs[0], addrs[i]],
|
|
4781
|
+
detectedAt: timestamp
|
|
4782
|
+
});
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
/**
|
|
4788
|
+
* Heuristic 2: Funding Chain (confidence 0.7)
|
|
4789
|
+
* If A sends to B, and A is agent-associated -> union A and B.
|
|
4790
|
+
*/
|
|
4791
|
+
applyFundingChain(store, uf, evidence, timestamp) {
|
|
4792
|
+
const transactions = store.getTransactions();
|
|
4793
|
+
const agentAssociated = /* @__PURE__ */ new Set();
|
|
4794
|
+
for (const tx of transactions) {
|
|
4795
|
+
agentAssociated.add(tx.from.toLowerCase());
|
|
4796
|
+
}
|
|
4797
|
+
for (const tx of transactions) {
|
|
4798
|
+
const from = tx.from.toLowerCase();
|
|
4799
|
+
const to = tx.to.toLowerCase();
|
|
4800
|
+
if (agentAssociated.has(from) && !uf.connected(from, to)) {
|
|
4801
|
+
uf.union(from, to);
|
|
4802
|
+
evidence.push({
|
|
4803
|
+
heuristic: "funding-chain",
|
|
4804
|
+
confidence: 0.7,
|
|
4805
|
+
addresses: [from, to],
|
|
4806
|
+
detectedAt: timestamp
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
/**
|
|
4812
|
+
* Heuristic 3: Gas Sponsorship (confidence 0.75)
|
|
4813
|
+
* If G is from in transfers to 3+ distinct agent wallets -> union G with all.
|
|
4814
|
+
*/
|
|
4815
|
+
applyGasSponsorship(store, uf, evidence, timestamp) {
|
|
4816
|
+
const transactions = store.getTransactions();
|
|
4817
|
+
const agentFromAddresses = /* @__PURE__ */ new Set();
|
|
4818
|
+
for (const tx of transactions) {
|
|
4819
|
+
agentFromAddresses.add(tx.from.toLowerCase());
|
|
4820
|
+
}
|
|
4821
|
+
const senderToRecipients = /* @__PURE__ */ new Map();
|
|
4822
|
+
for (const tx of transactions) {
|
|
4823
|
+
const from = tx.from.toLowerCase();
|
|
4824
|
+
const to = tx.to.toLowerCase();
|
|
4825
|
+
if (!senderToRecipients.has(from)) {
|
|
4826
|
+
senderToRecipients.set(from, /* @__PURE__ */ new Set());
|
|
4827
|
+
}
|
|
4828
|
+
senderToRecipients.get(from).add(to);
|
|
4829
|
+
}
|
|
4830
|
+
for (const [sender, recipients] of senderToRecipients) {
|
|
4831
|
+
const agentRecipients = Array.from(recipients).filter(
|
|
4832
|
+
(r) => agentFromAddresses.has(r)
|
|
4833
|
+
);
|
|
4834
|
+
if (agentRecipients.length >= this.config.minGasSponsoredWallets) {
|
|
4835
|
+
for (const recipient of agentRecipients) {
|
|
4836
|
+
if (!uf.connected(sender, recipient)) {
|
|
4837
|
+
uf.union(sender, recipient);
|
|
4838
|
+
evidence.push({
|
|
4839
|
+
heuristic: "gas-sponsorship",
|
|
4840
|
+
confidence: 0.75,
|
|
4841
|
+
addresses: [sender, recipient],
|
|
4842
|
+
detectedAt: timestamp,
|
|
4843
|
+
detail: `Sender ${sender} sponsors ${agentRecipients.length} agent wallets`
|
|
4844
|
+
});
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
/**
|
|
4851
|
+
* Heuristic 4: Temporal Co-Spending (confidence 0.6)
|
|
4852
|
+
* Addresses transacting within a window with destination overlap.
|
|
4853
|
+
*/
|
|
4854
|
+
applyTemporalCoSpending(store, uf, evidence, timestamp) {
|
|
4855
|
+
const transactions = store.getTransactions();
|
|
4856
|
+
if (transactions.length < 2) return;
|
|
4857
|
+
const txByFrom = /* @__PURE__ */ new Map();
|
|
4858
|
+
for (const tx of transactions) {
|
|
4859
|
+
const from = tx.from.toLowerCase();
|
|
4860
|
+
const to = tx.to.toLowerCase();
|
|
4861
|
+
if (!txByFrom.has(from)) {
|
|
4862
|
+
txByFrom.set(from, []);
|
|
4863
|
+
}
|
|
4864
|
+
txByFrom.get(from).push({
|
|
4865
|
+
to,
|
|
4866
|
+
time: new Date(tx.timestamp).getTime() / 1e3
|
|
4867
|
+
});
|
|
4868
|
+
}
|
|
4869
|
+
const fromAddresses = Array.from(txByFrom.keys());
|
|
4870
|
+
for (let i = 0; i < fromAddresses.length; i++) {
|
|
4871
|
+
for (let j = i + 1; j < fromAddresses.length; j++) {
|
|
4872
|
+
const addrA = fromAddresses[i];
|
|
4873
|
+
const addrB = fromAddresses[j];
|
|
4874
|
+
const txsA = txByFrom.get(addrA);
|
|
4875
|
+
const txsB = txByFrom.get(addrB);
|
|
4876
|
+
let hasTemporalOverlap = false;
|
|
4877
|
+
for (const a of txsA) {
|
|
4878
|
+
for (const b of txsB) {
|
|
4879
|
+
if (Math.abs(a.time - b.time) <= this.config.temporalWindowSeconds) {
|
|
4880
|
+
hasTemporalOverlap = true;
|
|
4881
|
+
break;
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
if (hasTemporalOverlap) break;
|
|
4885
|
+
}
|
|
4886
|
+
if (!hasTemporalOverlap) continue;
|
|
4887
|
+
const destsA = new Set(txsA.map((t) => t.to));
|
|
4888
|
+
const destsB = new Set(txsB.map((t) => t.to));
|
|
4889
|
+
const intersection = new Set([...destsA].filter((d) => destsB.has(d)));
|
|
4890
|
+
const union = /* @__PURE__ */ new Set([...destsA, ...destsB]);
|
|
4891
|
+
const overlapRatio = union.size > 0 ? intersection.size / union.size : 0;
|
|
4892
|
+
if (overlapRatio >= this.config.minDestinationOverlap) {
|
|
4893
|
+
if (!uf.connected(addrA, addrB)) {
|
|
4894
|
+
uf.union(addrA, addrB);
|
|
4895
|
+
evidence.push({
|
|
4896
|
+
heuristic: "temporal-co-spending",
|
|
4897
|
+
confidence: 0.6,
|
|
4898
|
+
addresses: [addrA, addrB],
|
|
4899
|
+
detectedAt: timestamp,
|
|
4900
|
+
detail: `Overlap ratio: ${overlapRatio.toFixed(2)}`
|
|
4901
|
+
});
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
/**
|
|
4908
|
+
* Heuristic 5: Declared Wallets (confidence 1.0)
|
|
4909
|
+
* All wallets declared by the same identity are unioned.
|
|
4910
|
+
*/
|
|
4911
|
+
applyDeclaredWallets(registry, uf, evidence, timestamp) {
|
|
4912
|
+
const identities = registry.getAll();
|
|
4913
|
+
for (const identity of identities) {
|
|
4914
|
+
const addrs = identity.wallets.map((w) => w.address);
|
|
4915
|
+
for (let i = 1; i < addrs.length; i++) {
|
|
4916
|
+
if (!uf.connected(addrs[0], addrs[i])) {
|
|
4917
|
+
uf.union(addrs[0], addrs[i]);
|
|
4918
|
+
evidence.push({
|
|
4919
|
+
heuristic: "declared-wallets",
|
|
4920
|
+
confidence: 1,
|
|
4921
|
+
addresses: [addrs[0], addrs[i]],
|
|
4922
|
+
detectedAt: timestamp
|
|
4923
|
+
});
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
// --------------------------------------------------------------------------
|
|
4929
|
+
// Cluster Building
|
|
4930
|
+
// --------------------------------------------------------------------------
|
|
4931
|
+
buildClusters(uf, evidence, store, registry, timestamp) {
|
|
4932
|
+
const components = uf.getComponents();
|
|
4933
|
+
const clusters = [];
|
|
4934
|
+
const evidenceByAddress = /* @__PURE__ */ new Map();
|
|
4935
|
+
for (const e of evidence) {
|
|
4936
|
+
for (const addr of e.addresses) {
|
|
4937
|
+
if (!evidenceByAddress.has(addr)) {
|
|
4938
|
+
evidenceByAddress.set(addr, []);
|
|
4939
|
+
}
|
|
4940
|
+
evidenceByAddress.get(addr).push(e);
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
const addressToAgents = /* @__PURE__ */ new Map();
|
|
4944
|
+
for (const tx of store.getTransactions()) {
|
|
4945
|
+
const from = tx.from.toLowerCase();
|
|
4946
|
+
if (!addressToAgents.has(from)) {
|
|
4947
|
+
addressToAgents.set(from, /* @__PURE__ */ new Set());
|
|
4948
|
+
}
|
|
4949
|
+
addressToAgents.get(from).add(tx.agentId);
|
|
4950
|
+
}
|
|
4951
|
+
if (registry) {
|
|
4952
|
+
for (const identity of registry.getAll()) {
|
|
4953
|
+
for (const wallet of identity.wallets) {
|
|
4954
|
+
if (!addressToAgents.has(wallet.address)) {
|
|
4955
|
+
addressToAgents.set(wallet.address, /* @__PURE__ */ new Set());
|
|
4956
|
+
}
|
|
4957
|
+
addressToAgents.get(wallet.address).add(identity.agentId);
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4961
|
+
for (const component of components) {
|
|
4962
|
+
if (component.length < 2) continue;
|
|
4963
|
+
const clusterEvidence = [];
|
|
4964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4965
|
+
for (const addr of component) {
|
|
4966
|
+
const addrEvidence = evidenceByAddress.get(addr) ?? [];
|
|
4967
|
+
for (const e of addrEvidence) {
|
|
4968
|
+
const key = `${e.heuristic}:${e.addresses[0]}:${e.addresses[1]}`;
|
|
4969
|
+
if (!seen.has(key)) {
|
|
4970
|
+
seen.add(key);
|
|
4971
|
+
clusterEvidence.push(e);
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
const agentIds = /* @__PURE__ */ new Set();
|
|
4976
|
+
for (const addr of component) {
|
|
4977
|
+
const agents = addressToAgents.get(addr);
|
|
4978
|
+
if (agents) {
|
|
4979
|
+
for (const a of agents) agentIds.add(a);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
const maxConfidence = clusterEvidence.length > 0 ? Math.max(...clusterEvidence.map((e) => e.confidence)) : 0;
|
|
4983
|
+
clusters.push({
|
|
4984
|
+
id: generateId(),
|
|
4985
|
+
addresses: component.sort(),
|
|
4986
|
+
agentIds: Array.from(agentIds).sort(),
|
|
4987
|
+
evidence: clusterEvidence,
|
|
4988
|
+
confidence: maxConfidence,
|
|
4989
|
+
createdAt: timestamp
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
return clusters;
|
|
4993
|
+
}
|
|
4994
|
+
};
|
|
4995
|
+
|
|
4996
|
+
// src/kya/behavioral-fingerprint.ts
|
|
4997
|
+
var MIN_SAMPLE_SIZE = 5;
|
|
4998
|
+
var BehavioralFingerprinter = class {
|
|
4999
|
+
/**
|
|
5000
|
+
* Compute a behavioral embedding for an agent from their transaction history.
|
|
5001
|
+
* Returns null if the agent has fewer than MIN_SAMPLE_SIZE transactions.
|
|
5002
|
+
*/
|
|
5003
|
+
computeEmbedding(agentId, store) {
|
|
5004
|
+
const transactions = store.getTransactionsByAgent(agentId);
|
|
5005
|
+
if (transactions.length < MIN_SAMPLE_SIZE) return null;
|
|
5006
|
+
return {
|
|
5007
|
+
agentId,
|
|
5008
|
+
temporal: this.extractTemporalFeatures(transactions),
|
|
5009
|
+
financial: this.extractFinancialFeatures(transactions),
|
|
5010
|
+
network: this.extractNetworkFeatures(transactions),
|
|
5011
|
+
operational: this.extractOperationalFeatures(transactions),
|
|
5012
|
+
sampleSize: transactions.length,
|
|
5013
|
+
computedAt: now()
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
/**
|
|
5017
|
+
* Compute cosine similarity between two behavioral embeddings.
|
|
5018
|
+
* Returns a value in [0, 1] where 1 means identical and 0 means orthogonal.
|
|
5019
|
+
*/
|
|
5020
|
+
cosineSimilarity(a, b) {
|
|
5021
|
+
const vecA = this.flattenEmbedding(a);
|
|
5022
|
+
const vecB = this.flattenEmbedding(b);
|
|
5023
|
+
let dot = 0;
|
|
5024
|
+
let magA = 0;
|
|
5025
|
+
let magB = 0;
|
|
5026
|
+
for (let i = 0; i < vecA.length; i++) {
|
|
5027
|
+
dot += vecA[i] * vecB[i];
|
|
5028
|
+
magA += vecA[i] * vecA[i];
|
|
5029
|
+
magB += vecB[i] * vecB[i];
|
|
5030
|
+
}
|
|
5031
|
+
const denominator = Math.sqrt(magA) * Math.sqrt(magB);
|
|
5032
|
+
if (denominator === 0) return 0;
|
|
5033
|
+
return dot / denominator;
|
|
5034
|
+
}
|
|
5035
|
+
// --------------------------------------------------------------------------
|
|
5036
|
+
// Feature Extraction
|
|
5037
|
+
// --------------------------------------------------------------------------
|
|
5038
|
+
extractTemporalFeatures(txs) {
|
|
5039
|
+
const sorted = [...txs].sort(
|
|
5040
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
5041
|
+
);
|
|
5042
|
+
const intervals = [];
|
|
5043
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
5044
|
+
const diff = (new Date(sorted[i].timestamp).getTime() - new Date(sorted[i - 1].timestamp).getTime()) / 1e3;
|
|
5045
|
+
intervals.push(diff);
|
|
5046
|
+
}
|
|
5047
|
+
const meanInterval = intervals.length > 0 ? intervals.reduce((s, v) => s + v, 0) / intervals.length : 0;
|
|
5048
|
+
const stddevInterval = intervals.length > 1 ? Math.sqrt(
|
|
5049
|
+
intervals.reduce((s, v) => s + (v - meanInterval) ** 2, 0) / (intervals.length - 1)
|
|
5050
|
+
) : 0;
|
|
5051
|
+
const hourCounts = new Array(24).fill(0);
|
|
5052
|
+
for (const tx of txs) {
|
|
5053
|
+
const hour = new Date(tx.timestamp).getUTCHours();
|
|
5054
|
+
hourCounts[hour] = (hourCounts[hour] ?? 0) + 1;
|
|
5055
|
+
}
|
|
5056
|
+
const hourTotal = hourCounts.reduce((s, v) => s + v, 0);
|
|
5057
|
+
const hourHistogram = hourTotal > 0 ? hourCounts.map((c) => c / hourTotal) : hourCounts;
|
|
5058
|
+
const dayCounts = new Array(7).fill(0);
|
|
5059
|
+
for (const tx of txs) {
|
|
5060
|
+
const day = new Date(tx.timestamp).getUTCDay();
|
|
5061
|
+
dayCounts[day] = (dayCounts[day] ?? 0) + 1;
|
|
5062
|
+
}
|
|
5063
|
+
const dayTotal = dayCounts.reduce((s, v) => s + v, 0);
|
|
5064
|
+
const dayHistogram = dayTotal > 0 ? dayCounts.map((c) => c / dayTotal) : dayCounts;
|
|
5065
|
+
return {
|
|
5066
|
+
meanIntervalSeconds: meanInterval,
|
|
5067
|
+
stddevIntervalSeconds: stddevInterval,
|
|
5068
|
+
hourHistogram,
|
|
5069
|
+
dayHistogram
|
|
5070
|
+
};
|
|
5071
|
+
}
|
|
5072
|
+
extractFinancialFeatures(txs) {
|
|
5073
|
+
const amounts = txs.map((tx) => parseFloat(tx.amount)).filter((a) => !isNaN(a));
|
|
5074
|
+
if (amounts.length === 0) {
|
|
5075
|
+
return {
|
|
5076
|
+
meanAmount: 0,
|
|
5077
|
+
stddevAmount: 0,
|
|
5078
|
+
medianAmount: 0,
|
|
5079
|
+
roundAmountRatio: 0,
|
|
5080
|
+
percentiles: [0, 0, 0, 0, 0]
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
5083
|
+
const sorted = [...amounts].sort((a, b) => a - b);
|
|
5084
|
+
const mean = amounts.reduce((s, v) => s + v, 0) / amounts.length;
|
|
5085
|
+
const stddev = amounts.length > 1 ? Math.sqrt(
|
|
5086
|
+
amounts.reduce((s, v) => s + (v - mean) ** 2, 0) / (amounts.length - 1)
|
|
5087
|
+
) : 0;
|
|
5088
|
+
const median = this.percentile(sorted, 50);
|
|
5089
|
+
const roundCount = amounts.filter((a) => a % 100 === 0).length;
|
|
5090
|
+
return {
|
|
5091
|
+
meanAmount: mean,
|
|
5092
|
+
stddevAmount: stddev,
|
|
5093
|
+
medianAmount: median,
|
|
5094
|
+
roundAmountRatio: amounts.length > 0 ? roundCount / amounts.length : 0,
|
|
5095
|
+
percentiles: [
|
|
5096
|
+
this.percentile(sorted, 10),
|
|
5097
|
+
this.percentile(sorted, 25),
|
|
5098
|
+
this.percentile(sorted, 50),
|
|
5099
|
+
this.percentile(sorted, 75),
|
|
5100
|
+
this.percentile(sorted, 90)
|
|
5101
|
+
]
|
|
5102
|
+
};
|
|
5103
|
+
}
|
|
5104
|
+
extractNetworkFeatures(txs) {
|
|
5105
|
+
const destinations = txs.map((tx) => tx.to.toLowerCase());
|
|
5106
|
+
const sources = txs.map((tx) => tx.from.toLowerCase());
|
|
5107
|
+
const uniqueDests = new Set(destinations);
|
|
5108
|
+
const uniqueSrcs = new Set(sources);
|
|
5109
|
+
const reuseRatio = destinations.length > 0 ? 1 - uniqueDests.size / destinations.length : 0;
|
|
5110
|
+
const destCounts = /* @__PURE__ */ new Map();
|
|
5111
|
+
for (const d of destinations) {
|
|
5112
|
+
destCounts.set(d, (destCounts.get(d) ?? 0) + 1);
|
|
5113
|
+
}
|
|
5114
|
+
let hhi = 0;
|
|
5115
|
+
for (const count of destCounts.values()) {
|
|
5116
|
+
const share = count / destinations.length;
|
|
5117
|
+
hhi += share * share;
|
|
5118
|
+
}
|
|
5119
|
+
return {
|
|
5120
|
+
uniqueDestinations: uniqueDests.size,
|
|
5121
|
+
reuseRatio,
|
|
5122
|
+
concentrationIndex: hhi,
|
|
5123
|
+
uniqueSources: uniqueSrcs.size
|
|
5124
|
+
};
|
|
5125
|
+
}
|
|
5126
|
+
extractOperationalFeatures(txs) {
|
|
5127
|
+
const chainCounts = /* @__PURE__ */ new Map();
|
|
5128
|
+
const tokenCounts = /* @__PURE__ */ new Map();
|
|
5129
|
+
for (const tx of txs) {
|
|
5130
|
+
const chain = tx.chain ?? "unknown";
|
|
5131
|
+
const token = tx.token ?? "unknown";
|
|
5132
|
+
chainCounts.set(chain, (chainCounts.get(chain) ?? 0) + 1);
|
|
5133
|
+
tokenCounts.set(token, (tokenCounts.get(token) ?? 0) + 1);
|
|
5134
|
+
}
|
|
5135
|
+
const total = txs.length;
|
|
5136
|
+
const chainDistribution = {};
|
|
5137
|
+
for (const [chain, count] of chainCounts) {
|
|
5138
|
+
chainDistribution[chain] = total > 0 ? count / total : 0;
|
|
5139
|
+
}
|
|
5140
|
+
const tokenDistribution = {};
|
|
5141
|
+
for (const [token, count] of tokenCounts) {
|
|
5142
|
+
tokenDistribution[token] = total > 0 ? count / total : 0;
|
|
5143
|
+
}
|
|
5144
|
+
const primaryChain = chainCounts.size > 0 ? Array.from(chainCounts.entries()).sort((a, b) => b[1] - a[1])[0][0] : "";
|
|
5145
|
+
const primaryToken = tokenCounts.size > 0 ? Array.from(tokenCounts.entries()).sort((a, b) => b[1] - a[1])[0][0] : "";
|
|
5146
|
+
return {
|
|
5147
|
+
chainDistribution,
|
|
5148
|
+
tokenDistribution,
|
|
5149
|
+
primaryChain,
|
|
5150
|
+
primaryToken
|
|
5151
|
+
};
|
|
5152
|
+
}
|
|
5153
|
+
// --------------------------------------------------------------------------
|
|
5154
|
+
// Helpers
|
|
5155
|
+
// --------------------------------------------------------------------------
|
|
5156
|
+
percentile(sorted, p) {
|
|
5157
|
+
if (sorted.length === 0) return 0;
|
|
5158
|
+
const idx = p / 100 * (sorted.length - 1);
|
|
5159
|
+
const lower = Math.floor(idx);
|
|
5160
|
+
const upper = Math.ceil(idx);
|
|
5161
|
+
if (lower === upper) return sorted[lower];
|
|
5162
|
+
return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
|
|
5163
|
+
}
|
|
5164
|
+
/**
|
|
5165
|
+
* Flatten an embedding into a numeric vector for cosine similarity.
|
|
5166
|
+
* Log-scales magnitude values with log(1+x).
|
|
5167
|
+
*/
|
|
5168
|
+
flattenEmbedding(e) {
|
|
5169
|
+
const vec = [];
|
|
5170
|
+
vec.push(Math.log(1 + e.temporal.meanIntervalSeconds));
|
|
5171
|
+
vec.push(Math.log(1 + e.temporal.stddevIntervalSeconds));
|
|
5172
|
+
vec.push(...e.temporal.hourHistogram);
|
|
5173
|
+
vec.push(...e.temporal.dayHistogram);
|
|
5174
|
+
vec.push(Math.log(1 + e.financial.meanAmount));
|
|
5175
|
+
vec.push(Math.log(1 + e.financial.stddevAmount));
|
|
5176
|
+
vec.push(Math.log(1 + e.financial.medianAmount));
|
|
5177
|
+
vec.push(e.financial.roundAmountRatio);
|
|
5178
|
+
vec.push(...e.financial.percentiles.map((p) => Math.log(1 + p)));
|
|
5179
|
+
vec.push(Math.log(1 + e.network.uniqueDestinations));
|
|
5180
|
+
vec.push(e.network.reuseRatio);
|
|
5181
|
+
vec.push(e.network.concentrationIndex);
|
|
5182
|
+
vec.push(Math.log(1 + e.network.uniqueSources));
|
|
5183
|
+
const chainValues = Object.values(e.operational.chainDistribution).sort(
|
|
5184
|
+
(a, b) => b - a
|
|
5185
|
+
);
|
|
5186
|
+
const tokenValues = Object.values(e.operational.tokenDistribution).sort(
|
|
5187
|
+
(a, b) => b - a
|
|
5188
|
+
);
|
|
5189
|
+
for (let i = 0; i < 5; i++) {
|
|
5190
|
+
vec.push(chainValues[i] ?? 0);
|
|
5191
|
+
}
|
|
5192
|
+
for (let i = 0; i < 5; i++) {
|
|
5193
|
+
vec.push(tokenValues[i] ?? 0);
|
|
5194
|
+
}
|
|
5195
|
+
return vec;
|
|
5196
|
+
}
|
|
5197
|
+
};
|
|
5198
|
+
|
|
5199
|
+
// src/kya/cross-session-linker.ts
|
|
5200
|
+
var WALLET_OVERLAP_WEIGHT = 0.4;
|
|
5201
|
+
var BEHAVIORAL_SIMILARITY_WEIGHT = 0.35;
|
|
5202
|
+
var DECLARED_IDENTITY_WEIGHT = 0.25;
|
|
5203
|
+
var CrossSessionLinker = class {
|
|
5204
|
+
config;
|
|
5205
|
+
links = /* @__PURE__ */ new Map();
|
|
5206
|
+
constructor(config = {}) {
|
|
5207
|
+
this.config = {
|
|
5208
|
+
minBehavioralSimilarity: config.minBehavioralSimilarity ?? 0.85,
|
|
5209
|
+
minLinkConfidence: config.minLinkConfidence ?? 0.6
|
|
5210
|
+
};
|
|
5211
|
+
}
|
|
5212
|
+
/**
|
|
5213
|
+
* Analyze all agents and create links between those with sufficient signals.
|
|
5214
|
+
*/
|
|
5215
|
+
analyzeAndLink(store, registry, clusterer, fingerprinter) {
|
|
5216
|
+
const newLinks = [];
|
|
5217
|
+
const agentIds = /* @__PURE__ */ new Set();
|
|
5218
|
+
for (const tx of store.getTransactions()) {
|
|
5219
|
+
agentIds.add(tx.agentId);
|
|
5220
|
+
}
|
|
5221
|
+
for (const action of store.getActions()) {
|
|
5222
|
+
agentIds.add(action.agentId);
|
|
5223
|
+
}
|
|
5224
|
+
const agents = Array.from(agentIds);
|
|
5225
|
+
const clusters = clusterer.getClusters();
|
|
5226
|
+
for (let i = 0; i < agents.length; i++) {
|
|
5227
|
+
for (let j = i + 1; j < agents.length; j++) {
|
|
5228
|
+
const agentA = agents[i];
|
|
5229
|
+
const agentB = agents[j];
|
|
5230
|
+
const existingKey = this.getLinkKey(agentA, agentB);
|
|
5231
|
+
if (this.links.has(existingKey)) continue;
|
|
5232
|
+
const signals = [];
|
|
5233
|
+
const walletOverlap = this.computeWalletOverlap(
|
|
5234
|
+
agentA,
|
|
5235
|
+
agentB,
|
|
5236
|
+
clusters
|
|
5237
|
+
);
|
|
5238
|
+
if (walletOverlap > 0) {
|
|
5239
|
+
signals.push({
|
|
5240
|
+
type: "wallet-overlap",
|
|
5241
|
+
strength: walletOverlap,
|
|
5242
|
+
weight: WALLET_OVERLAP_WEIGHT
|
|
5243
|
+
});
|
|
5244
|
+
}
|
|
5245
|
+
const embeddingA = fingerprinter.computeEmbedding(agentA, store);
|
|
5246
|
+
const embeddingB = fingerprinter.computeEmbedding(agentB, store);
|
|
5247
|
+
if (embeddingA && embeddingB) {
|
|
5248
|
+
const similarity = fingerprinter.cosineSimilarity(embeddingA, embeddingB);
|
|
5249
|
+
if (similarity >= this.config.minBehavioralSimilarity) {
|
|
5250
|
+
signals.push({
|
|
5251
|
+
type: "behavioral-similarity",
|
|
5252
|
+
strength: similarity,
|
|
5253
|
+
weight: BEHAVIORAL_SIMILARITY_WEIGHT
|
|
5254
|
+
});
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
const declaredStrength = this.computeDeclaredIdentitySignal(
|
|
5258
|
+
agentA,
|
|
5259
|
+
agentB,
|
|
5260
|
+
registry
|
|
5261
|
+
);
|
|
5262
|
+
if (declaredStrength > 0) {
|
|
5263
|
+
signals.push({
|
|
5264
|
+
type: "declared-identity",
|
|
5265
|
+
strength: declaredStrength,
|
|
5266
|
+
weight: DECLARED_IDENTITY_WEIGHT
|
|
5267
|
+
});
|
|
5268
|
+
}
|
|
5269
|
+
if (signals.length === 0) continue;
|
|
5270
|
+
const confidence = signals.reduce((sum, s) => sum + s.strength * s.weight, 0) / signals.reduce((sum, s) => sum + s.weight, 0);
|
|
5271
|
+
if (confidence >= this.config.minLinkConfidence) {
|
|
5272
|
+
const link = {
|
|
5273
|
+
id: generateId(),
|
|
5274
|
+
agentIdA: agentA,
|
|
5275
|
+
agentIdB: agentB,
|
|
5276
|
+
confidence,
|
|
5277
|
+
signals,
|
|
5278
|
+
status: "inferred",
|
|
5279
|
+
createdAt: now()
|
|
5280
|
+
};
|
|
5281
|
+
this.links.set(existingKey, link);
|
|
5282
|
+
newLinks.push(link);
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
return newLinks;
|
|
5287
|
+
}
|
|
5288
|
+
/**
|
|
5289
|
+
* Manually link two agents.
|
|
5290
|
+
*/
|
|
5291
|
+
manualLink(agentIdA, agentIdB, reviewedBy) {
|
|
5292
|
+
const key = this.getLinkKey(agentIdA, agentIdB);
|
|
5293
|
+
const existing = this.links.get(key);
|
|
5294
|
+
if (existing) {
|
|
5295
|
+
existing.status = "confirmed";
|
|
5296
|
+
existing.reviewedBy = reviewedBy;
|
|
5297
|
+
existing.reviewedAt = now();
|
|
5298
|
+
return { ...existing };
|
|
5299
|
+
}
|
|
5300
|
+
const link = {
|
|
5301
|
+
id: generateId(),
|
|
5302
|
+
agentIdA,
|
|
5303
|
+
agentIdB,
|
|
5304
|
+
confidence: 1,
|
|
5305
|
+
signals: [
|
|
5306
|
+
{
|
|
5307
|
+
type: "declared-identity",
|
|
5308
|
+
strength: 1,
|
|
5309
|
+
weight: 1,
|
|
5310
|
+
detail: `Manually linked by ${reviewedBy}`
|
|
5311
|
+
}
|
|
5312
|
+
],
|
|
5313
|
+
status: "confirmed",
|
|
5314
|
+
createdAt: now(),
|
|
5315
|
+
reviewedBy,
|
|
5316
|
+
reviewedAt: now()
|
|
5317
|
+
};
|
|
5318
|
+
this.links.set(key, link);
|
|
5319
|
+
return { ...link };
|
|
5320
|
+
}
|
|
5321
|
+
/**
|
|
5322
|
+
* Review a link (confirm or reject).
|
|
5323
|
+
*/
|
|
5324
|
+
reviewLink(linkId, decision, reviewedBy) {
|
|
5325
|
+
for (const link of this.links.values()) {
|
|
5326
|
+
if (link.id === linkId) {
|
|
5327
|
+
link.status = decision;
|
|
5328
|
+
link.reviewedBy = reviewedBy;
|
|
5329
|
+
link.reviewedAt = now();
|
|
5330
|
+
return { ...link };
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
return void 0;
|
|
5334
|
+
}
|
|
5335
|
+
/**
|
|
5336
|
+
* Get all agents linked to the given agent.
|
|
5337
|
+
*/
|
|
5338
|
+
getLinkedAgents(agentId) {
|
|
5339
|
+
const linked = /* @__PURE__ */ new Set();
|
|
5340
|
+
for (const link of this.links.values()) {
|
|
5341
|
+
if (link.status === "rejected") continue;
|
|
5342
|
+
if (link.agentIdA === agentId) linked.add(link.agentIdB);
|
|
5343
|
+
if (link.agentIdB === agentId) linked.add(link.agentIdA);
|
|
5344
|
+
}
|
|
5345
|
+
return Array.from(linked);
|
|
5346
|
+
}
|
|
5347
|
+
/**
|
|
5348
|
+
* Get all links for a specific agent.
|
|
5349
|
+
*/
|
|
5350
|
+
getLinksForAgent(agentId) {
|
|
5351
|
+
const result = [];
|
|
5352
|
+
for (const link of this.links.values()) {
|
|
5353
|
+
if (link.agentIdA === agentId || link.agentIdB === agentId) {
|
|
5354
|
+
result.push({ ...link });
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
return result;
|
|
5358
|
+
}
|
|
5359
|
+
/**
|
|
5360
|
+
* Get all links.
|
|
5361
|
+
*/
|
|
5362
|
+
getAllLinks() {
|
|
5363
|
+
return Array.from(this.links.values()).map((l) => ({ ...l }));
|
|
5364
|
+
}
|
|
5365
|
+
// --------------------------------------------------------------------------
|
|
5366
|
+
// Private Helpers
|
|
5367
|
+
// --------------------------------------------------------------------------
|
|
5368
|
+
getLinkKey(a, b) {
|
|
5369
|
+
return a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
5370
|
+
}
|
|
5371
|
+
computeWalletOverlap(agentA, agentB, clusters) {
|
|
5372
|
+
const clustersA = /* @__PURE__ */ new Set();
|
|
5373
|
+
const clustersB = /* @__PURE__ */ new Set();
|
|
5374
|
+
for (const cluster of clusters) {
|
|
5375
|
+
if (cluster.agentIds.includes(agentA)) {
|
|
5376
|
+
for (const addr of cluster.addresses) clustersA.add(addr);
|
|
5377
|
+
}
|
|
5378
|
+
if (cluster.agentIds.includes(agentB)) {
|
|
5379
|
+
for (const addr of cluster.addresses) clustersB.add(addr);
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
if (clustersA.size === 0 || clustersB.size === 0) return 0;
|
|
5383
|
+
const intersection = new Set([...clustersA].filter((a) => clustersB.has(a)));
|
|
5384
|
+
const union = /* @__PURE__ */ new Set([...clustersA, ...clustersB]);
|
|
5385
|
+
return union.size > 0 ? intersection.size / union.size : 0;
|
|
5386
|
+
}
|
|
5387
|
+
computeDeclaredIdentitySignal(agentA, agentB, registry) {
|
|
5388
|
+
const identityA = registry.get(agentA);
|
|
5389
|
+
const identityB = registry.get(agentB);
|
|
5390
|
+
if (!identityA || !identityB) return 0;
|
|
5391
|
+
const walletsA = new Set(identityA.wallets.map((w) => w.address));
|
|
5392
|
+
const walletsB = new Set(identityB.wallets.map((w) => w.address));
|
|
5393
|
+
const sharedWallets = [...walletsA].filter((w) => walletsB.has(w));
|
|
5394
|
+
if (sharedWallets.length > 0) return 1;
|
|
5395
|
+
const kycRefsA = new Set(
|
|
5396
|
+
identityA.kycReferences.map((r) => `${r.provider}:${r.referenceId}`)
|
|
5397
|
+
);
|
|
5398
|
+
const kycRefsB = new Set(
|
|
5399
|
+
identityB.kycReferences.map((r) => `${r.provider}:${r.referenceId}`)
|
|
5400
|
+
);
|
|
5401
|
+
const sharedKyc = [...kycRefsA].filter((r) => kycRefsB.has(r));
|
|
5402
|
+
if (sharedKyc.length > 0) return 1;
|
|
5403
|
+
return 0;
|
|
5404
|
+
}
|
|
5405
|
+
};
|
|
5406
|
+
|
|
5407
|
+
// src/kya/confidence-scorer.ts
|
|
5408
|
+
var KYAConfidenceScorer = class {
|
|
5409
|
+
weights;
|
|
5410
|
+
constructor(config = {}) {
|
|
5411
|
+
this.weights = {
|
|
5412
|
+
declaredIdentity: config.declaredIdentityWeight ?? 0.2,
|
|
5413
|
+
kycVerification: config.kycVerificationWeight ?? 0.3,
|
|
5414
|
+
walletGraph: config.walletGraphWeight ?? 0.2,
|
|
5415
|
+
behavioralConsistency: config.behavioralConsistencyWeight ?? 0.2,
|
|
5416
|
+
externalEnrichment: config.externalEnrichmentWeight ?? 0.1
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Compute a composite confidence score for an agent.
|
|
5421
|
+
*/
|
|
5422
|
+
computeScore(agentId, registry, clusterer, fingerprinter, linker, store) {
|
|
5423
|
+
const components = [];
|
|
5424
|
+
const declaredScore = this.scoreDeclaredIdentity(agentId, registry);
|
|
5425
|
+
components.push({
|
|
5426
|
+
name: "Declared Identity",
|
|
5427
|
+
score: declaredScore.score,
|
|
5428
|
+
weight: this.weights.declaredIdentity,
|
|
5429
|
+
weightedScore: declaredScore.score * this.weights.declaredIdentity,
|
|
5430
|
+
detail: declaredScore.detail
|
|
5431
|
+
});
|
|
5432
|
+
const kycScore = this.scoreKycVerification(agentId, registry);
|
|
5433
|
+
components.push({
|
|
5434
|
+
name: "KYC Verification",
|
|
5435
|
+
score: kycScore.score,
|
|
5436
|
+
weight: this.weights.kycVerification,
|
|
5437
|
+
weightedScore: kycScore.score * this.weights.kycVerification,
|
|
5438
|
+
detail: kycScore.detail
|
|
5439
|
+
});
|
|
5440
|
+
const walletScore = this.scoreWalletGraph(agentId, clusterer);
|
|
5441
|
+
components.push({
|
|
5442
|
+
name: "Wallet Graph",
|
|
5443
|
+
score: walletScore.score,
|
|
5444
|
+
weight: this.weights.walletGraph,
|
|
5445
|
+
weightedScore: walletScore.score * this.weights.walletGraph,
|
|
5446
|
+
detail: walletScore.detail
|
|
5447
|
+
});
|
|
5448
|
+
const behavioralScore = this.scoreBehavioralConsistency(
|
|
5449
|
+
agentId,
|
|
5450
|
+
fingerprinter,
|
|
5451
|
+
linker,
|
|
5452
|
+
store
|
|
5453
|
+
);
|
|
5454
|
+
components.push({
|
|
5455
|
+
name: "Behavioral Consistency",
|
|
5456
|
+
score: behavioralScore.score,
|
|
5457
|
+
weight: this.weights.behavioralConsistency,
|
|
5458
|
+
weightedScore: behavioralScore.score * this.weights.behavioralConsistency,
|
|
5459
|
+
detail: behavioralScore.detail
|
|
5460
|
+
});
|
|
5461
|
+
const externalScore = this.scoreExternalEnrichment(agentId, registry);
|
|
5462
|
+
components.push({
|
|
5463
|
+
name: "External Enrichment",
|
|
5464
|
+
score: externalScore.score,
|
|
5465
|
+
weight: this.weights.externalEnrichment,
|
|
5466
|
+
weightedScore: externalScore.score * this.weights.externalEnrichment,
|
|
5467
|
+
detail: externalScore.detail
|
|
5468
|
+
});
|
|
5469
|
+
const totalWeight = components.reduce((sum, c) => sum + c.weight, 0);
|
|
5470
|
+
const overallScore = totalWeight > 0 ? Math.round(components.reduce((sum, c) => sum + c.weightedScore, 0) / totalWeight) : 0;
|
|
5471
|
+
return {
|
|
5472
|
+
agentId,
|
|
5473
|
+
score: overallScore,
|
|
5474
|
+
level: this.scoreToLevel(overallScore),
|
|
5475
|
+
components,
|
|
5476
|
+
computedAt: now()
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
// --------------------------------------------------------------------------
|
|
5480
|
+
// Component Scorers
|
|
5481
|
+
// --------------------------------------------------------------------------
|
|
5482
|
+
scoreDeclaredIdentity(agentId, registry) {
|
|
5483
|
+
const identity = registry.get(agentId);
|
|
5484
|
+
if (!identity) return { score: 0, detail: "No declared identity" };
|
|
5485
|
+
let score = 30;
|
|
5486
|
+
const parts = ["identity registered"];
|
|
5487
|
+
if (identity.displayName) {
|
|
5488
|
+
score += 10;
|
|
5489
|
+
parts.push("displayName set");
|
|
5490
|
+
}
|
|
5491
|
+
if (identity.entityType !== "unknown") {
|
|
5492
|
+
score += 10;
|
|
5493
|
+
parts.push(`entityType: ${identity.entityType}`);
|
|
5494
|
+
}
|
|
5495
|
+
if (identity.wallets.length > 0) {
|
|
5496
|
+
score += 20;
|
|
5497
|
+
parts.push(`${identity.wallets.length} wallet(s)`);
|
|
5498
|
+
}
|
|
5499
|
+
if (identity.contactUri) {
|
|
5500
|
+
score += 10;
|
|
5501
|
+
parts.push("contactUri set");
|
|
5502
|
+
}
|
|
5503
|
+
if (Object.keys(identity.metadata).length > 0) {
|
|
5504
|
+
score += 20;
|
|
5505
|
+
parts.push("metadata provided");
|
|
5506
|
+
}
|
|
5507
|
+
return { score: Math.min(100, score), detail: parts.join(", ") };
|
|
5508
|
+
}
|
|
5509
|
+
scoreKycVerification(agentId, registry) {
|
|
5510
|
+
const identity = registry.get(agentId);
|
|
5511
|
+
if (!identity || identity.kycReferences.length === 0) {
|
|
5512
|
+
return { score: 0, detail: "No KYC verification" };
|
|
5513
|
+
}
|
|
5514
|
+
const refs = identity.kycReferences;
|
|
5515
|
+
const hasRejected = refs.some((r) => r.status === "rejected");
|
|
5516
|
+
const verifiedRefs = refs.filter((r) => r.status === "verified");
|
|
5517
|
+
const pendingRefs = refs.filter((r) => r.status === "pending");
|
|
5518
|
+
const currentTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
5519
|
+
const activeVerified = verifiedRefs.filter(
|
|
5520
|
+
(r) => r.expiresAt === null || r.expiresAt > currentTime
|
|
5521
|
+
);
|
|
5522
|
+
if (hasRejected && verifiedRefs.length === 0) {
|
|
5523
|
+
return { score: 10, detail: "KYC rejected, capped at 10" };
|
|
5524
|
+
}
|
|
5525
|
+
if (pendingRefs.length > 0 && verifiedRefs.length === 0) {
|
|
5526
|
+
return { score: 20, detail: "KYC pending" };
|
|
5527
|
+
}
|
|
5528
|
+
if (verifiedRefs.length > 0) {
|
|
5529
|
+
let score = 90;
|
|
5530
|
+
const parts = [`${verifiedRefs.length} verified`];
|
|
5531
|
+
if (activeVerified.length > 0) {
|
|
5532
|
+
score = 100;
|
|
5533
|
+
parts.push("non-expired");
|
|
5534
|
+
}
|
|
5535
|
+
if (verifiedRefs.length > 1) {
|
|
5536
|
+
score = Math.min(100, score + 10);
|
|
5537
|
+
parts.push("multiple providers");
|
|
5538
|
+
}
|
|
5539
|
+
return { score, detail: parts.join(", ") };
|
|
5540
|
+
}
|
|
5541
|
+
return { score: 0, detail: "No KYC verification" };
|
|
5542
|
+
}
|
|
5543
|
+
scoreWalletGraph(agentId, clusterer) {
|
|
5544
|
+
const clusters = clusterer.getClusters();
|
|
5545
|
+
const agentClusters = clusters.filter((c) => c.agentIds.includes(agentId));
|
|
5546
|
+
if (agentClusters.length === 0) {
|
|
5547
|
+
return { score: 20, detail: "No wallet cluster" };
|
|
5548
|
+
}
|
|
5549
|
+
const totalAddresses = new Set(
|
|
5550
|
+
agentClusters.flatMap((c) => c.addresses)
|
|
5551
|
+
).size;
|
|
5552
|
+
if (totalAddresses === 1) {
|
|
5553
|
+
return { score: 40, detail: "1 address in cluster" };
|
|
5554
|
+
}
|
|
5555
|
+
const heuristics = new Set(
|
|
5556
|
+
agentClusters.flatMap((c) => c.evidence.map((e) => e.heuristic))
|
|
5557
|
+
);
|
|
5558
|
+
const hasDeclared = heuristics.has("declared-wallets");
|
|
5559
|
+
const hasAuto = heuristics.size > (hasDeclared ? 1 : 0);
|
|
5560
|
+
if (hasDeclared && hasAuto) {
|
|
5561
|
+
return {
|
|
5562
|
+
score: 85,
|
|
5563
|
+
detail: `${totalAddresses} addresses, declared + auto heuristics`
|
|
5564
|
+
};
|
|
5565
|
+
}
|
|
5566
|
+
if (heuristics.size > 1) {
|
|
5567
|
+
return {
|
|
5568
|
+
score: 70,
|
|
5569
|
+
detail: `${totalAddresses} addresses, ${heuristics.size} heuristic types`
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
return {
|
|
5573
|
+
score: 50,
|
|
5574
|
+
detail: `${totalAddresses} addresses, single heuristic`
|
|
5575
|
+
};
|
|
5576
|
+
}
|
|
5577
|
+
scoreBehavioralConsistency(agentId, fingerprinter, linker, store) {
|
|
5578
|
+
const embedding = fingerprinter.computeEmbedding(agentId, store);
|
|
5579
|
+
if (!embedding) {
|
|
5580
|
+
return { score: 30, detail: "Insufficient data for embedding" };
|
|
5581
|
+
}
|
|
5582
|
+
const links = linker.getLinksForAgent(agentId);
|
|
5583
|
+
const confirmedLinks = links.filter((l) => l.status === "confirmed");
|
|
5584
|
+
if (confirmedLinks.length === 0 && links.length === 0) {
|
|
5585
|
+
return { score: 50, detail: "Embedding computed, no links" };
|
|
5586
|
+
}
|
|
5587
|
+
let maxSimilarity = 0;
|
|
5588
|
+
for (const link of confirmedLinks) {
|
|
5589
|
+
const behavioralSignal = link.signals.find(
|
|
5590
|
+
(s) => s.type === "behavioral-similarity"
|
|
5591
|
+
);
|
|
5592
|
+
if (behavioralSignal && behavioralSignal.strength > maxSimilarity) {
|
|
5593
|
+
maxSimilarity = behavioralSignal.strength;
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
if (maxSimilarity > 0.9) {
|
|
5597
|
+
return { score: 95, detail: `Confirmed link similarity: ${maxSimilarity.toFixed(2)}` };
|
|
5598
|
+
}
|
|
5599
|
+
if (maxSimilarity > 0.8) {
|
|
5600
|
+
return { score: 85, detail: `Confirmed link similarity: ${maxSimilarity.toFixed(2)}` };
|
|
5601
|
+
}
|
|
5602
|
+
if (confirmedLinks.length > 0) {
|
|
5603
|
+
return { score: 70, detail: `${confirmedLinks.length} confirmed link(s)` };
|
|
5604
|
+
}
|
|
5605
|
+
return { score: 50, detail: "Embedding computed, no confirmed links" };
|
|
5606
|
+
}
|
|
5607
|
+
scoreExternalEnrichment(agentId, registry) {
|
|
5608
|
+
const identity = registry.get(agentId);
|
|
5609
|
+
if (!identity) {
|
|
5610
|
+
return { score: 50, detail: "Default (neutral), no identity" };
|
|
5611
|
+
}
|
|
5612
|
+
const riskLevels = identity.kycReferences.filter((r) => r.riskLevel).map((r) => r.riskLevel);
|
|
5613
|
+
if (riskLevels.length === 0) {
|
|
5614
|
+
return { score: 50, detail: "Default (neutral), no risk data" };
|
|
5615
|
+
}
|
|
5616
|
+
if (riskLevels.includes("high")) {
|
|
5617
|
+
return { score: 20, detail: "High risk from external provider" };
|
|
5618
|
+
}
|
|
5619
|
+
if (riskLevels.includes("low")) {
|
|
5620
|
+
return { score: 80, detail: "Low risk from external provider" };
|
|
5621
|
+
}
|
|
5622
|
+
return { score: 50, detail: "Medium risk from external provider" };
|
|
5623
|
+
}
|
|
5624
|
+
// --------------------------------------------------------------------------
|
|
5625
|
+
// Helpers
|
|
5626
|
+
// --------------------------------------------------------------------------
|
|
5627
|
+
scoreToLevel(score) {
|
|
5628
|
+
if (score >= 85) return "verified";
|
|
5629
|
+
if (score >= 65) return "high";
|
|
5630
|
+
if (score >= 40) return "medium";
|
|
5631
|
+
if (score >= 20) return "low";
|
|
5632
|
+
return "unknown";
|
|
5633
|
+
}
|
|
5634
|
+
};
|
|
5635
|
+
|
|
5636
|
+
// src/client.ts
|
|
5637
|
+
var PLAN_STORAGE_KEY = "kontext:plan";
|
|
5638
|
+
var Kontext = class _Kontext {
|
|
5639
|
+
config;
|
|
5640
|
+
store;
|
|
5641
|
+
logger;
|
|
5642
|
+
taskManager;
|
|
5643
|
+
auditExporter;
|
|
5644
|
+
mode;
|
|
5645
|
+
planManager;
|
|
5646
|
+
exporter;
|
|
5647
|
+
featureFlagManager;
|
|
5648
|
+
trustScorer;
|
|
5649
|
+
anomalyDetector;
|
|
5650
|
+
screeningAggregator;
|
|
5651
|
+
walletMonitor = null;
|
|
5652
|
+
provenanceManager = null;
|
|
5653
|
+
identityRegistry = null;
|
|
5654
|
+
walletClusterer = null;
|
|
5655
|
+
behavioralFingerprinter = null;
|
|
5656
|
+
crossSessionLinker = null;
|
|
5657
|
+
confidenceScorer = null;
|
|
5658
|
+
constructor(config) {
|
|
5659
|
+
this.config = config;
|
|
5660
|
+
this.mode = config.apiKey ? "cloud" : "local";
|
|
5661
|
+
this.store = new KontextStore();
|
|
5662
|
+
if (config.metadataSchema && typeof config.metadataSchema.parse !== "function") {
|
|
5663
|
+
throw new KontextError(
|
|
5664
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5665
|
+
"metadataSchema must have a parse() method"
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
if (config.storage) {
|
|
5669
|
+
this.store.setStorageAdapter(config.storage);
|
|
5670
|
+
}
|
|
5671
|
+
const planTier = config.plan ?? "free";
|
|
5672
|
+
this.planManager = new PlanManager(planTier, void 0, config.seats ?? 1);
|
|
5673
|
+
if (config.upgradeUrl) {
|
|
5674
|
+
this.planManager.upgradeUrl = config.upgradeUrl;
|
|
5675
|
+
}
|
|
5676
|
+
this.exporter = config.exporter ?? new NoopExporter();
|
|
5677
|
+
this.logger = new ActionLogger(config, this.store);
|
|
5678
|
+
this.taskManager = new TaskManager(config, this.store);
|
|
5679
|
+
this.auditExporter = new AuditExporter(config, this.store);
|
|
5680
|
+
this.trustScorer = new TrustScorer(config, this.store);
|
|
5681
|
+
this.anomalyDetector = new AnomalyDetector(config, this.store);
|
|
5682
|
+
this.featureFlagManager = config.featureFlags ? new FeatureFlagManager(config.featureFlags) : null;
|
|
5683
|
+
this.screeningAggregator = config.screening ? new ScreeningAggregator({
|
|
5684
|
+
providers: config.screening.providers,
|
|
5685
|
+
consensus: config.screening.consensus,
|
|
5686
|
+
blocklist: config.screening.blocklist,
|
|
5687
|
+
allowlist: config.screening.allowlist,
|
|
5688
|
+
providerTimeoutMs: config.screening.providerTimeoutMs,
|
|
5689
|
+
onEvent: () => this.planManager.recordEvent()
|
|
5690
|
+
}) : null;
|
|
5691
|
+
if (config.anomalyRules && config.anomalyRules.length > 0) {
|
|
5692
|
+
const advancedRules = ["newDestination", "offHoursActivity", "rapidSuccession", "roundAmount"];
|
|
5693
|
+
const hasAdvanced = config.anomalyRules.some((r) => advancedRules.includes(r));
|
|
5694
|
+
if (hasAdvanced) {
|
|
5695
|
+
requirePlan("advanced-anomaly-rules", planTier);
|
|
5696
|
+
}
|
|
5697
|
+
this.anomalyDetector.enableAnomalyDetection({
|
|
5698
|
+
rules: config.anomalyRules,
|
|
5699
|
+
thresholds: config.anomalyThresholds
|
|
5700
|
+
});
|
|
5701
|
+
}
|
|
5702
|
+
if (config.walletMonitoring && config.walletMonitoring.wallets.length > 0) {
|
|
5703
|
+
const tokens = config.policy?.allowedTokens;
|
|
5704
|
+
this.walletMonitor = new WalletMonitor(
|
|
5705
|
+
this,
|
|
5706
|
+
config.walletMonitoring,
|
|
5707
|
+
{ agentId: config.agentId, tokens: tokens ?? void 0 }
|
|
5708
|
+
);
|
|
5709
|
+
this.walletMonitor.start().catch((err) => {
|
|
5710
|
+
if (config.debug) {
|
|
5711
|
+
console.debug(`[Kontext] Wallet monitor failed to start: ${err}`);
|
|
5712
|
+
}
|
|
5713
|
+
});
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
/**
|
|
5717
|
+
* Initialize the Kontext SDK.
|
|
5718
|
+
*
|
|
3290
5719
|
* @param config - Configuration options
|
|
3291
5720
|
* @returns Initialized Kontext client instance
|
|
3292
5721
|
*
|
|
@@ -3314,6 +5743,29 @@ var Kontext = class _Kontext {
|
|
|
3314
5743
|
* ```
|
|
3315
5744
|
*/
|
|
3316
5745
|
static init(config) {
|
|
5746
|
+
if (!config) {
|
|
5747
|
+
const fileConfig = loadConfigFile();
|
|
5748
|
+
if (!fileConfig) {
|
|
5749
|
+
throw new KontextError(
|
|
5750
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5751
|
+
"No config provided and no kontext.config.json found. Run `npx kontext init` to create one, or pass config to Kontext.init()."
|
|
5752
|
+
);
|
|
5753
|
+
}
|
|
5754
|
+
const mapped = {
|
|
5755
|
+
projectId: fileConfig.projectId,
|
|
5756
|
+
environment: fileConfig.environment ?? "production",
|
|
5757
|
+
apiKey: fileConfig.apiKey,
|
|
5758
|
+
agentId: fileConfig.agentId,
|
|
5759
|
+
interceptorMode: fileConfig.mode,
|
|
5760
|
+
policy: {
|
|
5761
|
+
allowedTokens: fileConfig.tokens,
|
|
5762
|
+
corridors: fileConfig.corridors?.from ? { blocked: fileConfig.corridors.to ? [{ from: fileConfig.corridors.from, to: fileConfig.corridors.to }] : void 0 } : void 0,
|
|
5763
|
+
thresholds: fileConfig.thresholds ? { edd: fileConfig.thresholds.alertAmount ? Number(fileConfig.thresholds.alertAmount) : void 0 } : void 0
|
|
5764
|
+
},
|
|
5765
|
+
walletMonitoring: fileConfig.wallets && fileConfig.wallets.length > 0 && fileConfig.rpcEndpoints ? { wallets: fileConfig.wallets, rpcEndpoints: fileConfig.rpcEndpoints } : void 0
|
|
5766
|
+
};
|
|
5767
|
+
return _Kontext.init(mapped);
|
|
5768
|
+
}
|
|
3317
5769
|
if (!config.projectId || config.projectId.trim() === "") {
|
|
3318
5770
|
throw new KontextError(
|
|
3319
5771
|
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
@@ -3371,6 +5823,147 @@ var Kontext = class _Kontext {
|
|
|
3371
5823
|
);
|
|
3372
5824
|
}
|
|
3373
5825
|
}
|
|
5826
|
+
/**
|
|
5827
|
+
* Map aggregated screening results into UsdcComplianceCheck format.
|
|
5828
|
+
* Produces the same check/riskLevel/recommendations shape as
|
|
5829
|
+
* UsdcCompliance.checkTransaction() and PaymentCompliance.checkPayment().
|
|
5830
|
+
*/
|
|
5831
|
+
buildComplianceFromScreening(input, fromResult, toResult) {
|
|
5832
|
+
const checks = [];
|
|
5833
|
+
const fromHit = fromResult.hit;
|
|
5834
|
+
const fromProviders = fromResult.providerResults.filter((r) => r.hit).map((r) => r.providerId).join(", ");
|
|
5835
|
+
checks.push({
|
|
5836
|
+
name: "sanctions_sender",
|
|
5837
|
+
passed: !fromHit,
|
|
5838
|
+
description: fromHit ? `Sender flagged by: ${fromProviders}` : `Sender cleared (${fromResult.totalProviders} provider${fromResult.totalProviders !== 1 ? "s" : ""} checked)`,
|
|
5839
|
+
severity: fromHit ? "critical" : "low"
|
|
5840
|
+
});
|
|
5841
|
+
const toHit = toResult.hit;
|
|
5842
|
+
const toProviders = toResult.providerResults.filter((r) => r.hit).map((r) => r.providerId).join(", ");
|
|
5843
|
+
checks.push({
|
|
5844
|
+
name: "sanctions_recipient",
|
|
5845
|
+
passed: !toHit,
|
|
5846
|
+
description: toHit ? `Recipient flagged by: ${toProviders}` : `Recipient cleared (${toResult.totalProviders} provider${toResult.totalProviders !== 1 ? "s" : ""} checked)`,
|
|
5847
|
+
severity: toHit ? "critical" : "low"
|
|
5848
|
+
});
|
|
5849
|
+
const allUncovered = [
|
|
5850
|
+
.../* @__PURE__ */ new Set([...fromResult.uncoveredLists, ...toResult.uncoveredLists])
|
|
5851
|
+
];
|
|
5852
|
+
if (allUncovered.length > 0) {
|
|
5853
|
+
checks.push({
|
|
5854
|
+
name: "jurisdiction_coverage",
|
|
5855
|
+
passed: false,
|
|
5856
|
+
description: `Required lists not covered: ${allUncovered.join(", ")}`,
|
|
5857
|
+
severity: "medium"
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
const thresholds = this.config.policy?.thresholds;
|
|
5861
|
+
const eddThreshold = thresholds?.edd ?? 3e3;
|
|
5862
|
+
const reportingThreshold = thresholds?.reporting ?? 1e4;
|
|
5863
|
+
const largeThreshold = thresholds?.largeTransaction ?? 5e4;
|
|
5864
|
+
const amount = parseAmount(input.amount);
|
|
5865
|
+
if (amount >= eddThreshold) {
|
|
5866
|
+
checks.push({
|
|
5867
|
+
name: "enhanced_due_diligence",
|
|
5868
|
+
passed: false,
|
|
5869
|
+
description: `Amount $${amount.toLocaleString()} meets EDD threshold ($${eddThreshold.toLocaleString()})`,
|
|
5870
|
+
severity: "low"
|
|
5871
|
+
});
|
|
5872
|
+
}
|
|
5873
|
+
if (amount >= reportingThreshold) {
|
|
5874
|
+
checks.push({
|
|
5875
|
+
name: "reporting_threshold",
|
|
5876
|
+
passed: false,
|
|
5877
|
+
description: `Amount $${amount.toLocaleString()} meets CTR threshold ($${reportingThreshold.toLocaleString()})`,
|
|
5878
|
+
severity: "low"
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5881
|
+
if (amount >= largeThreshold) {
|
|
5882
|
+
checks.push({
|
|
5883
|
+
name: "large_transaction",
|
|
5884
|
+
passed: false,
|
|
5885
|
+
description: `Large transaction: $${amount.toLocaleString()} exceeds $${largeThreshold.toLocaleString()}`,
|
|
5886
|
+
severity: "medium"
|
|
5887
|
+
});
|
|
5888
|
+
}
|
|
5889
|
+
const allErrors = [...fromResult.errors, ...toResult.errors];
|
|
5890
|
+
if (allErrors.length > 0) {
|
|
5891
|
+
checks.push({
|
|
5892
|
+
name: "screening_errors",
|
|
5893
|
+
passed: false,
|
|
5894
|
+
description: `Provider errors: ${allErrors.map((e) => `${e.providerId}: ${e.error}`).join("; ")}`,
|
|
5895
|
+
severity: "medium"
|
|
5896
|
+
});
|
|
5897
|
+
}
|
|
5898
|
+
const failedChecks = checks.filter((c) => !c.passed);
|
|
5899
|
+
const compliant = failedChecks.every((c) => c.severity === "low");
|
|
5900
|
+
const severityOrder = ["low", "medium", "high", "critical"];
|
|
5901
|
+
const highestSeverity = failedChecks.reduce(
|
|
5902
|
+
(max, c) => severityOrder.indexOf(c.severity) > severityOrder.indexOf(max) ? c.severity : max,
|
|
5903
|
+
"low"
|
|
5904
|
+
);
|
|
5905
|
+
const recommendations = [];
|
|
5906
|
+
if (fromHit || toHit) {
|
|
5907
|
+
recommendations.push("Block transaction: sanctions match detected");
|
|
5908
|
+
}
|
|
5909
|
+
if (allUncovered.length > 0) {
|
|
5910
|
+
recommendations.push(`Add providers covering: ${allUncovered.join(", ")}`);
|
|
5911
|
+
}
|
|
5912
|
+
if (amount >= eddThreshold && amount < reportingThreshold) {
|
|
5913
|
+
recommendations.push("Collect enhanced due diligence information");
|
|
5914
|
+
}
|
|
5915
|
+
if (amount >= reportingThreshold) {
|
|
5916
|
+
recommendations.push("File Currency Transaction Report (CTR)");
|
|
5917
|
+
}
|
|
5918
|
+
return {
|
|
5919
|
+
compliant,
|
|
5920
|
+
checks,
|
|
5921
|
+
riskLevel: highestSeverity,
|
|
5922
|
+
recommendations
|
|
5923
|
+
};
|
|
5924
|
+
}
|
|
5925
|
+
/** Lazy-init ProvenanceManager on first use. */
|
|
5926
|
+
getProvenanceManager() {
|
|
5927
|
+
if (!this.provenanceManager) {
|
|
5928
|
+
this.provenanceManager = new ProvenanceManager(this.store, this.logger);
|
|
5929
|
+
}
|
|
5930
|
+
return this.provenanceManager;
|
|
5931
|
+
}
|
|
5932
|
+
/** Lazy-init AgentIdentityRegistry on first use. */
|
|
5933
|
+
getIdentityRegistry() {
|
|
5934
|
+
if (!this.identityRegistry) {
|
|
5935
|
+
this.identityRegistry = new AgentIdentityRegistry();
|
|
5936
|
+
}
|
|
5937
|
+
return this.identityRegistry;
|
|
5938
|
+
}
|
|
5939
|
+
/** Lazy-init WalletClusterer on first use. */
|
|
5940
|
+
getWalletClusterer() {
|
|
5941
|
+
if (!this.walletClusterer) {
|
|
5942
|
+
this.walletClusterer = new WalletClusterer();
|
|
5943
|
+
}
|
|
5944
|
+
return this.walletClusterer;
|
|
5945
|
+
}
|
|
5946
|
+
/** Lazy-init BehavioralFingerprinter on first use. */
|
|
5947
|
+
getBehavioralFingerprinter() {
|
|
5948
|
+
if (!this.behavioralFingerprinter) {
|
|
5949
|
+
this.behavioralFingerprinter = new BehavioralFingerprinter();
|
|
5950
|
+
}
|
|
5951
|
+
return this.behavioralFingerprinter;
|
|
5952
|
+
}
|
|
5953
|
+
/** Lazy-init CrossSessionLinker on first use. */
|
|
5954
|
+
getCrossSessionLinker() {
|
|
5955
|
+
if (!this.crossSessionLinker) {
|
|
5956
|
+
this.crossSessionLinker = new CrossSessionLinker();
|
|
5957
|
+
}
|
|
5958
|
+
return this.crossSessionLinker;
|
|
5959
|
+
}
|
|
5960
|
+
/** Lazy-init KYAConfidenceScorer on first use. */
|
|
5961
|
+
getConfidenceScorer() {
|
|
5962
|
+
if (!this.confidenceScorer) {
|
|
5963
|
+
this.confidenceScorer = new KYAConfidenceScorer();
|
|
5964
|
+
}
|
|
5965
|
+
return this.confidenceScorer;
|
|
5966
|
+
}
|
|
3374
5967
|
// --------------------------------------------------------------------------
|
|
3375
5968
|
// Action Logging
|
|
3376
5969
|
// --------------------------------------------------------------------------
|
|
@@ -3401,7 +5994,7 @@ var Kontext = class _Kontext {
|
|
|
3401
5994
|
* @returns The created transaction record
|
|
3402
5995
|
*/
|
|
3403
5996
|
async logTransaction(input) {
|
|
3404
|
-
if (input.chain && input.chain !== "base") {
|
|
5997
|
+
if (input.chain && input.chain !== "base" && input.chain !== "arc") {
|
|
3405
5998
|
requirePlan("multi-chain", this.planManager.getTier());
|
|
3406
5999
|
}
|
|
3407
6000
|
this.validateMetadata(input.metadata);
|
|
@@ -3756,7 +6349,28 @@ var Kontext = class _Kontext {
|
|
|
3756
6349
|
*/
|
|
3757
6350
|
async verify(input) {
|
|
3758
6351
|
const transaction = await this.logTransaction(input);
|
|
3759
|
-
|
|
6352
|
+
let compliance;
|
|
6353
|
+
if (this.screeningAggregator) {
|
|
6354
|
+
const fromResult = await this.screeningAggregator.screen(input.from, {
|
|
6355
|
+
chain: input.chain,
|
|
6356
|
+
token: input.token,
|
|
6357
|
+
currency: input.currency,
|
|
6358
|
+
amount: input.amount,
|
|
6359
|
+
agentId: input.agentId
|
|
6360
|
+
});
|
|
6361
|
+
const toResult = await this.screeningAggregator.screen(input.to, {
|
|
6362
|
+
chain: input.chain,
|
|
6363
|
+
token: input.token,
|
|
6364
|
+
currency: input.currency,
|
|
6365
|
+
amount: input.amount,
|
|
6366
|
+
agentId: input.agentId
|
|
6367
|
+
});
|
|
6368
|
+
compliance = this.buildComplianceFromScreening(input, fromResult, toResult);
|
|
6369
|
+
} else if (isCryptoTransaction(input)) {
|
|
6370
|
+
compliance = UsdcCompliance.checkTransaction(input);
|
|
6371
|
+
} else {
|
|
6372
|
+
compliance = PaymentCompliance.checkPayment(input);
|
|
6373
|
+
}
|
|
3760
6374
|
let reasoningId;
|
|
3761
6375
|
if (input.reasoning) {
|
|
3762
6376
|
const entry = await this.logReasoning({
|
|
@@ -3778,6 +6392,29 @@ var Kontext = class _Kontext {
|
|
|
3778
6392
|
const verification = this.verifyDigestChain();
|
|
3779
6393
|
const chainLength = this.logger.getDigestChain().getChainLength();
|
|
3780
6394
|
const terminalDigest = this.getTerminalDigest();
|
|
6395
|
+
let anchorProof;
|
|
6396
|
+
if (input.anchor) {
|
|
6397
|
+
const { anchorDigest: anchorDigest2 } = await Promise.resolve().then(() => (init_onchain(), onchain_exports));
|
|
6398
|
+
anchorProof = await anchorDigest2(input.anchor, terminalDigest, this.config.projectId);
|
|
6399
|
+
}
|
|
6400
|
+
let counterpartyResult;
|
|
6401
|
+
if (input.counterparty) {
|
|
6402
|
+
const { exchangeAttestation: exchangeAttestation2 } = await Promise.resolve().then(() => (init_attestation(), attestation_exports));
|
|
6403
|
+
counterpartyResult = await exchangeAttestation2(input.counterparty, {
|
|
6404
|
+
senderDigest: terminalDigest,
|
|
6405
|
+
senderAgentId: input.agentId,
|
|
6406
|
+
txHash: input.txHash,
|
|
6407
|
+
chain: input.chain,
|
|
6408
|
+
amount: input.amount,
|
|
6409
|
+
token: input.token,
|
|
6410
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6411
|
+
});
|
|
6412
|
+
}
|
|
6413
|
+
let attribution;
|
|
6414
|
+
if (input.erc8021 && input.txHash) {
|
|
6415
|
+
const { fetchTransactionAttribution: fetchTransactionAttribution2 } = await Promise.resolve().then(() => (init_erc8021(), erc8021_exports));
|
|
6416
|
+
attribution = await fetchTransactionAttribution2(input.erc8021.rpcUrl, input.txHash) ?? void 0;
|
|
6417
|
+
}
|
|
3781
6418
|
let requiresApproval;
|
|
3782
6419
|
let task;
|
|
3783
6420
|
if (this.config.approvalThreshold) {
|
|
@@ -3819,7 +6456,10 @@ var Kontext = class _Kontext {
|
|
|
3819
6456
|
valid: verification.valid
|
|
3820
6457
|
},
|
|
3821
6458
|
...reasoningId ? { reasoningId } : {},
|
|
3822
|
-
...requiresApproval ? { requiresApproval, task } : {}
|
|
6459
|
+
...requiresApproval ? { requiresApproval, task } : {},
|
|
6460
|
+
...anchorProof ? { anchorProof } : {},
|
|
6461
|
+
...counterpartyResult ? { counterparty: counterpartyResult } : {},
|
|
6462
|
+
...attribution ? { attribution } : {}
|
|
3823
6463
|
};
|
|
3824
6464
|
}
|
|
3825
6465
|
// --------------------------------------------------------------------------
|
|
@@ -3999,10 +6639,211 @@ var Kontext = class _Kontext {
|
|
|
3999
6639
|
actions: actionSummary,
|
|
4000
6640
|
reasoning: reasoningEntries
|
|
4001
6641
|
};
|
|
4002
|
-
const hash = crypto$1.createHash("sha256");
|
|
4003
|
-
hash.update(JSON.stringify(certificateContent));
|
|
4004
|
-
const contentHash = hash.digest("hex");
|
|
4005
|
-
return { ...certificateContent, contentHash };
|
|
6642
|
+
const hash = crypto$1.createHash("sha256");
|
|
6643
|
+
hash.update(JSON.stringify(certificateContent));
|
|
6644
|
+
const contentHash = hash.digest("hex");
|
|
6645
|
+
return { ...certificateContent, contentHash };
|
|
6646
|
+
}
|
|
6647
|
+
// --------------------------------------------------------------------------
|
|
6648
|
+
// Agent Provenance
|
|
6649
|
+
// --------------------------------------------------------------------------
|
|
6650
|
+
/**
|
|
6651
|
+
* Create a delegated agent session. Records the delegation in the
|
|
6652
|
+
* tamper-evident digest chain as the session's genesis event.
|
|
6653
|
+
*/
|
|
6654
|
+
async createAgentSession(input) {
|
|
6655
|
+
return this.getProvenanceManager().createSession(input);
|
|
6656
|
+
}
|
|
6657
|
+
/**
|
|
6658
|
+
* Get an agent session by ID. Automatically marks expired sessions.
|
|
6659
|
+
*/
|
|
6660
|
+
getAgentSession(sessionId) {
|
|
6661
|
+
return this.getProvenanceManager().getSession(sessionId);
|
|
6662
|
+
}
|
|
6663
|
+
/**
|
|
6664
|
+
* Get all agent sessions.
|
|
6665
|
+
*/
|
|
6666
|
+
getAgentSessions() {
|
|
6667
|
+
return this.getProvenanceManager().getSessions();
|
|
6668
|
+
}
|
|
6669
|
+
/**
|
|
6670
|
+
* End an active agent session. Records the termination in the digest chain.
|
|
6671
|
+
*/
|
|
6672
|
+
async endAgentSession(sessionId) {
|
|
6673
|
+
return this.getProvenanceManager().endSession(sessionId);
|
|
6674
|
+
}
|
|
6675
|
+
/**
|
|
6676
|
+
* Check whether an action is within a session's delegated scope.
|
|
6677
|
+
*/
|
|
6678
|
+
validateSessionScope(sessionId, action) {
|
|
6679
|
+
return this.getProvenanceManager().validateScope(sessionId, action);
|
|
6680
|
+
}
|
|
6681
|
+
/**
|
|
6682
|
+
* Get all actions bound to a session (via sessionId on log/verify calls).
|
|
6683
|
+
*/
|
|
6684
|
+
getSessionActions(sessionId) {
|
|
6685
|
+
return this.store.getActionsBySession(sessionId);
|
|
6686
|
+
}
|
|
6687
|
+
/**
|
|
6688
|
+
* Create a provenance checkpoint -- a review point where a human
|
|
6689
|
+
* can attest to a batch of agent actions.
|
|
6690
|
+
*/
|
|
6691
|
+
async createCheckpoint(input) {
|
|
6692
|
+
return this.getProvenanceManager().createCheckpoint(input);
|
|
6693
|
+
}
|
|
6694
|
+
/**
|
|
6695
|
+
* Attach an externally-produced human attestation to a checkpoint.
|
|
6696
|
+
* The attestation includes a cryptographic signature that the agent
|
|
6697
|
+
* never touches -- key separation is the critical security property.
|
|
6698
|
+
*/
|
|
6699
|
+
async attachAttestation(checkpointId, attestation) {
|
|
6700
|
+
return this.getProvenanceManager().attachAttestation(checkpointId, attestation);
|
|
6701
|
+
}
|
|
6702
|
+
/**
|
|
6703
|
+
* Get a checkpoint by ID.
|
|
6704
|
+
*/
|
|
6705
|
+
getCheckpoint(checkpointId) {
|
|
6706
|
+
return this.getProvenanceManager().getCheckpoint(checkpointId);
|
|
6707
|
+
}
|
|
6708
|
+
/**
|
|
6709
|
+
* Get all checkpoints, optionally filtered by session.
|
|
6710
|
+
*/
|
|
6711
|
+
getCheckpoints(sessionId) {
|
|
6712
|
+
return this.getProvenanceManager().getCheckpoints(sessionId);
|
|
6713
|
+
}
|
|
6714
|
+
/**
|
|
6715
|
+
* Export the full provenance bundle for a session: session record,
|
|
6716
|
+
* all bound actions, all checkpoints, and verification stats.
|
|
6717
|
+
*/
|
|
6718
|
+
getProvenanceBundle(sessionId) {
|
|
6719
|
+
return this.getProvenanceManager().getProvenanceBundle(sessionId);
|
|
6720
|
+
}
|
|
6721
|
+
// --------------------------------------------------------------------------
|
|
6722
|
+
// Agent Forensics (KYA)
|
|
6723
|
+
// --------------------------------------------------------------------------
|
|
6724
|
+
/**
|
|
6725
|
+
* Register a new agent identity with optional wallet mappings.
|
|
6726
|
+
* Requires Pro plan.
|
|
6727
|
+
*/
|
|
6728
|
+
registerAgentIdentity(input) {
|
|
6729
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6730
|
+
return this.getIdentityRegistry().register(input);
|
|
6731
|
+
}
|
|
6732
|
+
/**
|
|
6733
|
+
* Get a registered agent identity by ID.
|
|
6734
|
+
* Requires Pro plan.
|
|
6735
|
+
*/
|
|
6736
|
+
getAgentIdentity(agentId) {
|
|
6737
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6738
|
+
return this.getIdentityRegistry().get(agentId);
|
|
6739
|
+
}
|
|
6740
|
+
/**
|
|
6741
|
+
* Update an existing agent identity.
|
|
6742
|
+
* Requires Pro plan.
|
|
6743
|
+
*/
|
|
6744
|
+
updateAgentIdentity(agentId, input) {
|
|
6745
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6746
|
+
return this.getIdentityRegistry().update(agentId, input);
|
|
6747
|
+
}
|
|
6748
|
+
/**
|
|
6749
|
+
* Remove an agent identity.
|
|
6750
|
+
* Requires Pro plan.
|
|
6751
|
+
*/
|
|
6752
|
+
removeAgentIdentity(agentId) {
|
|
6753
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6754
|
+
return this.getIdentityRegistry().remove(agentId);
|
|
6755
|
+
}
|
|
6756
|
+
/**
|
|
6757
|
+
* Add a wallet to an existing agent identity.
|
|
6758
|
+
* Requires Pro plan.
|
|
6759
|
+
*/
|
|
6760
|
+
addAgentWallet(agentId, wallet) {
|
|
6761
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6762
|
+
return this.getIdentityRegistry().addWallet(agentId, wallet);
|
|
6763
|
+
}
|
|
6764
|
+
/**
|
|
6765
|
+
* Look up which agent owns a wallet address.
|
|
6766
|
+
* Requires Pro plan.
|
|
6767
|
+
*/
|
|
6768
|
+
lookupAgentByWallet(address) {
|
|
6769
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6770
|
+
return this.getIdentityRegistry().lookupByWallet(address);
|
|
6771
|
+
}
|
|
6772
|
+
/**
|
|
6773
|
+
* Compute wallet clusters from transaction patterns and declared identities.
|
|
6774
|
+
* Requires Pro plan.
|
|
6775
|
+
*/
|
|
6776
|
+
getWalletClusters() {
|
|
6777
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6778
|
+
const clusterer = this.getWalletClusterer();
|
|
6779
|
+
clusterer.analyzeFromStore(this.store, this.getIdentityRegistry());
|
|
6780
|
+
return clusterer.getClusters();
|
|
6781
|
+
}
|
|
6782
|
+
/**
|
|
6783
|
+
* Export all KYA data as a single envelope.
|
|
6784
|
+
* Requires Pro plan.
|
|
6785
|
+
*/
|
|
6786
|
+
getKYAExport() {
|
|
6787
|
+
requirePlan("kya-identity", this.planManager.getTier());
|
|
6788
|
+
const registry = this.getIdentityRegistry();
|
|
6789
|
+
const clusterer = this.getWalletClusterer();
|
|
6790
|
+
clusterer.analyzeFromStore(this.store, registry);
|
|
6791
|
+
return {
|
|
6792
|
+
identities: registry.getAll(),
|
|
6793
|
+
clusters: clusterer.getClusters(),
|
|
6794
|
+
embeddings: [],
|
|
6795
|
+
links: [],
|
|
6796
|
+
scores: [],
|
|
6797
|
+
generatedAt: now()
|
|
6798
|
+
};
|
|
6799
|
+
}
|
|
6800
|
+
/**
|
|
6801
|
+
* Compute a behavioral embedding for an agent from transaction history.
|
|
6802
|
+
* Returns null if insufficient data. Requires Enterprise plan.
|
|
6803
|
+
*/
|
|
6804
|
+
computeBehavioralEmbedding(agentId) {
|
|
6805
|
+
requirePlan("kya-behavioral", this.planManager.getTier());
|
|
6806
|
+
return this.getBehavioralFingerprinter().computeEmbedding(agentId, this.store);
|
|
6807
|
+
}
|
|
6808
|
+
/**
|
|
6809
|
+
* Analyze all agents and create cross-session links.
|
|
6810
|
+
* Requires Enterprise plan.
|
|
6811
|
+
*/
|
|
6812
|
+
analyzeAgentLinks() {
|
|
6813
|
+
requirePlan("kya-behavioral", this.planManager.getTier());
|
|
6814
|
+
const clusterer = this.getWalletClusterer();
|
|
6815
|
+
clusterer.analyzeFromStore(this.store, this.getIdentityRegistry());
|
|
6816
|
+
return this.getCrossSessionLinker().analyzeAndLink(
|
|
6817
|
+
this.store,
|
|
6818
|
+
this.getIdentityRegistry(),
|
|
6819
|
+
clusterer,
|
|
6820
|
+
this.getBehavioralFingerprinter()
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6823
|
+
/**
|
|
6824
|
+
* Get agents linked to a specific agent.
|
|
6825
|
+
* Requires Enterprise plan.
|
|
6826
|
+
*/
|
|
6827
|
+
getLinkedAgents(agentId) {
|
|
6828
|
+
requirePlan("kya-behavioral", this.planManager.getTier());
|
|
6829
|
+
return this.getCrossSessionLinker().getLinkedAgents(agentId);
|
|
6830
|
+
}
|
|
6831
|
+
/**
|
|
6832
|
+
* Compute a composite identity confidence score for an agent.
|
|
6833
|
+
* Requires Enterprise plan.
|
|
6834
|
+
*/
|
|
6835
|
+
getKYAConfidenceScore(agentId) {
|
|
6836
|
+
requirePlan("kya-behavioral", this.planManager.getTier());
|
|
6837
|
+
const clusterer = this.getWalletClusterer();
|
|
6838
|
+
clusterer.analyzeFromStore(this.store, this.getIdentityRegistry());
|
|
6839
|
+
return this.getConfidenceScorer().computeScore(
|
|
6840
|
+
agentId,
|
|
6841
|
+
this.getIdentityRegistry(),
|
|
6842
|
+
clusterer,
|
|
6843
|
+
this.getBehavioralFingerprinter(),
|
|
6844
|
+
this.getCrossSessionLinker(),
|
|
6845
|
+
this.store
|
|
6846
|
+
);
|
|
4006
6847
|
}
|
|
4007
6848
|
// --------------------------------------------------------------------------
|
|
4008
6849
|
// Plan & Usage Metering
|
|
@@ -4105,9 +6946,20 @@ var Kontext = class _Kontext {
|
|
|
4105
6946
|
// Lifecycle
|
|
4106
6947
|
// --------------------------------------------------------------------------
|
|
4107
6948
|
/**
|
|
4108
|
-
*
|
|
6949
|
+
* Get the wallet monitor instance (or null if not configured).
|
|
6950
|
+
* Used by the viem interceptor for dedup registration.
|
|
6951
|
+
*/
|
|
6952
|
+
getWalletMonitor() {
|
|
6953
|
+
return this.walletMonitor;
|
|
6954
|
+
}
|
|
6955
|
+
/**
|
|
6956
|
+
* Gracefully shut down the SDK, flushing any pending data and stopping watchers.
|
|
4109
6957
|
*/
|
|
4110
6958
|
async destroy() {
|
|
6959
|
+
if (this.walletMonitor) {
|
|
6960
|
+
this.walletMonitor.stop();
|
|
6961
|
+
this.walletMonitor = null;
|
|
6962
|
+
}
|
|
4111
6963
|
await this.logger.destroy();
|
|
4112
6964
|
await this.exporter.shutdown();
|
|
4113
6965
|
}
|
|
@@ -4137,20 +6989,20 @@ var MemoryStorage = class {
|
|
|
4137
6989
|
var FileStorage = class {
|
|
4138
6990
|
baseDir;
|
|
4139
6991
|
constructor(baseDir) {
|
|
4140
|
-
this.baseDir =
|
|
6992
|
+
this.baseDir = path5__namespace.resolve(baseDir);
|
|
4141
6993
|
}
|
|
4142
6994
|
async save(key, data) {
|
|
4143
|
-
|
|
6995
|
+
fs5__namespace.mkdirSync(this.baseDir, { recursive: true });
|
|
4144
6996
|
const filePath = this.keyToPath(key);
|
|
4145
|
-
const dir =
|
|
4146
|
-
|
|
4147
|
-
|
|
6997
|
+
const dir = path5__namespace.dirname(filePath);
|
|
6998
|
+
fs5__namespace.mkdirSync(dir, { recursive: true });
|
|
6999
|
+
fs5__namespace.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
4148
7000
|
}
|
|
4149
7001
|
async load(key) {
|
|
4150
7002
|
const filePath = this.keyToPath(key);
|
|
4151
|
-
if (!
|
|
7003
|
+
if (!fs5__namespace.existsSync(filePath)) return null;
|
|
4152
7004
|
try {
|
|
4153
|
-
const raw =
|
|
7005
|
+
const raw = fs5__namespace.readFileSync(filePath, "utf-8");
|
|
4154
7006
|
return JSON.parse(raw);
|
|
4155
7007
|
} catch {
|
|
4156
7008
|
return null;
|
|
@@ -4158,12 +7010,12 @@ var FileStorage = class {
|
|
|
4158
7010
|
}
|
|
4159
7011
|
async delete(key) {
|
|
4160
7012
|
const filePath = this.keyToPath(key);
|
|
4161
|
-
if (
|
|
4162
|
-
|
|
7013
|
+
if (fs5__namespace.existsSync(filePath)) {
|
|
7014
|
+
fs5__namespace.unlinkSync(filePath);
|
|
4163
7015
|
}
|
|
4164
7016
|
}
|
|
4165
7017
|
async list(prefix) {
|
|
4166
|
-
if (!
|
|
7018
|
+
if (!fs5__namespace.existsSync(this.baseDir)) return [];
|
|
4167
7019
|
return this.listRecursive(this.baseDir, prefix);
|
|
4168
7020
|
}
|
|
4169
7021
|
/** Get the base directory path. */
|
|
@@ -4175,18 +7027,18 @@ var FileStorage = class {
|
|
|
4175
7027
|
// --------------------------------------------------------------------------
|
|
4176
7028
|
keyToPath(key) {
|
|
4177
7029
|
const safeName = key.replace(/[<>"|?*]/g, "_");
|
|
4178
|
-
return
|
|
7030
|
+
return path5__namespace.join(this.baseDir, `${safeName}.json`);
|
|
4179
7031
|
}
|
|
4180
7032
|
pathToKey(filePath) {
|
|
4181
|
-
const relative2 =
|
|
7033
|
+
const relative2 = path5__namespace.relative(this.baseDir, filePath);
|
|
4182
7034
|
return relative2.replace(/\.json$/, "");
|
|
4183
7035
|
}
|
|
4184
7036
|
listRecursive(dir, prefix) {
|
|
4185
7037
|
const keys = [];
|
|
4186
|
-
if (!
|
|
4187
|
-
const entries =
|
|
7038
|
+
if (!fs5__namespace.existsSync(dir)) return keys;
|
|
7039
|
+
const entries = fs5__namespace.readdirSync(dir, { withFileTypes: true });
|
|
4188
7040
|
for (const entry of entries) {
|
|
4189
|
-
const fullPath =
|
|
7041
|
+
const fullPath = path5__namespace.join(dir, entry.name);
|
|
4190
7042
|
if (entry.isDirectory()) {
|
|
4191
7043
|
keys.push(...this.listRecursive(fullPath, prefix));
|
|
4192
7044
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
@@ -4200,25 +7052,958 @@ var FileStorage = class {
|
|
|
4200
7052
|
}
|
|
4201
7053
|
};
|
|
4202
7054
|
|
|
7055
|
+
// src/index.ts
|
|
7056
|
+
init_onchain();
|
|
7057
|
+
init_erc8021();
|
|
7058
|
+
init_attestation();
|
|
7059
|
+
|
|
7060
|
+
// src/integrations/provider-treasury-sdn.ts
|
|
7061
|
+
var ACTIVE_SET = new Set(
|
|
7062
|
+
OFAC_SDN_ACTIVE_ADDRESSES.map((a) => a.toLowerCase())
|
|
7063
|
+
);
|
|
7064
|
+
var DELISTED_SET = new Set(
|
|
7065
|
+
OFAC_SDN_DELISTED_ADDRESSES.map((a) => a.toLowerCase())
|
|
7066
|
+
);
|
|
7067
|
+
var ALL_SET = new Set(
|
|
7068
|
+
OFAC_SDN_ADDRESSES.map((a) => a.toLowerCase())
|
|
7069
|
+
);
|
|
7070
|
+
var ORIGINAL_CASE = /* @__PURE__ */ new Map();
|
|
7071
|
+
for (const addr of OFAC_SDN_ADDRESSES) {
|
|
7072
|
+
ORIGINAL_CASE.set(addr.toLowerCase(), addr);
|
|
7073
|
+
}
|
|
7074
|
+
var OFACAddressProvider = class {
|
|
7075
|
+
id = "ofac-sdn-address";
|
|
7076
|
+
name = "OFAC SDN Address Screener";
|
|
7077
|
+
lists = ["OFAC_SDN"];
|
|
7078
|
+
requiresApiKey = false;
|
|
7079
|
+
browserCompatible = true;
|
|
7080
|
+
queryTypes = ["address"];
|
|
7081
|
+
async screen(query, _context) {
|
|
7082
|
+
const start = Date.now();
|
|
7083
|
+
const lower = query.toLowerCase();
|
|
7084
|
+
const matches = [];
|
|
7085
|
+
if (ACTIVE_SET.has(lower)) {
|
|
7086
|
+
const originalAddr = ORIGINAL_CASE.get(lower) ?? query;
|
|
7087
|
+
matches.push({
|
|
7088
|
+
list: "OFAC_SDN",
|
|
7089
|
+
matchType: "exact_address",
|
|
7090
|
+
similarity: 1,
|
|
7091
|
+
matchedValue: originalAddr,
|
|
7092
|
+
entityStatus: "active",
|
|
7093
|
+
program: "SDN"
|
|
7094
|
+
});
|
|
7095
|
+
} else if (DELISTED_SET.has(lower)) {
|
|
7096
|
+
const originalAddr = ORIGINAL_CASE.get(lower) ?? query;
|
|
7097
|
+
matches.push({
|
|
7098
|
+
list: "OFAC_SDN",
|
|
7099
|
+
matchType: "exact_address",
|
|
7100
|
+
similarity: 1,
|
|
7101
|
+
matchedValue: originalAddr,
|
|
7102
|
+
entityStatus: "delisted",
|
|
7103
|
+
program: "SDN_DELISTED"
|
|
7104
|
+
});
|
|
7105
|
+
}
|
|
7106
|
+
const hasActiveHit = matches.some((m) => m.entityStatus === "active");
|
|
7107
|
+
return {
|
|
7108
|
+
providerId: this.id,
|
|
7109
|
+
hit: hasActiveHit,
|
|
7110
|
+
matches,
|
|
7111
|
+
listsChecked: this.lists,
|
|
7112
|
+
entriesSearched: ALL_SET.size,
|
|
7113
|
+
durationMs: Date.now() - start
|
|
7114
|
+
};
|
|
7115
|
+
}
|
|
7116
|
+
isAvailable() {
|
|
7117
|
+
return true;
|
|
7118
|
+
}
|
|
7119
|
+
getEntryCount() {
|
|
7120
|
+
return ALL_SET.size;
|
|
7121
|
+
}
|
|
7122
|
+
};
|
|
7123
|
+
|
|
7124
|
+
// src/integrations/provider-ofac-entity.ts
|
|
7125
|
+
var NAME_MATCH_THRESHOLD2 = 0.85;
|
|
7126
|
+
var MIN_MATCH_LENGTH = 4;
|
|
7127
|
+
var _screener2 = null;
|
|
7128
|
+
var _screenerLoaded2 = false;
|
|
7129
|
+
function getScreener2() {
|
|
7130
|
+
if (!_screenerLoaded2) {
|
|
7131
|
+
_screenerLoaded2 = true;
|
|
7132
|
+
try {
|
|
7133
|
+
const mod = __require("./ofac-sanctions.js");
|
|
7134
|
+
if (mod.OFACSanctionsScreener) {
|
|
7135
|
+
_screener2 = new mod.OFACSanctionsScreener();
|
|
7136
|
+
}
|
|
7137
|
+
} catch {
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
return _screener2;
|
|
7141
|
+
}
|
|
7142
|
+
var OFACEntityProvider = class {
|
|
7143
|
+
id = "ofac-sdn-entity";
|
|
7144
|
+
name = "OFAC SDN Entity Screener";
|
|
7145
|
+
lists = ["OFAC_SDN"];
|
|
7146
|
+
requiresApiKey = false;
|
|
7147
|
+
browserCompatible = false;
|
|
7148
|
+
queryTypes = ["entity_name"];
|
|
7149
|
+
async screen(query, _context) {
|
|
7150
|
+
const start = Date.now();
|
|
7151
|
+
const screener = getScreener2();
|
|
7152
|
+
if (!screener) {
|
|
7153
|
+
return {
|
|
7154
|
+
providerId: this.id,
|
|
7155
|
+
hit: false,
|
|
7156
|
+
matches: [],
|
|
7157
|
+
listsChecked: this.lists,
|
|
7158
|
+
entriesSearched: 0,
|
|
7159
|
+
durationMs: Date.now() - start,
|
|
7160
|
+
error: "OFACSanctionsScreener not available"
|
|
7161
|
+
};
|
|
7162
|
+
}
|
|
7163
|
+
const rawMatches = screener.searchEntityName(query, NAME_MATCH_THRESHOLD2);
|
|
7164
|
+
const filteredMatches = rawMatches.filter(
|
|
7165
|
+
(m) => m.similarity >= NAME_MATCH_THRESHOLD2 && m.matchedOn.length >= MIN_MATCH_LENGTH
|
|
7166
|
+
);
|
|
7167
|
+
const matches = filteredMatches.map((m) => {
|
|
7168
|
+
const isActive = m.entity.list !== "DELISTED";
|
|
7169
|
+
return {
|
|
7170
|
+
list: "OFAC_SDN",
|
|
7171
|
+
matchType: m.similarity >= 0.99 ? "exact_address" : "fuzzy_name",
|
|
7172
|
+
similarity: m.similarity,
|
|
7173
|
+
matchedValue: m.matchedOn,
|
|
7174
|
+
entityStatus: isActive ? "active" : "delisted",
|
|
7175
|
+
entityName: m.entity.name,
|
|
7176
|
+
program: m.entity.programs?.[0]
|
|
7177
|
+
};
|
|
7178
|
+
});
|
|
7179
|
+
const hasActiveHit = matches.some((m) => m.entityStatus === "active");
|
|
7180
|
+
return {
|
|
7181
|
+
providerId: this.id,
|
|
7182
|
+
hit: hasActiveHit,
|
|
7183
|
+
matches,
|
|
7184
|
+
listsChecked: this.lists,
|
|
7185
|
+
entriesSearched: screener.getEntityCount?.() ?? 0,
|
|
7186
|
+
durationMs: Date.now() - start
|
|
7187
|
+
};
|
|
7188
|
+
}
|
|
7189
|
+
isAvailable() {
|
|
7190
|
+
return getScreener2() !== null;
|
|
7191
|
+
}
|
|
7192
|
+
getEntryCount() {
|
|
7193
|
+
const screener = getScreener2();
|
|
7194
|
+
return screener?.getEntityCount?.() ?? 0;
|
|
7195
|
+
}
|
|
7196
|
+
};
|
|
7197
|
+
|
|
7198
|
+
// src/integrations/data/uk-ofsi-addresses.ts
|
|
7199
|
+
var UK_OFSI_ADDRESSES = [
|
|
7200
|
+
// Garantex (designated under Russia sanctions regime, May 2024)
|
|
7201
|
+
"0x6F1cA141A28907F78Ebaa64f83E4AE6038d3cbe7",
|
|
7202
|
+
// Lazarus Group / DPRK (overlaps with OFAC SDN but independently listed by OFSI)
|
|
7203
|
+
"0x098B716B8Aaf21512996dC57EB0615e2383E2f96",
|
|
7204
|
+
"0xa0e1c89Ef1a489c9C7dE96311eD5Ce5D32c20E4B",
|
|
7205
|
+
"0x3Cffd56B47B7b41c56258D9C7731ABaDc360E460",
|
|
7206
|
+
"0x53b6936513e738f44FB50d2b9476730C0Ab3Bfc1",
|
|
7207
|
+
// DPRK-linked addresses (OFSI cyber programme)
|
|
7208
|
+
"0x7F367cC41522cE07553e823bf3be79A889DEbe1B",
|
|
7209
|
+
"0x01e2919679362dFBC9ee1644Ba9C6da6D6245BB1"
|
|
7210
|
+
];
|
|
7211
|
+
|
|
7212
|
+
// src/integrations/provider-uk-ofsi.ts
|
|
7213
|
+
var ADDRESS_SET = new Set(
|
|
7214
|
+
UK_OFSI_ADDRESSES.map((a) => a.toLowerCase())
|
|
7215
|
+
);
|
|
7216
|
+
var ORIGINAL_CASE2 = /* @__PURE__ */ new Map();
|
|
7217
|
+
for (const addr of UK_OFSI_ADDRESSES) {
|
|
7218
|
+
ORIGINAL_CASE2.set(addr.toLowerCase(), addr);
|
|
7219
|
+
}
|
|
7220
|
+
var UKOFSIProvider = class {
|
|
7221
|
+
id = "uk-ofsi-address";
|
|
7222
|
+
name = "UK OFSI Address Screener";
|
|
7223
|
+
lists = ["UK_OFSI"];
|
|
7224
|
+
requiresApiKey = false;
|
|
7225
|
+
browserCompatible = true;
|
|
7226
|
+
queryTypes = ["address"];
|
|
7227
|
+
async screen(query, _context) {
|
|
7228
|
+
const start = Date.now();
|
|
7229
|
+
const lower = query.toLowerCase();
|
|
7230
|
+
const matches = [];
|
|
7231
|
+
if (ADDRESS_SET.has(lower)) {
|
|
7232
|
+
const originalAddr = ORIGINAL_CASE2.get(lower) ?? query;
|
|
7233
|
+
matches.push({
|
|
7234
|
+
list: "UK_OFSI",
|
|
7235
|
+
matchType: "exact_address",
|
|
7236
|
+
similarity: 1,
|
|
7237
|
+
matchedValue: originalAddr,
|
|
7238
|
+
entityStatus: "active",
|
|
7239
|
+
program: "OFSI_CONSOLIDATED"
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
7242
|
+
return {
|
|
7243
|
+
providerId: this.id,
|
|
7244
|
+
hit: matches.length > 0,
|
|
7245
|
+
matches,
|
|
7246
|
+
listsChecked: this.lists,
|
|
7247
|
+
entriesSearched: ADDRESS_SET.size,
|
|
7248
|
+
durationMs: Date.now() - start
|
|
7249
|
+
};
|
|
7250
|
+
}
|
|
7251
|
+
isAvailable() {
|
|
7252
|
+
return true;
|
|
7253
|
+
}
|
|
7254
|
+
getEntryCount() {
|
|
7255
|
+
return ADDRESS_SET.size;
|
|
7256
|
+
}
|
|
7257
|
+
};
|
|
7258
|
+
|
|
7259
|
+
// src/integrations/provider-opensanctions-local.ts
|
|
7260
|
+
var NAME_MATCH_THRESHOLD3 = 0.85;
|
|
7261
|
+
var MIN_MATCH_LENGTH2 = 4;
|
|
7262
|
+
var DEFAULT_DATA_DIR = ".kontext/sanctions";
|
|
7263
|
+
function trigramSimilarity(a, b) {
|
|
7264
|
+
const aNorm = a.toLowerCase().trim();
|
|
7265
|
+
const bNorm = b.toLowerCase().trim();
|
|
7266
|
+
if (aNorm === bNorm) return 1;
|
|
7267
|
+
if (aNorm.length < 2 || bNorm.length < 2) return 0;
|
|
7268
|
+
const trigramsA = /* @__PURE__ */ new Set();
|
|
7269
|
+
const trigramsB = /* @__PURE__ */ new Set();
|
|
7270
|
+
for (let i = 0; i <= aNorm.length - 3; i++) {
|
|
7271
|
+
trigramsA.add(aNorm.slice(i, i + 3));
|
|
7272
|
+
}
|
|
7273
|
+
for (let i = 0; i <= bNorm.length - 3; i++) {
|
|
7274
|
+
trigramsB.add(bNorm.slice(i, i + 3));
|
|
7275
|
+
}
|
|
7276
|
+
if (trigramsA.size === 0 || trigramsB.size === 0) return 0;
|
|
7277
|
+
let intersection = 0;
|
|
7278
|
+
for (const t of trigramsA) {
|
|
7279
|
+
if (trigramsB.has(t)) intersection++;
|
|
7280
|
+
}
|
|
7281
|
+
return 2 * intersection / (trigramsA.size + trigramsB.size);
|
|
7282
|
+
}
|
|
7283
|
+
var OpenSanctionsLocalProvider = class {
|
|
7284
|
+
id = "opensanctions-local";
|
|
7285
|
+
name = "OpenSanctions (Local Data)";
|
|
7286
|
+
lists = ["OPENSANCTIONS"];
|
|
7287
|
+
requiresApiKey = false;
|
|
7288
|
+
browserCompatible = false;
|
|
7289
|
+
queryTypes = ["both"];
|
|
7290
|
+
dataDir;
|
|
7291
|
+
entities = [];
|
|
7292
|
+
addressSet = /* @__PURE__ */ new Set();
|
|
7293
|
+
addressToEntity = /* @__PURE__ */ new Map();
|
|
7294
|
+
loaded = false;
|
|
7295
|
+
constructor(dataDir) {
|
|
7296
|
+
this.dataDir = dataDir ?? this.resolveDataDir();
|
|
7297
|
+
}
|
|
7298
|
+
async screen(query, _context) {
|
|
7299
|
+
const start = Date.now();
|
|
7300
|
+
if (!this.loaded) {
|
|
7301
|
+
this.loadData();
|
|
7302
|
+
}
|
|
7303
|
+
if (this.entities.length === 0) {
|
|
7304
|
+
return {
|
|
7305
|
+
providerId: this.id,
|
|
7306
|
+
hit: false,
|
|
7307
|
+
matches: [],
|
|
7308
|
+
listsChecked: this.lists,
|
|
7309
|
+
entriesSearched: 0,
|
|
7310
|
+
durationMs: Date.now() - start,
|
|
7311
|
+
error: "No local OpenSanctions data. Run: kontext sync --lists default"
|
|
7312
|
+
};
|
|
7313
|
+
}
|
|
7314
|
+
const matches = isBlockchainAddress(query) ? this.screenAddress(query) : this.screenEntityName(query);
|
|
7315
|
+
const hasActiveHit = matches.some((m) => m.entityStatus === "active");
|
|
7316
|
+
return {
|
|
7317
|
+
providerId: this.id,
|
|
7318
|
+
hit: hasActiveHit,
|
|
7319
|
+
matches,
|
|
7320
|
+
listsChecked: this.lists,
|
|
7321
|
+
entriesSearched: this.entities.length,
|
|
7322
|
+
durationMs: Date.now() - start
|
|
7323
|
+
};
|
|
7324
|
+
}
|
|
7325
|
+
isAvailable() {
|
|
7326
|
+
if (!this.loaded) {
|
|
7327
|
+
this.loadData();
|
|
7328
|
+
}
|
|
7329
|
+
return this.entities.length > 0;
|
|
7330
|
+
}
|
|
7331
|
+
getEntryCount() {
|
|
7332
|
+
if (!this.loaded) {
|
|
7333
|
+
this.loadData();
|
|
7334
|
+
}
|
|
7335
|
+
return this.entities.length;
|
|
7336
|
+
}
|
|
7337
|
+
async sync() {
|
|
7338
|
+
this.loaded = false;
|
|
7339
|
+
this.loadData();
|
|
7340
|
+
return { updated: true, count: this.entities.length };
|
|
7341
|
+
}
|
|
7342
|
+
// --------------------------------------------------------------------------
|
|
7343
|
+
// Private
|
|
7344
|
+
// --------------------------------------------------------------------------
|
|
7345
|
+
screenAddress(address) {
|
|
7346
|
+
const lower = address.toLowerCase();
|
|
7347
|
+
const entity = this.addressToEntity.get(lower);
|
|
7348
|
+
if (!entity) return [];
|
|
7349
|
+
const entityStatus = this.getEntityStatus(entity);
|
|
7350
|
+
return [
|
|
7351
|
+
{
|
|
7352
|
+
list: "OPENSANCTIONS",
|
|
7353
|
+
matchType: "exact_address",
|
|
7354
|
+
similarity: 1,
|
|
7355
|
+
matchedValue: address,
|
|
7356
|
+
entityStatus,
|
|
7357
|
+
entityName: entity.caption,
|
|
7358
|
+
program: entity.datasets.join(", ")
|
|
7359
|
+
}
|
|
7360
|
+
];
|
|
7361
|
+
}
|
|
7362
|
+
screenEntityName(query) {
|
|
7363
|
+
const matches = [];
|
|
7364
|
+
const queryLower = query.toLowerCase().trim();
|
|
7365
|
+
for (const entity of this.entities) {
|
|
7366
|
+
const captionSim = trigramSimilarity(queryLower, entity.caption);
|
|
7367
|
+
if (captionSim >= NAME_MATCH_THRESHOLD3 && entity.caption.length >= MIN_MATCH_LENGTH2) {
|
|
7368
|
+
matches.push(this.entityToMatch(entity, entity.caption, captionSim));
|
|
7369
|
+
continue;
|
|
7370
|
+
}
|
|
7371
|
+
const aliases = entity.properties["alias"] ?? [];
|
|
7372
|
+
for (const alias of aliases) {
|
|
7373
|
+
const aliasSim = trigramSimilarity(queryLower, alias);
|
|
7374
|
+
if (aliasSim >= NAME_MATCH_THRESHOLD3 && alias.length >= MIN_MATCH_LENGTH2) {
|
|
7375
|
+
matches.push(this.entityToMatch(entity, alias, aliasSim));
|
|
7376
|
+
break;
|
|
7377
|
+
}
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
matches.sort((a, b) => b.similarity - a.similarity);
|
|
7381
|
+
return matches.slice(0, 5);
|
|
7382
|
+
}
|
|
7383
|
+
entityToMatch(entity, matchedValue, similarity) {
|
|
7384
|
+
return {
|
|
7385
|
+
list: "OPENSANCTIONS",
|
|
7386
|
+
matchType: similarity >= 0.99 ? "exact_address" : "fuzzy_name",
|
|
7387
|
+
similarity,
|
|
7388
|
+
matchedValue,
|
|
7389
|
+
entityStatus: this.getEntityStatus(entity),
|
|
7390
|
+
entityName: entity.caption,
|
|
7391
|
+
program: entity.datasets.join(", ")
|
|
7392
|
+
};
|
|
7393
|
+
}
|
|
7394
|
+
getEntityStatus(entity) {
|
|
7395
|
+
return "active";
|
|
7396
|
+
}
|
|
7397
|
+
loadData() {
|
|
7398
|
+
this.loaded = true;
|
|
7399
|
+
this.entities = [];
|
|
7400
|
+
this.addressSet.clear();
|
|
7401
|
+
this.addressToEntity.clear();
|
|
7402
|
+
try {
|
|
7403
|
+
const fs6 = __require("fs");
|
|
7404
|
+
const pathMod = __require("path");
|
|
7405
|
+
const dataPath = pathMod.resolve(this.dataDir);
|
|
7406
|
+
if (!fs6.existsSync(dataPath)) return;
|
|
7407
|
+
const files = fs6.readdirSync(dataPath).filter(
|
|
7408
|
+
(f) => f.endsWith(".json")
|
|
7409
|
+
);
|
|
7410
|
+
for (const file of files) {
|
|
7411
|
+
const filePath = pathMod.join(dataPath, file);
|
|
7412
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
7413
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
7414
|
+
for (const line of lines) {
|
|
7415
|
+
try {
|
|
7416
|
+
const entity = JSON.parse(line);
|
|
7417
|
+
this.entities.push(entity);
|
|
7418
|
+
const addresses = entity.properties["cryptoAddress"] ?? [];
|
|
7419
|
+
for (const addr of addresses) {
|
|
7420
|
+
const lower = addr.toLowerCase();
|
|
7421
|
+
this.addressSet.add(lower);
|
|
7422
|
+
this.addressToEntity.set(lower, entity);
|
|
7423
|
+
}
|
|
7424
|
+
} catch {
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
} catch {
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
resolveDataDir() {
|
|
7432
|
+
try {
|
|
7433
|
+
const os = __require("os");
|
|
7434
|
+
const pathMod = __require("path");
|
|
7435
|
+
return pathMod.join(os.homedir(), DEFAULT_DATA_DIR);
|
|
7436
|
+
} catch {
|
|
7437
|
+
return DEFAULT_DATA_DIR;
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7440
|
+
};
|
|
7441
|
+
|
|
7442
|
+
// src/integrations/provider-apis.ts
|
|
7443
|
+
var OpenSanctionsProvider = class {
|
|
7444
|
+
id = "opensanctions-api";
|
|
7445
|
+
name = "OpenSanctions (API)";
|
|
7446
|
+
lists = ["OPENSANCTIONS"];
|
|
7447
|
+
requiresApiKey = true;
|
|
7448
|
+
browserCompatible = false;
|
|
7449
|
+
queryTypes = ["both"];
|
|
7450
|
+
apiKey;
|
|
7451
|
+
baseUrl;
|
|
7452
|
+
dataset;
|
|
7453
|
+
constructor(config) {
|
|
7454
|
+
this.apiKey = config.apiKey;
|
|
7455
|
+
this.baseUrl = config.baseUrl ?? "https://api.opensanctions.org";
|
|
7456
|
+
this.dataset = config.dataset ?? "default";
|
|
7457
|
+
}
|
|
7458
|
+
async screen(query, _context) {
|
|
7459
|
+
const start = Date.now();
|
|
7460
|
+
const isAddr = isBlockchainAddress(query);
|
|
7461
|
+
const schema = isAddr ? "CryptoWallet" : "LegalEntity";
|
|
7462
|
+
const properties = isAddr ? { cryptoAddress: [query] } : { name: [query] };
|
|
7463
|
+
const body = {
|
|
7464
|
+
queries: {
|
|
7465
|
+
q: {
|
|
7466
|
+
schema,
|
|
7467
|
+
properties
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
};
|
|
7471
|
+
try {
|
|
7472
|
+
const response = await fetch(
|
|
7473
|
+
`${this.baseUrl}/match/${this.dataset}`,
|
|
7474
|
+
{
|
|
7475
|
+
method: "POST",
|
|
7476
|
+
headers: {
|
|
7477
|
+
"Content-Type": "application/json",
|
|
7478
|
+
"Authorization": `ApiKey ${this.apiKey}`
|
|
7479
|
+
},
|
|
7480
|
+
body: JSON.stringify(body)
|
|
7481
|
+
}
|
|
7482
|
+
);
|
|
7483
|
+
if (!response.ok) {
|
|
7484
|
+
return {
|
|
7485
|
+
providerId: this.id,
|
|
7486
|
+
hit: false,
|
|
7487
|
+
matches: [],
|
|
7488
|
+
listsChecked: this.lists,
|
|
7489
|
+
entriesSearched: 0,
|
|
7490
|
+
durationMs: Date.now() - start,
|
|
7491
|
+
error: `OpenSanctions API error: ${response.status} ${response.statusText}`
|
|
7492
|
+
};
|
|
7493
|
+
}
|
|
7494
|
+
const data = await response.json();
|
|
7495
|
+
const queryResult = data.responses?.["q"];
|
|
7496
|
+
if (!queryResult) {
|
|
7497
|
+
return {
|
|
7498
|
+
providerId: this.id,
|
|
7499
|
+
hit: false,
|
|
7500
|
+
matches: [],
|
|
7501
|
+
listsChecked: this.lists,
|
|
7502
|
+
entriesSearched: 0,
|
|
7503
|
+
durationMs: Date.now() - start
|
|
7504
|
+
};
|
|
7505
|
+
}
|
|
7506
|
+
const matches = queryResult.results.filter((r) => r.match && r.score >= 0.7).map((r) => ({
|
|
7507
|
+
list: "OPENSANCTIONS",
|
|
7508
|
+
matchType: r.score >= 0.99 ? "exact_address" : "fuzzy_name",
|
|
7509
|
+
similarity: r.score,
|
|
7510
|
+
matchedValue: r.caption,
|
|
7511
|
+
entityStatus: "active",
|
|
7512
|
+
entityName: r.caption,
|
|
7513
|
+
program: r.datasets.join(", ")
|
|
7514
|
+
}));
|
|
7515
|
+
const hasActiveHit = matches.some((m) => m.entityStatus === "active");
|
|
7516
|
+
return {
|
|
7517
|
+
providerId: this.id,
|
|
7518
|
+
hit: hasActiveHit,
|
|
7519
|
+
matches,
|
|
7520
|
+
listsChecked: this.lists,
|
|
7521
|
+
entriesSearched: queryResult.total?.value ?? 0,
|
|
7522
|
+
durationMs: Date.now() - start
|
|
7523
|
+
};
|
|
7524
|
+
} catch (err) {
|
|
7525
|
+
return {
|
|
7526
|
+
providerId: this.id,
|
|
7527
|
+
hit: false,
|
|
7528
|
+
matches: [],
|
|
7529
|
+
listsChecked: this.lists,
|
|
7530
|
+
entriesSearched: 0,
|
|
7531
|
+
durationMs: Date.now() - start,
|
|
7532
|
+
error: `OpenSanctions API error: ${err instanceof Error ? err.message : String(err)}`
|
|
7533
|
+
};
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
isAvailable() {
|
|
7537
|
+
return !!this.apiKey;
|
|
7538
|
+
}
|
|
7539
|
+
};
|
|
7540
|
+
var ChainalysisFreeAPIProvider = class {
|
|
7541
|
+
id = "chainalysis-free-api";
|
|
7542
|
+
name = "Chainalysis Free API";
|
|
7543
|
+
lists = ["CHAINALYSIS"];
|
|
7544
|
+
requiresApiKey = true;
|
|
7545
|
+
browserCompatible = false;
|
|
7546
|
+
queryTypes = ["address"];
|
|
7547
|
+
apiKey;
|
|
7548
|
+
baseUrl;
|
|
7549
|
+
constructor(config) {
|
|
7550
|
+
this.apiKey = config.apiKey;
|
|
7551
|
+
this.baseUrl = config.baseUrl ?? "https://public.chainalysis.com/api/v1";
|
|
7552
|
+
}
|
|
7553
|
+
async screen(query, _context) {
|
|
7554
|
+
const start = Date.now();
|
|
7555
|
+
try {
|
|
7556
|
+
const response = await fetch(
|
|
7557
|
+
`${this.baseUrl}/address/${query}`,
|
|
7558
|
+
{
|
|
7559
|
+
method: "GET",
|
|
7560
|
+
headers: {
|
|
7561
|
+
"X-API-Key": this.apiKey,
|
|
7562
|
+
"Accept": "application/json"
|
|
7563
|
+
}
|
|
7564
|
+
}
|
|
7565
|
+
);
|
|
7566
|
+
if (!response.ok) {
|
|
7567
|
+
return {
|
|
7568
|
+
providerId: this.id,
|
|
7569
|
+
hit: false,
|
|
7570
|
+
matches: [],
|
|
7571
|
+
listsChecked: this.lists,
|
|
7572
|
+
entriesSearched: 0,
|
|
7573
|
+
durationMs: Date.now() - start,
|
|
7574
|
+
error: `Chainalysis API error: ${response.status} ${response.statusText}`
|
|
7575
|
+
};
|
|
7576
|
+
}
|
|
7577
|
+
const data = await response.json();
|
|
7578
|
+
const identifications = data.identifications ?? [];
|
|
7579
|
+
const matches = identifications.map((id) => ({
|
|
7580
|
+
list: "CHAINALYSIS",
|
|
7581
|
+
matchType: "exact_address",
|
|
7582
|
+
similarity: 1,
|
|
7583
|
+
matchedValue: query,
|
|
7584
|
+
entityStatus: "active",
|
|
7585
|
+
entityName: id.name,
|
|
7586
|
+
program: id.category
|
|
7587
|
+
}));
|
|
7588
|
+
return {
|
|
7589
|
+
providerId: this.id,
|
|
7590
|
+
hit: matches.length > 0,
|
|
7591
|
+
matches,
|
|
7592
|
+
listsChecked: this.lists,
|
|
7593
|
+
entriesSearched: 1,
|
|
7594
|
+
durationMs: Date.now() - start
|
|
7595
|
+
};
|
|
7596
|
+
} catch (err) {
|
|
7597
|
+
return {
|
|
7598
|
+
providerId: this.id,
|
|
7599
|
+
hit: false,
|
|
7600
|
+
matches: [],
|
|
7601
|
+
listsChecked: this.lists,
|
|
7602
|
+
entriesSearched: 0,
|
|
7603
|
+
durationMs: Date.now() - start,
|
|
7604
|
+
error: `Chainalysis API error: ${err instanceof Error ? err.message : String(err)}`
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
}
|
|
7608
|
+
isAvailable() {
|
|
7609
|
+
return !!this.apiKey;
|
|
7610
|
+
}
|
|
7611
|
+
};
|
|
7612
|
+
|
|
7613
|
+
// src/integrations/provider-ofac.ts
|
|
7614
|
+
var ORACLE_CONTRACT = "0x40C57923924B5c5c5455c48D93317139ADDaC8fb";
|
|
7615
|
+
var IS_SANCTIONED_SELECTOR = "0xdfb80831";
|
|
7616
|
+
var ChainalysisOracleProvider = class {
|
|
7617
|
+
id = "chainalysis-oracle";
|
|
7618
|
+
name = "Chainalysis OFAC Oracle";
|
|
7619
|
+
lists = ["OFAC_SDN", "CHAINALYSIS"];
|
|
7620
|
+
requiresApiKey = true;
|
|
7621
|
+
browserCompatible = false;
|
|
7622
|
+
queryTypes = ["address"];
|
|
7623
|
+
apiKey;
|
|
7624
|
+
rpcUrl;
|
|
7625
|
+
apiBaseUrl;
|
|
7626
|
+
constructor(config) {
|
|
7627
|
+
this.apiKey = config.apiKey;
|
|
7628
|
+
this.rpcUrl = config.rpcUrl;
|
|
7629
|
+
this.apiBaseUrl = config.apiBaseUrl ?? "https://public.chainalysis.com/api/v1";
|
|
7630
|
+
}
|
|
7631
|
+
async screen(query, _context) {
|
|
7632
|
+
const start = Date.now();
|
|
7633
|
+
if (this.rpcUrl) {
|
|
7634
|
+
return this.screenOnChain(query, start);
|
|
7635
|
+
}
|
|
7636
|
+
if (this.apiKey) {
|
|
7637
|
+
return this.screenApi(query, start);
|
|
7638
|
+
}
|
|
7639
|
+
return {
|
|
7640
|
+
providerId: this.id,
|
|
7641
|
+
hit: false,
|
|
7642
|
+
matches: [],
|
|
7643
|
+
listsChecked: this.lists,
|
|
7644
|
+
entriesSearched: 0,
|
|
7645
|
+
durationMs: Date.now() - start,
|
|
7646
|
+
error: "No API key or RPC URL configured"
|
|
7647
|
+
};
|
|
7648
|
+
}
|
|
7649
|
+
isAvailable() {
|
|
7650
|
+
return !!(this.apiKey || this.rpcUrl);
|
|
7651
|
+
}
|
|
7652
|
+
// --------------------------------------------------------------------------
|
|
7653
|
+
// On-chain oracle query
|
|
7654
|
+
// --------------------------------------------------------------------------
|
|
7655
|
+
async screenOnChain(address, start) {
|
|
7656
|
+
try {
|
|
7657
|
+
const paddedAddress = address.toLowerCase().replace("0x", "").padStart(64, "0");
|
|
7658
|
+
const callData = IS_SANCTIONED_SELECTOR + paddedAddress;
|
|
7659
|
+
const response = await fetch(this.rpcUrl, {
|
|
7660
|
+
method: "POST",
|
|
7661
|
+
headers: { "Content-Type": "application/json" },
|
|
7662
|
+
body: JSON.stringify({
|
|
7663
|
+
jsonrpc: "2.0",
|
|
7664
|
+
id: 1,
|
|
7665
|
+
method: "eth_call",
|
|
7666
|
+
params: [
|
|
7667
|
+
{ to: ORACLE_CONTRACT, data: callData },
|
|
7668
|
+
"latest"
|
|
7669
|
+
]
|
|
7670
|
+
})
|
|
7671
|
+
});
|
|
7672
|
+
if (!response.ok) {
|
|
7673
|
+
return this.errorResult(start, `RPC error: ${response.status}`);
|
|
7674
|
+
}
|
|
7675
|
+
const data = await response.json();
|
|
7676
|
+
if (data.error) {
|
|
7677
|
+
return this.errorResult(start, `RPC error: ${data.error.message}`);
|
|
7678
|
+
}
|
|
7679
|
+
const isSanctioned = data.result ? parseInt(data.result.slice(-2), 16) === 1 : false;
|
|
7680
|
+
const matches = isSanctioned ? [{
|
|
7681
|
+
list: "OFAC_SDN",
|
|
7682
|
+
matchType: "exact_address",
|
|
7683
|
+
similarity: 1,
|
|
7684
|
+
matchedValue: address,
|
|
7685
|
+
entityStatus: "active",
|
|
7686
|
+
program: "CHAINALYSIS_ORACLE"
|
|
7687
|
+
}] : [];
|
|
7688
|
+
return {
|
|
7689
|
+
providerId: this.id,
|
|
7690
|
+
hit: isSanctioned,
|
|
7691
|
+
matches,
|
|
7692
|
+
listsChecked: this.lists,
|
|
7693
|
+
entriesSearched: 1,
|
|
7694
|
+
durationMs: Date.now() - start
|
|
7695
|
+
};
|
|
7696
|
+
} catch (err) {
|
|
7697
|
+
return this.errorResult(
|
|
7698
|
+
start,
|
|
7699
|
+
`Oracle query failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7700
|
+
);
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
// --------------------------------------------------------------------------
|
|
7704
|
+
// REST API query
|
|
7705
|
+
// --------------------------------------------------------------------------
|
|
7706
|
+
async screenApi(address, start) {
|
|
7707
|
+
try {
|
|
7708
|
+
const response = await fetch(
|
|
7709
|
+
`${this.apiBaseUrl}/address/${address}`,
|
|
7710
|
+
{
|
|
7711
|
+
method: "GET",
|
|
7712
|
+
headers: {
|
|
7713
|
+
"X-API-Key": this.apiKey,
|
|
7714
|
+
"Accept": "application/json"
|
|
7715
|
+
}
|
|
7716
|
+
}
|
|
7717
|
+
);
|
|
7718
|
+
if (!response.ok) {
|
|
7719
|
+
return this.errorResult(
|
|
7720
|
+
start,
|
|
7721
|
+
`Chainalysis API error: ${response.status} ${response.statusText}`
|
|
7722
|
+
);
|
|
7723
|
+
}
|
|
7724
|
+
const data = await response.json();
|
|
7725
|
+
const identifications = data.identifications ?? [];
|
|
7726
|
+
const matches = identifications.map((id) => ({
|
|
7727
|
+
list: "OFAC_SDN",
|
|
7728
|
+
matchType: "exact_address",
|
|
7729
|
+
similarity: 1,
|
|
7730
|
+
matchedValue: address,
|
|
7731
|
+
entityStatus: "active",
|
|
7732
|
+
entityName: id.name,
|
|
7733
|
+
program: id.category
|
|
7734
|
+
}));
|
|
7735
|
+
return {
|
|
7736
|
+
providerId: this.id,
|
|
7737
|
+
hit: matches.length > 0,
|
|
7738
|
+
matches,
|
|
7739
|
+
listsChecked: this.lists,
|
|
7740
|
+
entriesSearched: 1,
|
|
7741
|
+
durationMs: Date.now() - start
|
|
7742
|
+
};
|
|
7743
|
+
} catch (err) {
|
|
7744
|
+
return this.errorResult(
|
|
7745
|
+
start,
|
|
7746
|
+
`Chainalysis API error: ${err instanceof Error ? err.message : String(err)}`
|
|
7747
|
+
);
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
errorResult(start, error) {
|
|
7751
|
+
return {
|
|
7752
|
+
providerId: this.id,
|
|
7753
|
+
hit: false,
|
|
7754
|
+
matches: [],
|
|
7755
|
+
listsChecked: this.lists,
|
|
7756
|
+
entriesSearched: 0,
|
|
7757
|
+
durationMs: Date.now() - start,
|
|
7758
|
+
error
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
};
|
|
7762
|
+
|
|
7763
|
+
// src/integrations/viem-interceptor.ts
|
|
7764
|
+
var ViemComplianceError = class extends Error {
|
|
7765
|
+
result;
|
|
7766
|
+
from;
|
|
7767
|
+
to;
|
|
7768
|
+
amount;
|
|
7769
|
+
constructor(message, result, details) {
|
|
7770
|
+
super(message);
|
|
7771
|
+
this.name = "ViemComplianceError";
|
|
7772
|
+
this.result = result;
|
|
7773
|
+
this.from = details.from;
|
|
7774
|
+
this.to = details.to;
|
|
7775
|
+
this.amount = details.amount;
|
|
7776
|
+
}
|
|
7777
|
+
};
|
|
7778
|
+
function withKontextCompliance(client, kontext, options) {
|
|
7779
|
+
const config = kontext.getConfig();
|
|
7780
|
+
const agentId = options?.agentId ?? config.agentId ?? "viem-agent";
|
|
7781
|
+
const mode = options?.mode ?? config.interceptorMode ?? "post-send";
|
|
7782
|
+
const sessionId = options?.sessionId;
|
|
7783
|
+
const metadata = options?.metadata;
|
|
7784
|
+
const onVerify = options?.onVerify;
|
|
7785
|
+
const onError = options?.onError;
|
|
7786
|
+
const allowedTokens = options?.tokens ? new Set(options.tokens) : config.policy?.allowedTokens ? new Set(config.policy.allowedTokens) : null;
|
|
7787
|
+
const allowedChains = options?.chains ? new Set(options.chains) : null;
|
|
7788
|
+
const allowedContracts = /* @__PURE__ */ new Set();
|
|
7789
|
+
for (const [address, info] of Object.entries(STABLECOIN_CONTRACTS)) {
|
|
7790
|
+
if (allowedTokens && !allowedTokens.has(info.token)) continue;
|
|
7791
|
+
if (allowedChains && !allowedChains.has(info.chain)) continue;
|
|
7792
|
+
allowedContracts.add(address);
|
|
7793
|
+
}
|
|
7794
|
+
const monitor = kontext.getWalletMonitor?.() ?? null;
|
|
7795
|
+
return client.extend((baseClient) => ({
|
|
7796
|
+
async sendTransaction(params) {
|
|
7797
|
+
const target = params.to?.toLowerCase();
|
|
7798
|
+
if (!target || !allowedContracts.has(target)) {
|
|
7799
|
+
return baseClient.sendTransaction(params);
|
|
7800
|
+
}
|
|
7801
|
+
const decoded = params.data ? decodeTransferCalldata(params.data) : null;
|
|
7802
|
+
if (!decoded) {
|
|
7803
|
+
return baseClient.sendTransaction(params);
|
|
7804
|
+
}
|
|
7805
|
+
const contractInfo = STABLECOIN_CONTRACTS[target];
|
|
7806
|
+
const verifyInput = buildVerifyInput(
|
|
7807
|
+
decoded,
|
|
7808
|
+
contractInfo,
|
|
7809
|
+
params,
|
|
7810
|
+
baseClient,
|
|
7811
|
+
agentId,
|
|
7812
|
+
sessionId,
|
|
7813
|
+
metadata
|
|
7814
|
+
);
|
|
7815
|
+
if (mode === "pre-send" || mode === "both") {
|
|
7816
|
+
await runPreSendScreen(kontext, verifyInput);
|
|
7817
|
+
}
|
|
7818
|
+
const txHash = await baseClient.sendTransaction(params);
|
|
7819
|
+
if (mode === "post-send" || mode === "both") {
|
|
7820
|
+
monitor?.markVerified(txHash);
|
|
7821
|
+
runPostSendVerify(kontext, { ...verifyInput, txHash }, txHash, onVerify, onError);
|
|
7822
|
+
}
|
|
7823
|
+
return txHash;
|
|
7824
|
+
},
|
|
7825
|
+
async writeContract(params) {
|
|
7826
|
+
if (!baseClient.writeContract) {
|
|
7827
|
+
throw new Error("writeContract not available on this client");
|
|
7828
|
+
}
|
|
7829
|
+
const target = params.address?.toLowerCase();
|
|
7830
|
+
if (!target || !allowedContracts.has(target)) {
|
|
7831
|
+
return baseClient.writeContract(params);
|
|
7832
|
+
}
|
|
7833
|
+
const fn = params.functionName;
|
|
7834
|
+
if (fn !== "transfer" && fn !== "transferFrom") {
|
|
7835
|
+
return baseClient.writeContract(params);
|
|
7836
|
+
}
|
|
7837
|
+
const decoded = decodeWriteContractArgs(fn, params.args);
|
|
7838
|
+
if (!decoded) {
|
|
7839
|
+
return baseClient.writeContract(params);
|
|
7840
|
+
}
|
|
7841
|
+
const contractInfo = STABLECOIN_CONTRACTS[target];
|
|
7842
|
+
const verifyInput = buildVerifyInput(
|
|
7843
|
+
decoded,
|
|
7844
|
+
contractInfo,
|
|
7845
|
+
params,
|
|
7846
|
+
baseClient,
|
|
7847
|
+
agentId,
|
|
7848
|
+
sessionId,
|
|
7849
|
+
metadata
|
|
7850
|
+
);
|
|
7851
|
+
if (mode === "pre-send" || mode === "both") {
|
|
7852
|
+
await runPreSendScreen(kontext, verifyInput);
|
|
7853
|
+
}
|
|
7854
|
+
const txHash = await baseClient.writeContract(params);
|
|
7855
|
+
if (mode === "post-send" || mode === "both") {
|
|
7856
|
+
monitor?.markVerified(txHash);
|
|
7857
|
+
runPostSendVerify(kontext, { ...verifyInput, txHash }, txHash, onVerify, onError);
|
|
7858
|
+
}
|
|
7859
|
+
return txHash;
|
|
7860
|
+
}
|
|
7861
|
+
}));
|
|
7862
|
+
}
|
|
7863
|
+
function buildVerifyInput(decoded, contractInfo, params, client, agentId, sessionId, metadata) {
|
|
7864
|
+
const chain = client.chain?.id ? CHAIN_ID_MAP[client.chain.id] ?? contractInfo.chain : contractInfo.chain;
|
|
7865
|
+
const from = (decoded.from ?? params.account?.address ?? client.account?.address ?? params.from ?? "").toLowerCase();
|
|
7866
|
+
return {
|
|
7867
|
+
txHash: "",
|
|
7868
|
+
chain,
|
|
7869
|
+
amount: formatTokenAmount2(decoded.amount, contractInfo.decimals),
|
|
7870
|
+
token: contractInfo.token,
|
|
7871
|
+
from,
|
|
7872
|
+
to: decoded.to.toLowerCase(),
|
|
7873
|
+
agentId,
|
|
7874
|
+
sessionId,
|
|
7875
|
+
metadata: {
|
|
7876
|
+
...metadata,
|
|
7877
|
+
source: "viem-auto-instrumentation",
|
|
7878
|
+
contractAddress: params.to ?? params.address
|
|
7879
|
+
}
|
|
7880
|
+
};
|
|
7881
|
+
}
|
|
7882
|
+
function runPostSendVerify(kontext, input, txHash, onVerify, onError) {
|
|
7883
|
+
kontext.verify(input).then(
|
|
7884
|
+
(result) => {
|
|
7885
|
+
if (onVerify) {
|
|
7886
|
+
try {
|
|
7887
|
+
const p = onVerify(result, txHash);
|
|
7888
|
+
if (p && typeof p.catch === "function") {
|
|
7889
|
+
p.catch(() => {
|
|
7890
|
+
});
|
|
7891
|
+
}
|
|
7892
|
+
} catch {
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
},
|
|
7896
|
+
(error) => {
|
|
7897
|
+
if (onError) {
|
|
7898
|
+
try {
|
|
7899
|
+
const p = onError(error, txHash);
|
|
7900
|
+
if (p && typeof p.catch === "function") {
|
|
7901
|
+
p.catch(() => {
|
|
7902
|
+
});
|
|
7903
|
+
}
|
|
7904
|
+
} catch {
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
}
|
|
7908
|
+
);
|
|
7909
|
+
}
|
|
7910
|
+
async function runPreSendScreen(kontext, input) {
|
|
7911
|
+
const result = await kontext.verify({ ...input, txHash: "pre-screening" });
|
|
7912
|
+
if (!result.compliant) {
|
|
7913
|
+
throw new ViemComplianceError(
|
|
7914
|
+
`Transaction blocked: ${result.recommendations?.[0] ?? "compliance check failed"}`,
|
|
7915
|
+
result,
|
|
7916
|
+
{ from: input.from, to: input.to, amount: input.amount }
|
|
7917
|
+
);
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
function decodeTransferCalldata(data) {
|
|
7921
|
+
if (!data || data.length < 10) return null;
|
|
7922
|
+
const selector = data.slice(0, 10).toLowerCase();
|
|
7923
|
+
if (selector === TRANSFER_SELECTOR && data.length >= 138) {
|
|
7924
|
+
const to = "0x" + data.slice(34, 74);
|
|
7925
|
+
const amount = BigInt("0x" + data.slice(74, 138));
|
|
7926
|
+
return { to, amount };
|
|
7927
|
+
}
|
|
7928
|
+
if (selector === TRANSFER_FROM_SELECTOR && data.length >= 202) {
|
|
7929
|
+
const from = "0x" + data.slice(34, 74);
|
|
7930
|
+
const to = "0x" + data.slice(98, 138);
|
|
7931
|
+
const amount = BigInt("0x" + data.slice(138, 202));
|
|
7932
|
+
return { from, to, amount };
|
|
7933
|
+
}
|
|
7934
|
+
return null;
|
|
7935
|
+
}
|
|
7936
|
+
function decodeWriteContractArgs(functionName, args) {
|
|
7937
|
+
if (!args || !Array.isArray(args)) return null;
|
|
7938
|
+
if (functionName === "transfer" && args.length >= 2) {
|
|
7939
|
+
return { to: String(args[0]), amount: BigInt(args[1]) };
|
|
7940
|
+
}
|
|
7941
|
+
if (functionName === "transferFrom" && args.length >= 3) {
|
|
7942
|
+
return { from: String(args[0]), to: String(args[1]), amount: BigInt(args[2]) };
|
|
7943
|
+
}
|
|
7944
|
+
return null;
|
|
7945
|
+
}
|
|
7946
|
+
function formatTokenAmount2(amount, decimals) {
|
|
7947
|
+
const divisor = BigInt(10 ** decimals);
|
|
7948
|
+
const whole = amount / divisor;
|
|
7949
|
+
const fraction = amount % divisor;
|
|
7950
|
+
if (fraction === 0n) return whole.toString();
|
|
7951
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
7952
|
+
return `${whole}.${fractionStr}`;
|
|
7953
|
+
}
|
|
7954
|
+
|
|
7955
|
+
exports.AgentIdentityRegistry = AgentIdentityRegistry;
|
|
4203
7956
|
exports.AnomalyDetector = AnomalyDetector;
|
|
7957
|
+
exports.BehavioralFingerprinter = BehavioralFingerprinter;
|
|
7958
|
+
exports.CHAIN_ID_MAP = CHAIN_ID_MAP;
|
|
7959
|
+
exports.CURRENCY_REQUIRED_LISTS = CURRENCY_REQUIRED_LISTS;
|
|
7960
|
+
exports.ChainalysisFreeAPIProvider = ChainalysisFreeAPIProvider;
|
|
7961
|
+
exports.ChainalysisOracleProvider = ChainalysisOracleProvider;
|
|
4204
7962
|
exports.ConsoleExporter = ConsoleExporter;
|
|
7963
|
+
exports.CrossSessionLinker = CrossSessionLinker;
|
|
4205
7964
|
exports.DigestChain = DigestChain;
|
|
4206
7965
|
exports.FeatureFlagManager = FeatureFlagManager;
|
|
4207
7966
|
exports.FileStorage = FileStorage;
|
|
4208
7967
|
exports.JsonFileExporter = JsonFileExporter;
|
|
7968
|
+
exports.KYAConfidenceScorer = KYAConfidenceScorer;
|
|
4209
7969
|
exports.Kontext = Kontext;
|
|
4210
7970
|
exports.KontextError = KontextError;
|
|
4211
7971
|
exports.KontextErrorCode = KontextErrorCode;
|
|
4212
7972
|
exports.MemoryStorage = MemoryStorage;
|
|
4213
7973
|
exports.NoopExporter = NoopExporter;
|
|
7974
|
+
exports.OFACAddressProvider = OFACAddressProvider;
|
|
7975
|
+
exports.OFACEntityProvider = OFACEntityProvider;
|
|
7976
|
+
exports.OpenSanctionsLocalProvider = OpenSanctionsLocalProvider;
|
|
7977
|
+
exports.OpenSanctionsProvider = OpenSanctionsProvider;
|
|
4214
7978
|
exports.PLAN_LIMITS = PLAN_LIMITS;
|
|
4215
7979
|
exports.PaymentCompliance = PaymentCompliance;
|
|
4216
7980
|
exports.PlanManager = PlanManager;
|
|
7981
|
+
exports.ProvenanceManager = ProvenanceManager;
|
|
7982
|
+
exports.STABLECOIN_CONTRACTS = STABLECOIN_CONTRACTS;
|
|
7983
|
+
exports.ScreeningAggregator = ScreeningAggregator;
|
|
7984
|
+
exports.TOKEN_REQUIRED_LISTS = TOKEN_REQUIRED_LISTS;
|
|
4217
7985
|
exports.TrustScorer = TrustScorer;
|
|
7986
|
+
exports.UKOFSIProvider = UKOFSIProvider;
|
|
4218
7987
|
exports.UsdcCompliance = UsdcCompliance;
|
|
7988
|
+
exports.ViemComplianceError = ViemComplianceError;
|
|
7989
|
+
exports.WalletClusterer = WalletClusterer;
|
|
7990
|
+
exports.WalletMonitor = WalletMonitor;
|
|
7991
|
+
exports.anchorDigest = anchorDigest;
|
|
7992
|
+
exports.encodeERC8021Suffix = encodeERC8021Suffix;
|
|
7993
|
+
exports.exchangeAttestation = exchangeAttestation;
|
|
7994
|
+
exports.fetchAgentCard = fetchAgentCard;
|
|
7995
|
+
exports.fetchTransactionAttribution = fetchTransactionAttribution;
|
|
7996
|
+
exports.getAnchor = getAnchor;
|
|
7997
|
+
exports.getRequiredLists = getRequiredLists;
|
|
7998
|
+
exports.isBlockchainAddress = isBlockchainAddress;
|
|
4219
7999
|
exports.isCryptoTransaction = isCryptoTransaction;
|
|
4220
8000
|
exports.isFeatureAvailable = isFeatureAvailable;
|
|
8001
|
+
exports.loadConfigFile = loadConfigFile;
|
|
8002
|
+
exports.parseERC8021Suffix = parseERC8021Suffix;
|
|
8003
|
+
exports.providerSupportsQuery = providerSupportsQuery;
|
|
4221
8004
|
exports.requirePlan = requirePlan;
|
|
8005
|
+
exports.verifyAnchor = verifyAnchor;
|
|
4222
8006
|
exports.verifyExportedChain = verifyExportedChain;
|
|
8007
|
+
exports.withKontextCompliance = withKontextCompliance;
|
|
4223
8008
|
//# sourceMappingURL=index.js.map
|
|
4224
8009
|
//# sourceMappingURL=index.js.map
|