kontext-sdk 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -18,6 +18,90 @@ var __export = (target, all) => {
18
18
  __defProp(target, name, { get: all[name], enumerable: true });
19
19
  };
20
20
 
21
+ // src/integrations/erc8021.ts
22
+ var erc8021_exports = {};
23
+ __export(erc8021_exports, {
24
+ KONTEXT_BUILDER_CODE: () => KONTEXT_BUILDER_CODE,
25
+ encodeERC8021Suffix: () => encodeERC8021Suffix,
26
+ fetchTransactionAttribution: () => fetchTransactionAttribution,
27
+ parseERC8021Suffix: () => parseERC8021Suffix
28
+ });
29
+ function encodeERC8021Suffix(codes) {
30
+ if (codes.length === 0) {
31
+ throw new Error("ERC-8021: at least one builder code is required");
32
+ }
33
+ const codesStr = codes.join(",");
34
+ let codesHex = "";
35
+ for (let i = 0; i < codesStr.length; i++) {
36
+ codesHex += codesStr.charCodeAt(i).toString(16).padStart(2, "0");
37
+ }
38
+ const codesLength = codesStr.length;
39
+ if (codesLength > 255) {
40
+ throw new Error("ERC-8021: combined codes length exceeds 255 bytes");
41
+ }
42
+ const codesLengthHex = codesLength.toString(16).padStart(2, "0");
43
+ const schemaId = "00";
44
+ return codesLengthHex + codesHex + schemaId + ERC_8021_MARKER;
45
+ }
46
+ function parseERC8021Suffix(calldata) {
47
+ const clean = calldata.startsWith("0x") ? calldata.slice(2) : calldata;
48
+ if (clean.length < 38) return null;
49
+ const markerStart = clean.length - 32;
50
+ const marker = clean.slice(markerStart);
51
+ if (marker !== ERC_8021_MARKER) return null;
52
+ const schemaIdStart = markerStart - 2;
53
+ const schemaId = parseInt(clean.slice(schemaIdStart, markerStart), 16);
54
+ if (isNaN(schemaId)) return null;
55
+ let codesLength = 0;
56
+ let codesLengthPos = 0;
57
+ for (let L = 1; L <= 255; L++) {
58
+ const candidatePos = schemaIdStart - L * 2 - 2;
59
+ if (candidatePos < 0) break;
60
+ const candidate = parseInt(clean.slice(candidatePos, candidatePos + 2), 16);
61
+ if (candidate === L) {
62
+ codesLength = L;
63
+ codesLengthPos = candidatePos;
64
+ break;
65
+ }
66
+ }
67
+ if (codesLength === 0) return null;
68
+ const codesHex = clean.slice(codesLengthPos + 2, codesLengthPos + 2 + codesLength * 2);
69
+ let codesStr = "";
70
+ for (let i = 0; i < codesHex.length; i += 2) {
71
+ codesStr += String.fromCharCode(parseInt(codesHex.slice(i, i + 2), 16));
72
+ }
73
+ const codes = codesStr.split(",").filter((c) => c.length > 0);
74
+ if (codes.length === 0) return null;
75
+ const rawSuffix = "0x" + clean.slice(codesLengthPos);
76
+ return { codes, schemaId, rawSuffix };
77
+ }
78
+ async function fetchTransactionAttribution(rpcUrl, txHash) {
79
+ try {
80
+ const res = await fetch(rpcUrl, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({
84
+ jsonrpc: "2.0",
85
+ id: 1,
86
+ method: "eth_getTransactionByHash",
87
+ params: [txHash]
88
+ })
89
+ });
90
+ const json = await res.json();
91
+ if (json.error || !json.result?.input) return null;
92
+ return parseERC8021Suffix(json.result.input);
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+ var ERC_8021_MARKER, KONTEXT_BUILDER_CODE;
98
+ var init_erc8021 = __esm({
99
+ "src/integrations/erc8021.ts"() {
100
+ ERC_8021_MARKER = "80218021802180218021802180218021";
101
+ KONTEXT_BUILDER_CODE = "kontext";
102
+ }
103
+ });
104
+
21
105
  // src/onchain.ts
22
106
  var onchain_exports = {};
23
107
  __export(onchain_exports, {
@@ -115,12 +199,18 @@ async function anchorDigest(config, digest, projectId) {
115
199
  outputs: []
116
200
  }
117
201
  ];
118
- const txHash = await client.writeContract({
119
- address: config.contractAddress,
202
+ const { encodeERC8021Suffix: encodeERC8021Suffix2, KONTEXT_BUILDER_CODE: KONTEXT_BUILDER_CODE2 } = await Promise.resolve().then(() => (init_erc8021(), erc8021_exports));
203
+ const calldata = viem.encodeFunctionData({
120
204
  abi,
121
205
  functionName: "anchor",
122
206
  args: [digestBytes32, projectHash]
123
207
  });
208
+ const builderCode = config.builderCode ?? KONTEXT_BUILDER_CODE2;
209
+ const suffix = encodeERC8021Suffix2([builderCode]);
210
+ const txHash = await client.sendTransaction({
211
+ to: config.contractAddress,
212
+ data: calldata + suffix
213
+ });
124
214
  const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
125
215
  return {
126
216
  digest,
@@ -1129,13 +1219,17 @@ var STORAGE_KEYS = {
1129
1219
  actions: "kontext:actions",
1130
1220
  transactions: "kontext:transactions",
1131
1221
  tasks: "kontext:tasks",
1132
- anomalies: "kontext:anomalies"
1222
+ anomalies: "kontext:anomalies",
1223
+ sessions: "kontext:sessions",
1224
+ checkpoints: "kontext:checkpoints"
1133
1225
  };
1134
1226
  var KontextStore = class {
1135
1227
  actions = [];
1136
1228
  transactions = [];
1137
1229
  tasks = /* @__PURE__ */ new Map();
1138
1230
  anomalies = [];
1231
+ sessions = /* @__PURE__ */ new Map();
1232
+ checkpoints = /* @__PURE__ */ new Map();
1139
1233
  storageAdapter = null;
1140
1234
  maxEntries;
1141
1235
  constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
@@ -1168,11 +1262,15 @@ var KontextStore = class {
1168
1262
  const transactionsSnapshot = [...this.transactions];
1169
1263
  const tasksSnapshot = Array.from(this.tasks.entries());
1170
1264
  const anomaliesSnapshot = [...this.anomalies];
1265
+ const sessionsSnapshot = Array.from(this.sessions.entries());
1266
+ const checkpointsSnapshot = Array.from(this.checkpoints.entries());
1171
1267
  await Promise.all([
1172
1268
  this.storageAdapter.save(STORAGE_KEYS.actions, actionsSnapshot),
1173
1269
  this.storageAdapter.save(STORAGE_KEYS.transactions, transactionsSnapshot),
1174
1270
  this.storageAdapter.save(STORAGE_KEYS.tasks, tasksSnapshot),
1175
- this.storageAdapter.save(STORAGE_KEYS.anomalies, anomaliesSnapshot)
1271
+ this.storageAdapter.save(STORAGE_KEYS.anomalies, anomaliesSnapshot),
1272
+ this.storageAdapter.save(STORAGE_KEYS.sessions, sessionsSnapshot),
1273
+ this.storageAdapter.save(STORAGE_KEYS.checkpoints, checkpointsSnapshot)
1176
1274
  ]);
1177
1275
  }
1178
1276
  /**
@@ -1182,11 +1280,13 @@ var KontextStore = class {
1182
1280
  */
1183
1281
  async restore() {
1184
1282
  if (!this.storageAdapter) return;
1185
- const [actions, transactions, tasksEntries, anomalies] = await Promise.all([
1283
+ const [actions, transactions, tasksEntries, anomalies, sessionsEntries, checkpointsEntries] = await Promise.all([
1186
1284
  this.storageAdapter.load(STORAGE_KEYS.actions),
1187
1285
  this.storageAdapter.load(STORAGE_KEYS.transactions),
1188
1286
  this.storageAdapter.load(STORAGE_KEYS.tasks),
1189
- this.storageAdapter.load(STORAGE_KEYS.anomalies)
1287
+ this.storageAdapter.load(STORAGE_KEYS.anomalies),
1288
+ this.storageAdapter.load(STORAGE_KEYS.sessions),
1289
+ this.storageAdapter.load(STORAGE_KEYS.checkpoints)
1190
1290
  ]);
1191
1291
  if (Array.isArray(actions)) {
1192
1292
  this.actions = actions;
@@ -1200,6 +1300,12 @@ var KontextStore = class {
1200
1300
  if (Array.isArray(anomalies)) {
1201
1301
  this.anomalies = anomalies;
1202
1302
  }
1303
+ if (Array.isArray(sessionsEntries)) {
1304
+ this.sessions = new Map(sessionsEntries);
1305
+ }
1306
+ if (Array.isArray(checkpointsEntries)) {
1307
+ this.checkpoints = new Map(checkpointsEntries);
1308
+ }
1203
1309
  }
1204
1310
  // --------------------------------------------------------------------------
1205
1311
  // Actions
@@ -1299,6 +1405,60 @@ var KontextStore = class {
1299
1405
  return this.anomalies.filter(predicate);
1300
1406
  }
1301
1407
  // --------------------------------------------------------------------------
1408
+ // Sessions (Provenance Layer 1)
1409
+ // --------------------------------------------------------------------------
1410
+ /** Store a session. */
1411
+ addSession(session) {
1412
+ this.sessions.set(session.sessionId, session);
1413
+ }
1414
+ /** Retrieve a session by ID. */
1415
+ getSession(sessionId) {
1416
+ return this.sessions.get(sessionId);
1417
+ }
1418
+ /** Update a session. */
1419
+ updateSession(sessionId, updates) {
1420
+ const existing = this.sessions.get(sessionId);
1421
+ if (!existing) return void 0;
1422
+ const updated = { ...existing, ...updates };
1423
+ this.sessions.set(sessionId, updated);
1424
+ return updated;
1425
+ }
1426
+ /** Retrieve all sessions. */
1427
+ getSessions() {
1428
+ return Array.from(this.sessions.values());
1429
+ }
1430
+ /** Retrieve sessions filtered by a predicate. */
1431
+ querySessions(predicate) {
1432
+ return Array.from(this.sessions.values()).filter(predicate);
1433
+ }
1434
+ // --------------------------------------------------------------------------
1435
+ // Checkpoints (Provenance Layer 3)
1436
+ // --------------------------------------------------------------------------
1437
+ /** Store a checkpoint. */
1438
+ addCheckpoint(checkpoint) {
1439
+ this.checkpoints.set(checkpoint.id, checkpoint);
1440
+ }
1441
+ /** Retrieve a checkpoint by ID. */
1442
+ getCheckpoint(checkpointId) {
1443
+ return this.checkpoints.get(checkpointId);
1444
+ }
1445
+ /** Update a checkpoint. */
1446
+ updateCheckpoint(checkpointId, updates) {
1447
+ const existing = this.checkpoints.get(checkpointId);
1448
+ if (!existing) return void 0;
1449
+ const updated = { ...existing, ...updates };
1450
+ this.checkpoints.set(checkpointId, updated);
1451
+ return updated;
1452
+ }
1453
+ /** Retrieve all checkpoints. */
1454
+ getCheckpoints() {
1455
+ return Array.from(this.checkpoints.values());
1456
+ }
1457
+ /** Retrieve checkpoints filtered by a predicate. */
1458
+ queryCheckpoints(predicate) {
1459
+ return Array.from(this.checkpoints.values()).filter(predicate);
1460
+ }
1461
+ // --------------------------------------------------------------------------
1302
1462
  // Utilities
1303
1463
  // --------------------------------------------------------------------------
1304
1464
  /** Get total record counts across all stores. */
@@ -1307,7 +1467,9 @@ var KontextStore = class {
1307
1467
  actions: this.actions.length,
1308
1468
  transactions: this.transactions.length,
1309
1469
  tasks: this.tasks.size,
1310
- anomalies: this.anomalies.length
1470
+ anomalies: this.anomalies.length,
1471
+ sessions: this.sessions.size,
1472
+ checkpoints: this.checkpoints.size
1311
1473
  };
1312
1474
  }
1313
1475
  /** Clear all stored data. Useful for testing. */
@@ -1316,6 +1478,8 @@ var KontextStore = class {
1316
1478
  this.transactions = [];
1317
1479
  this.tasks.clear();
1318
1480
  this.anomalies = [];
1481
+ this.sessions.clear();
1482
+ this.checkpoints.clear();
1319
1483
  }
1320
1484
  };
1321
1485
  var DIGEST_EXCLUDED_FIELDS = /* @__PURE__ */ new Set(["digest", "priorDigest"]);
@@ -2333,17 +2497,9 @@ var AuditExporter = class {
2333
2497
  return sections.join("\n\n");
2334
2498
  }
2335
2499
  };
2336
- var USDC_CONTRACTS = {
2337
- ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2338
- base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2339
- polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
2340
- arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
2341
- optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
2342
- // Arc (Circle's stablecoin-native blockchain) -- placeholder address, update when Arc mainnet launches
2343
- arc: "0xa0c0000000000000000000000000000000000001"
2344
- };
2345
- var SANCTIONED_ADDRESSES = [
2346
- // --- ACTIVELY SANCTIONED (SDN) ---
2500
+
2501
+ // src/integrations/data/ofac-addresses.ts
2502
+ var OFAC_SDN_ACTIVE_ADDRESSES = [
2347
2503
  // Lazarus Group / DPRK (Ronin Bridge hack)
2348
2504
  "0x098B716B8Aaf21512996dC57EB0615e2383E2f96",
2349
2505
  "0xa0e1c89Ef1a489c9C7dE96311eD5Ce5D32c20E4B",
@@ -2367,8 +2523,9 @@ var SANCTIONED_ADDRESSES = [
2367
2523
  "0x931546D9e66836AbF687d2bc64B30407bAc8C568",
2368
2524
  "0x43fa21d92141BA9db43052492E0DeEE5aa5f0A93",
2369
2525
  // Zedcex / Zedxion (IRGC-linked, sanctioned June 2024)
2370
- "0xaeAAc358560e11f52454D997AAFF2c5731B6f8a6",
2371
- // --- DELISTED (formerly sanctioned, retained for backward compat) ---
2526
+ "0xaeAAc358560e11f52454D997AAFF2c5731B6f8a6"
2527
+ ];
2528
+ var OFAC_SDN_DELISTED_ADDRESSES = [
2372
2529
  // Tornado Cash contracts (sanctioned Aug 2022, DELISTED March 21, 2025)
2373
2530
  "0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b",
2374
2531
  "0xd96f2B1c14Db8458374d9Aca76E26c3D18364307",
@@ -2390,6 +2547,20 @@ var SANCTIONED_ADDRESSES = [
2390
2547
  "0x722122dF12D4e14e13Ac3b6895a86e84145b6967"
2391
2548
  // Tornado Cash / Sinbad.io
2392
2549
  ];
2550
+ var OFAC_SDN_ADDRESSES = [
2551
+ ...OFAC_SDN_ACTIVE_ADDRESSES,
2552
+ ...OFAC_SDN_DELISTED_ADDRESSES
2553
+ ];
2554
+ var USDC_CONTRACTS = {
2555
+ ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2556
+ base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2557
+ polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
2558
+ arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
2559
+ optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
2560
+ // Arc (Circle's stablecoin-native blockchain) -- placeholder address, update when Arc mainnet launches
2561
+ arc: "0xa0c0000000000000000000000000000000000001"
2562
+ };
2563
+ var SANCTIONED_ADDRESSES = [...OFAC_SDN_ADDRESSES];
2393
2564
  var SANCTIONED_SET = new Set(
2394
2565
  SANCTIONED_ADDRESSES.map((addr) => addr.toLowerCase())
2395
2566
  );
@@ -2885,11 +3056,301 @@ var PaymentCompliance = class _PaymentCompliance {
2885
3056
  }
2886
3057
  };
2887
3058
 
3059
+ // src/integrations/screening-provider.ts
3060
+ var ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
3061
+ function isBlockchainAddress(query) {
3062
+ return ETH_ADDRESS_RE.test(query);
3063
+ }
3064
+ function providerSupportsQuery(provider, query) {
3065
+ const isAddr = isBlockchainAddress(query);
3066
+ if (isAddr) {
3067
+ return provider.queryTypes.includes("address") || provider.queryTypes.includes("both");
3068
+ }
3069
+ return provider.queryTypes.includes("entity_name") || provider.queryTypes.includes("both");
3070
+ }
3071
+ var TOKEN_REQUIRED_LISTS = {
3072
+ USDC: ["OFAC_SDN"],
3073
+ EURC: ["EU_CONSOLIDATED"],
3074
+ USDT: ["OFAC_SDN", "EU_CONSOLIDATED"],
3075
+ DAI: ["OFAC_SDN", "EU_CONSOLIDATED"],
3076
+ USDP: ["OFAC_SDN"],
3077
+ USDG: ["OFAC_SDN"]
3078
+ };
3079
+ var CURRENCY_REQUIRED_LISTS = {
3080
+ USD: ["OFAC_SDN"],
3081
+ EUR: ["EU_CONSOLIDATED"],
3082
+ GBP: ["UK_OFSI"],
3083
+ AED: ["UAE_LOCAL", "UN_SECURITY_COUNCIL"],
3084
+ INR: ["UN_SECURITY_COUNCIL", "INDIA_DOMESTIC"],
3085
+ SGD: ["MAS_TFS", "UN_SECURITY_COUNCIL"],
3086
+ CNY: ["UN_SECURITY_COUNCIL"],
3087
+ CNH: ["UN_SECURITY_COUNCIL"],
3088
+ HKD: ["HK_UNSO", "UN_SECURITY_COUNCIL"],
3089
+ NZD: ["UN_SECURITY_COUNCIL", "NZ_DESIGNATED"],
3090
+ KRW: ["UN_SECURITY_COUNCIL", "KOFIU_DOMESTIC"],
3091
+ MYR: ["BNM_DOMESTIC", "UN_SECURITY_COUNCIL"],
3092
+ THB: ["UN_SECURITY_COUNCIL", "AMLO_DOMESTIC"]
3093
+ };
3094
+ var DEFAULT_REQUIRED_LISTS = [
3095
+ "OFAC_SDN",
3096
+ "EU_CONSOLIDATED",
3097
+ "UN_SECURITY_COUNCIL"
3098
+ ];
3099
+ function getRequiredLists(context) {
3100
+ if (!context) return DEFAULT_REQUIRED_LISTS;
3101
+ if (context.token && typeof context.token === "string") {
3102
+ const tokenLists = TOKEN_REQUIRED_LISTS[context.token];
3103
+ if (tokenLists) return tokenLists;
3104
+ }
3105
+ if (context.currency && typeof context.currency === "string") {
3106
+ const currencyLists = CURRENCY_REQUIRED_LISTS[context.currency];
3107
+ if (currencyLists) return currencyLists;
3108
+ }
3109
+ return DEFAULT_REQUIRED_LISTS;
3110
+ }
3111
+
3112
+ // src/integrations/screening-aggregator.ts
3113
+ var ScreeningAggregator = class {
3114
+ providers;
3115
+ consensus;
3116
+ blocklistSet;
3117
+ allowlistSet;
3118
+ continueOnError;
3119
+ providerTimeoutMs;
3120
+ onEvent;
3121
+ constructor(config) {
3122
+ this.providers = config.providers;
3123
+ this.consensus = config.consensus ?? "ANY_MATCH";
3124
+ this.blocklistSet = new Set(
3125
+ (config.blocklist ?? []).map((s) => s.toLowerCase())
3126
+ );
3127
+ this.allowlistSet = new Set(
3128
+ (config.allowlist ?? []).map((s) => s.toLowerCase())
3129
+ );
3130
+ this.continueOnError = config.continueOnError ?? true;
3131
+ this.providerTimeoutMs = config.providerTimeoutMs;
3132
+ this.onEvent = config.onEvent;
3133
+ }
3134
+ /**
3135
+ * Screen a single query (address or entity name).
3136
+ */
3137
+ async screen(query, context) {
3138
+ const start = Date.now();
3139
+ const queryLower = query.toLowerCase();
3140
+ const queryType = isBlockchainAddress(query) ? "address" : "entity_name";
3141
+ this.onEvent?.();
3142
+ if (this.allowlistSet.has(queryLower)) {
3143
+ return this.buildResult({
3144
+ queryType,
3145
+ start,
3146
+ allowlisted: true,
3147
+ context
3148
+ });
3149
+ }
3150
+ if (this.blocklistSet.has(queryLower)) {
3151
+ return this.buildResult({
3152
+ queryType,
3153
+ start,
3154
+ blocklisted: true,
3155
+ context
3156
+ });
3157
+ }
3158
+ const compatibleProviders = this.providers.filter(
3159
+ (p) => p.isAvailable() && providerSupportsQuery(p, query)
3160
+ );
3161
+ if (compatibleProviders.length === 0) {
3162
+ return this.buildResult({
3163
+ queryType,
3164
+ start,
3165
+ providerResults: [],
3166
+ context
3167
+ });
3168
+ }
3169
+ const results = [];
3170
+ const errors = [];
3171
+ const promises = compatibleProviders.map(async (provider) => {
3172
+ try {
3173
+ const result = await this.runWithTimeout(provider, query, context);
3174
+ return { type: "success", result };
3175
+ } catch (err) {
3176
+ const errorMsg = err instanceof Error ? err.message : String(err);
3177
+ return { type: "error", providerId: provider.id, error: errorMsg };
3178
+ }
3179
+ });
3180
+ const settled = await Promise.all(promises);
3181
+ for (const outcome of settled) {
3182
+ if (outcome.type === "success") {
3183
+ results.push(outcome.result);
3184
+ } else {
3185
+ if (!this.continueOnError) {
3186
+ throw new Error(outcome.error);
3187
+ }
3188
+ errors.push({ providerId: outcome.providerId, error: outcome.error });
3189
+ }
3190
+ }
3191
+ return this.buildResult({
3192
+ queryType,
3193
+ start,
3194
+ providerResults: results,
3195
+ errors,
3196
+ context
3197
+ });
3198
+ }
3199
+ /**
3200
+ * Screen multiple queries in batch.
3201
+ */
3202
+ async screenBatch(queries, context) {
3203
+ const results = /* @__PURE__ */ new Map();
3204
+ const promises = queries.map(async (query) => {
3205
+ const result = await this.screen(query, context);
3206
+ return { query, result };
3207
+ });
3208
+ const settled = await Promise.all(promises);
3209
+ for (const { query, result } of settled) {
3210
+ results.set(query, result);
3211
+ }
3212
+ return results;
3213
+ }
3214
+ /**
3215
+ * Get available providers, optionally filtered by query type.
3216
+ */
3217
+ getAvailableProviders(queryType) {
3218
+ return this.providers.filter((p) => {
3219
+ if (!p.isAvailable()) return false;
3220
+ if (!queryType) return true;
3221
+ return p.queryTypes.includes(queryType) || p.queryTypes.includes("both");
3222
+ });
3223
+ }
3224
+ /**
3225
+ * Get the union of all sanctions lists covered by available providers.
3226
+ */
3227
+ getCoveredLists() {
3228
+ const lists = /* @__PURE__ */ new Set();
3229
+ for (const p of this.providers) {
3230
+ if (p.isAvailable()) {
3231
+ for (const list of p.lists) {
3232
+ lists.add(list);
3233
+ }
3234
+ }
3235
+ }
3236
+ return Array.from(lists);
3237
+ }
3238
+ // --------------------------------------------------------------------------
3239
+ // Private
3240
+ // --------------------------------------------------------------------------
3241
+ async runWithTimeout(provider, query, context) {
3242
+ if (!this.providerTimeoutMs) {
3243
+ return provider.screen(query, context);
3244
+ }
3245
+ return Promise.race([
3246
+ provider.screen(query, context),
3247
+ new Promise(
3248
+ (_, reject) => setTimeout(
3249
+ () => reject(new Error(`Provider ${provider.id} timed out after ${this.providerTimeoutMs}ms`)),
3250
+ this.providerTimeoutMs
3251
+ )
3252
+ )
3253
+ ]);
3254
+ }
3255
+ buildResult(opts) {
3256
+ const {
3257
+ queryType,
3258
+ start,
3259
+ providerResults = [],
3260
+ errors = [],
3261
+ blocklisted = false,
3262
+ allowlisted = false,
3263
+ context
3264
+ } = opts;
3265
+ if (blocklisted) {
3266
+ return {
3267
+ providerId: "aggregator",
3268
+ hit: true,
3269
+ matches: [],
3270
+ listsChecked: [],
3271
+ entriesSearched: 0,
3272
+ durationMs: Date.now() - start,
3273
+ queryType,
3274
+ totalProviders: 0,
3275
+ hitCount: 0,
3276
+ consensus: this.consensus,
3277
+ blocklisted: true,
3278
+ errors: [],
3279
+ uncoveredLists: [],
3280
+ providerResults: []
3281
+ };
3282
+ }
3283
+ if (allowlisted) {
3284
+ return {
3285
+ providerId: "aggregator",
3286
+ hit: false,
3287
+ matches: [],
3288
+ listsChecked: [],
3289
+ entriesSearched: 0,
3290
+ durationMs: Date.now() - start,
3291
+ queryType,
3292
+ totalProviders: 0,
3293
+ hitCount: 0,
3294
+ consensus: this.consensus,
3295
+ allowlisted: true,
3296
+ errors: [],
3297
+ uncoveredLists: [],
3298
+ providerResults: []
3299
+ };
3300
+ }
3301
+ const allMatches = [];
3302
+ const allListsChecked = /* @__PURE__ */ new Set();
3303
+ let totalEntries = 0;
3304
+ for (const r of providerResults) {
3305
+ allMatches.push(...r.matches);
3306
+ for (const list of r.listsChecked) {
3307
+ allListsChecked.add(list);
3308
+ }
3309
+ totalEntries += r.entriesSearched;
3310
+ }
3311
+ const hitCount = providerResults.filter((r) => r.hit).length;
3312
+ const totalProviders = providerResults.length;
3313
+ let hit;
3314
+ switch (this.consensus) {
3315
+ case "ALL_MATCH":
3316
+ hit = totalProviders > 0 && hitCount === totalProviders;
3317
+ break;
3318
+ case "MAJORITY":
3319
+ hit = totalProviders > 0 && hitCount > totalProviders / 2;
3320
+ break;
3321
+ case "ANY_MATCH":
3322
+ default:
3323
+ hit = hitCount > 0;
3324
+ break;
3325
+ }
3326
+ const requiredLists = getRequiredLists(context);
3327
+ const coveredLists = allListsChecked;
3328
+ const uncoveredLists = requiredLists.filter(
3329
+ (l) => !coveredLists.has(l)
3330
+ );
3331
+ return {
3332
+ providerId: "aggregator",
3333
+ hit,
3334
+ matches: allMatches,
3335
+ listsChecked: Array.from(allListsChecked),
3336
+ entriesSearched: totalEntries,
3337
+ durationMs: Date.now() - start,
3338
+ queryType,
3339
+ totalProviders,
3340
+ hitCount,
3341
+ consensus: this.consensus,
3342
+ errors,
3343
+ uncoveredLists,
3344
+ providerResults
3345
+ };
3346
+ }
3347
+ };
3348
+
2888
3349
  // src/plans.ts
2889
3350
  var PLAN_LIMITS = {
2890
3351
  free: 2e4,
2891
- pro: 1e5,
2892
- // per user/seat
3352
+ pro: Infinity,
3353
+ // usage-based: $2/1K events above 20K free
2893
3354
  enterprise: Infinity
2894
3355
  };
2895
3356
  var DEFAULT_WARNING_THRESHOLD = 0.8;
@@ -2936,10 +3397,7 @@ var PlanManager = class _PlanManager {
2936
3397
  }
2937
3398
  /** Get the event limit for the current plan (Pro is multiplied by seats) */
2938
3399
  getLimit() {
2939
- const base = PLAN_LIMITS[this.tier];
2940
- if (base === Infinity) return Infinity;
2941
- if (this.tier === "pro") return base * this.seats;
2942
- return base;
3400
+ return PLAN_LIMITS[this.tier];
2943
3401
  }
2944
3402
  /** Get the current number of seats */
2945
3403
  getSeats() {
@@ -3144,12 +3602,7 @@ var PlanManager = class _PlanManager {
3144
3602
  logLimitMessage() {
3145
3603
  if (this.tier === "free") {
3146
3604
  console.warn(
3147
- `You've reached the 20,000 event limit on the Free plan. Upgrade to Pro for 100K events/user/mo and full compliance features \u2192 ${this.upgradeUrl}`
3148
- );
3149
- } else if (this.tier === "pro") {
3150
- const effectiveLimit = (1e5 * this.seats).toLocaleString();
3151
- console.warn(
3152
- `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}`
3605
+ `You've reached the 20,000 event limit on the Free plan. Upgrade to Pro ($2/1K events above 20K free) \u2192 ${this.upgradeUrl}`
3153
3606
  );
3154
3607
  }
3155
3608
  }
@@ -3426,120 +3879,1653 @@ function extractDocumentId(resourceName) {
3426
3879
  const parts = resourceName.split("/");
3427
3880
  return parts[parts.length - 1] ?? resourceName;
3428
3881
  }
3429
-
3430
- // src/client.ts
3431
- var PLAN_STORAGE_KEY = "kontext:plan";
3432
- var Kontext = class _Kontext {
3433
- config;
3882
+ var ProvenanceManager = class {
3434
3883
  store;
3435
3884
  logger;
3436
- taskManager;
3437
- auditExporter;
3438
- mode;
3439
- planManager;
3440
- exporter;
3441
- featureFlagManager;
3442
- trustScorer;
3443
- anomalyDetector;
3444
- constructor(config) {
3445
- this.config = config;
3446
- this.mode = config.apiKey ? "cloud" : "local";
3447
- this.store = new KontextStore();
3448
- if (config.metadataSchema && typeof config.metadataSchema.parse !== "function") {
3449
- throw new KontextError(
3450
- "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
3451
- "metadataSchema must have a parse() method"
3452
- );
3453
- }
3454
- if (config.storage) {
3455
- this.store.setStorageAdapter(config.storage);
3456
- }
3457
- const planTier = config.plan ?? "free";
3458
- this.planManager = new PlanManager(planTier, void 0, config.seats ?? 1);
3459
- if (config.upgradeUrl) {
3460
- this.planManager.upgradeUrl = config.upgradeUrl;
3885
+ constructor(store, logger) {
3886
+ this.store = store;
3887
+ this.logger = logger;
3888
+ }
3889
+ // --------------------------------------------------------------------------
3890
+ // Layer 1: Session Delegation
3891
+ // --------------------------------------------------------------------------
3892
+ /**
3893
+ * Create a delegated agent session. Records the delegation in the
3894
+ * tamper-evident digest chain as the session's genesis event.
3895
+ */
3896
+ async createSession(input) {
3897
+ if (!input.agentId) {
3898
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "agentId is required");
3899
+ }
3900
+ if (!input.delegatedBy) {
3901
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "delegatedBy is required");
3902
+ }
3903
+ if (!input.scope || input.scope.length === 0) {
3904
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "scope must contain at least one capability");
3905
+ }
3906
+ const sessionId = generateId();
3907
+ const createdAt = now();
3908
+ const constraints = input.constraints ? {
3909
+ ...input.constraints,
3910
+ ...input.constraints.allowedRecipients ? { allowedRecipients: input.constraints.allowedRecipients.map((a) => a.toLowerCase()) } : {}
3911
+ } : void 0;
3912
+ const session = {
3913
+ sessionId,
3914
+ agentId: input.agentId,
3915
+ delegatedBy: input.delegatedBy,
3916
+ scope: [...input.scope],
3917
+ ...constraints ? { constraints } : {},
3918
+ status: "active",
3919
+ createdAt,
3920
+ ...input.expiresIn ? { expiresAt: new Date(Date.now() + input.expiresIn).toISOString() } : {},
3921
+ metadata: input.metadata ? { ...input.metadata } : {}
3922
+ };
3923
+ const action = await this.logger.log({
3924
+ type: "session-start",
3925
+ description: `Session created: ${input.agentId} delegated by ${input.delegatedBy}`,
3926
+ agentId: input.agentId,
3927
+ sessionId,
3928
+ metadata: {
3929
+ delegatedBy: input.delegatedBy,
3930
+ scope: input.scope,
3931
+ ...constraints ? { constraints } : {}
3932
+ }
3933
+ });
3934
+ session.digest = action.digest;
3935
+ session.priorDigest = action.priorDigest;
3936
+ this.store.addSession(session);
3937
+ return { ...session, scope: [...session.scope] };
3938
+ }
3939
+ /**
3940
+ * Get an agent session by ID. Automatically marks expired sessions.
3941
+ */
3942
+ getSession(sessionId) {
3943
+ const session = this.store.getSession(sessionId);
3944
+ if (!session) return void 0;
3945
+ if (session.status === "active" && session.expiresAt && new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
3946
+ this.store.updateSession(sessionId, { status: "expired" });
3947
+ return { ...session, status: "expired", scope: [...session.scope] };
3461
3948
  }
3462
- this.exporter = config.exporter ?? new NoopExporter();
3463
- this.logger = new ActionLogger(config, this.store);
3464
- this.taskManager = new TaskManager(config, this.store);
3465
- this.auditExporter = new AuditExporter(config, this.store);
3466
- this.trustScorer = new TrustScorer(config, this.store);
3467
- this.anomalyDetector = new AnomalyDetector(config, this.store);
3468
- this.featureFlagManager = config.featureFlags ? new FeatureFlagManager(config.featureFlags) : null;
3469
- if (config.anomalyRules && config.anomalyRules.length > 0) {
3470
- const advancedRules = ["newDestination", "offHoursActivity", "rapidSuccession", "roundAmount"];
3471
- const hasAdvanced = config.anomalyRules.some((r) => advancedRules.includes(r));
3472
- if (hasAdvanced) {
3473
- requirePlan("advanced-anomaly-rules", planTier);
3949
+ return { ...session, scope: [...session.scope] };
3950
+ }
3951
+ /**
3952
+ * Get all agent sessions.
3953
+ */
3954
+ getSessions() {
3955
+ return this.store.getSessions().map((s) => {
3956
+ if (s.status === "active" && s.expiresAt && new Date(s.expiresAt) < /* @__PURE__ */ new Date()) {
3957
+ this.store.updateSession(s.sessionId, { status: "expired" });
3958
+ return { ...s, status: "expired", scope: [...s.scope] };
3474
3959
  }
3475
- this.anomalyDetector.enableAnomalyDetection({
3476
- rules: config.anomalyRules,
3477
- thresholds: config.anomalyThresholds
3478
- });
3960
+ return { ...s, scope: [...s.scope] };
3961
+ });
3962
+ }
3963
+ /**
3964
+ * End an active agent session. Records the termination in the digest chain.
3965
+ */
3966
+ async endSession(sessionId) {
3967
+ const session = this.store.getSession(sessionId);
3968
+ if (!session) {
3969
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session not found: ${sessionId}`);
3970
+ }
3971
+ if (session.status !== "active") {
3972
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session is already ${session.status}: ${sessionId}`);
3973
+ }
3974
+ const endedAt = now();
3975
+ await this.logger.log({
3976
+ type: "session-end",
3977
+ description: `Session ended: ${session.agentId}`,
3978
+ agentId: session.agentId,
3979
+ sessionId,
3980
+ metadata: { delegatedBy: session.delegatedBy }
3981
+ });
3982
+ const updated = this.store.updateSession(sessionId, { status: "ended", endedAt });
3983
+ if (!updated) {
3984
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Failed to update session: ${sessionId}`);
3479
3985
  }
3986
+ return { ...updated, scope: [...updated.scope] };
3480
3987
  }
3481
3988
  /**
3482
- * Initialize the Kontext SDK.
3483
- *
3484
- * @param config - Configuration options
3485
- * @returns Initialized Kontext client instance
3486
- *
3487
- * @example
3488
- * ```typescript
3489
- * // Local/OSS mode (no API key)
3490
- * const kontext = Kontext.init({
3491
- * projectId: 'my-project',
3492
- * environment: 'development',
3493
- * });
3494
- *
3495
- * // Cloud mode (with API key)
3496
- * const kontext = Kontext.init({
3497
- * apiKey: 'sk_live_...',
3498
- * projectId: 'my-project',
3499
- * environment: 'production',
3500
- * });
3501
- *
3502
- * // With persistent file storage
3503
- * const kontext = Kontext.init({
3504
- * projectId: 'my-project',
3505
- * environment: 'development',
3506
- * storage: new FileStorage('./kontext-data'),
3507
- * });
3508
- * ```
3989
+ * Check whether an action is within a session's delegated scope.
3509
3990
  */
3510
- static init(config) {
3511
- if (!config.projectId || config.projectId.trim() === "") {
3512
- throw new KontextError(
3513
- "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
3514
- "projectId is required"
3515
- );
3991
+ validateScope(sessionId, action) {
3992
+ const session = this.getSession(sessionId);
3993
+ if (!session || session.status !== "active") return false;
3994
+ return session.scope.includes(action);
3995
+ }
3996
+ /**
3997
+ * Check whether a transaction meets session constraints.
3998
+ */
3999
+ validateConstraints(sessionId, input) {
4000
+ const session = this.getSession(sessionId);
4001
+ if (!session || session.status !== "active") return false;
4002
+ if (!session.constraints) return true;
4003
+ const c = session.constraints;
4004
+ if (c.maxAmount && input.amount) {
4005
+ if (parseAmount(input.amount) > parseAmount(c.maxAmount)) return false;
3516
4006
  }
3517
- const validEnvironments = ["development", "staging", "production"];
3518
- if (!validEnvironments.includes(config.environment)) {
3519
- throw new KontextError(
3520
- "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
3521
- `Invalid environment: ${config.environment}. Must be one of: ${validEnvironments.join(", ")}`
3522
- );
4007
+ if (c.allowedChains && input.chain) {
4008
+ if (!c.allowedChains.includes(input.chain)) return false;
3523
4009
  }
3524
- if (config.debug) {
3525
- const mode = config.apiKey ? "cloud" : "local";
3526
- console.debug(
3527
- `[Kontext] Initializing in ${mode} mode for project ${config.projectId} (${config.environment})`
3528
- );
4010
+ if (c.allowedTokens && input.token) {
4011
+ if (!c.allowedTokens.includes(input.token)) return false;
3529
4012
  }
3530
- return new _Kontext(config);
4013
+ if (c.allowedRecipients && input.to) {
4014
+ if (!c.allowedRecipients.includes(input.to.toLowerCase())) return false;
4015
+ }
4016
+ return true;
3531
4017
  }
3532
4018
  // --------------------------------------------------------------------------
3533
- // Mode & Config
4019
+ // Layer 3: Checkpoints & Human Attestation
3534
4020
  // --------------------------------------------------------------------------
3535
4021
  /**
3536
- * Get the current operating mode.
3537
- */
3538
- getMode() {
3539
- return this.mode;
3540
- }
3541
- /**
3542
- * Get the current configuration (API key is masked).
4022
+ * Create a provenance checkpoint -- a review point where a human
4023
+ * can attest to a batch of agent actions.
4024
+ */
4025
+ async createCheckpoint(input) {
4026
+ const session = this.getSession(input.sessionId);
4027
+ if (!session) {
4028
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session not found: ${input.sessionId}`);
4029
+ }
4030
+ if (session.status !== "active") {
4031
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session is ${session.status}: ${input.sessionId}`);
4032
+ }
4033
+ if (!input.actionIds || input.actionIds.length === 0) {
4034
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "actionIds must contain at least one action");
4035
+ }
4036
+ if (!input.summary) {
4037
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "summary is required");
4038
+ }
4039
+ const sessionActions = this.store.getActionsBySession(input.sessionId);
4040
+ const sessionActionIds = new Set(sessionActions.map((a) => a.id));
4041
+ for (const actionId of input.actionIds) {
4042
+ if (!sessionActionIds.has(actionId)) {
4043
+ throw new KontextError(
4044
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
4045
+ `Action ${actionId} not found in session ${input.sessionId}`
4046
+ );
4047
+ }
4048
+ }
4049
+ const actionDigests = input.actionIds.map((id) => {
4050
+ const action = sessionActions.find((a) => a.id === id);
4051
+ return action?.digest ?? "";
4052
+ }).sort();
4053
+ const hash = createHash("sha256");
4054
+ hash.update(actionDigests.join(""));
4055
+ const actionsDigest = hash.digest("hex");
4056
+ const checkpointId = generateId();
4057
+ const createdAt = now();
4058
+ const checkpoint = {
4059
+ id: checkpointId,
4060
+ sessionId: input.sessionId,
4061
+ actionIds: [...input.actionIds],
4062
+ summary: input.summary,
4063
+ actionsDigest,
4064
+ status: "pending",
4065
+ createdAt,
4066
+ ...input.expiresIn ? { expiresAt: new Date(Date.now() + input.expiresIn).toISOString() } : {}
4067
+ };
4068
+ await this.logger.log({
4069
+ type: "checkpoint-created",
4070
+ description: `Checkpoint created: ${input.summary}`,
4071
+ agentId: session.agentId,
4072
+ sessionId: input.sessionId,
4073
+ metadata: {
4074
+ checkpointId,
4075
+ actionCount: input.actionIds.length,
4076
+ actionsDigest
4077
+ }
4078
+ });
4079
+ this.store.addCheckpoint(checkpoint);
4080
+ return { ...checkpoint, actionIds: [...checkpoint.actionIds] };
4081
+ }
4082
+ /**
4083
+ * Attach an externally-produced human attestation to a checkpoint.
4084
+ * The attestation includes a cryptographic signature that the agent
4085
+ * never touches -- key separation is the critical security property.
4086
+ */
4087
+ async attachAttestation(checkpointId, attestation) {
4088
+ const checkpoint = this.store.getCheckpoint(checkpointId);
4089
+ if (!checkpoint) {
4090
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Checkpoint not found: ${checkpointId}`);
4091
+ }
4092
+ if (checkpoint.status === "pending" && checkpoint.expiresAt && new Date(checkpoint.expiresAt) < /* @__PURE__ */ new Date()) {
4093
+ this.store.updateCheckpoint(checkpointId, { status: "expired" });
4094
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Checkpoint expired: ${checkpointId}`);
4095
+ }
4096
+ if (checkpoint.status !== "pending") {
4097
+ throw new KontextError(
4098
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
4099
+ `Checkpoint is already ${checkpoint.status}: ${checkpointId}`
4100
+ );
4101
+ }
4102
+ if (attestation.checkpointId !== checkpointId) {
4103
+ throw new KontextError(
4104
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
4105
+ `Attestation checkpointId mismatch: expected ${checkpointId}, got ${attestation.checkpointId}`
4106
+ );
4107
+ }
4108
+ const newStatus = attestation.decision === "approved" ? "attested" : "rejected";
4109
+ const session = this.store.getSession(checkpoint.sessionId);
4110
+ const agentId = session?.agentId ?? "unknown";
4111
+ await this.logger.log({
4112
+ type: `checkpoint-${newStatus}`,
4113
+ description: `Checkpoint ${newStatus} by ${attestation.reviewerId}`,
4114
+ agentId,
4115
+ sessionId: checkpoint.sessionId,
4116
+ metadata: {
4117
+ checkpointId,
4118
+ reviewerId: attestation.reviewerId,
4119
+ decision: attestation.decision,
4120
+ attestationId: attestation.attestationId
4121
+ }
4122
+ });
4123
+ const updated = this.store.updateCheckpoint(checkpointId, {
4124
+ status: newStatus,
4125
+ attestation
4126
+ });
4127
+ if (!updated) {
4128
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Failed to update checkpoint: ${checkpointId}`);
4129
+ }
4130
+ return { ...updated, actionIds: [...updated.actionIds] };
4131
+ }
4132
+ /**
4133
+ * Get a checkpoint by ID. Automatically marks expired checkpoints.
4134
+ */
4135
+ getCheckpoint(checkpointId) {
4136
+ const cp = this.store.getCheckpoint(checkpointId);
4137
+ if (!cp) return void 0;
4138
+ if (cp.status === "pending" && cp.expiresAt && new Date(cp.expiresAt) < /* @__PURE__ */ new Date()) {
4139
+ this.store.updateCheckpoint(checkpointId, { status: "expired" });
4140
+ return { ...cp, status: "expired", actionIds: [...cp.actionIds] };
4141
+ }
4142
+ return { ...cp, actionIds: [...cp.actionIds] };
4143
+ }
4144
+ /**
4145
+ * Get all checkpoints, optionally filtered by session.
4146
+ */
4147
+ getCheckpoints(sessionId) {
4148
+ const all = sessionId ? this.store.queryCheckpoints((cp) => cp.sessionId === sessionId) : this.store.getCheckpoints();
4149
+ return all.map((cp) => {
4150
+ if (cp.status === "pending" && cp.expiresAt && new Date(cp.expiresAt) < /* @__PURE__ */ new Date()) {
4151
+ this.store.updateCheckpoint(cp.id, { status: "expired" });
4152
+ return { ...cp, status: "expired", actionIds: [...cp.actionIds] };
4153
+ }
4154
+ return { ...cp, actionIds: [...cp.actionIds] };
4155
+ });
4156
+ }
4157
+ // --------------------------------------------------------------------------
4158
+ // Provenance Bundle Export
4159
+ // --------------------------------------------------------------------------
4160
+ /**
4161
+ * Export the full provenance bundle for a session.
4162
+ */
4163
+ getProvenanceBundle(sessionId) {
4164
+ const session = this.getSession(sessionId);
4165
+ if (!session) {
4166
+ throw new KontextError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Session not found: ${sessionId}`);
4167
+ }
4168
+ const sessionActions = this.store.getActionsBySession(sessionId);
4169
+ const checkpoints = this.getCheckpoints(sessionId);
4170
+ const actions = sessionActions.filter((a) => a.digest && a.priorDigest).map((a) => ({
4171
+ actionId: a.id,
4172
+ type: a.type,
4173
+ digest: a.digest,
4174
+ priorDigest: a.priorDigest,
4175
+ sessionId,
4176
+ timestamp: a.timestamp
4177
+ }));
4178
+ const attestedCheckpoints = checkpoints.filter((cp) => cp.status === "attested");
4179
+ const attestedActionIds = /* @__PURE__ */ new Set();
4180
+ for (const cp of attestedCheckpoints) {
4181
+ for (const actionId of cp.actionIds) {
4182
+ attestedActionIds.add(actionId);
4183
+ }
4184
+ }
4185
+ const userActions = sessionActions.filter(
4186
+ (a) => !a.type.startsWith("session-") && !a.type.startsWith("checkpoint-")
4187
+ );
4188
+ const verification = {
4189
+ digestChainValid: this.logger.verifyChain(this.store.getActions()).valid,
4190
+ totalActions: userActions.length,
4191
+ humanAttested: userActions.filter((a) => attestedActionIds.has(a.id)).length,
4192
+ sessionScoped: userActions.length,
4193
+ unattested: userActions.filter((a) => !attestedActionIds.has(a.id)).length
4194
+ };
4195
+ return {
4196
+ session,
4197
+ actions,
4198
+ checkpoints,
4199
+ verification,
4200
+ generatedAt: now()
4201
+ };
4202
+ }
4203
+ };
4204
+
4205
+ // src/kya/identity-registry.ts
4206
+ var AgentIdentityRegistry = class {
4207
+ /** agentId -> AgentIdentity */
4208
+ identities = /* @__PURE__ */ new Map();
4209
+ /** normalized address -> agentId (reverse index) */
4210
+ walletIndex = /* @__PURE__ */ new Map();
4211
+ /**
4212
+ * Register a new agent identity.
4213
+ */
4214
+ register(input) {
4215
+ if (this.identities.has(input.agentId)) {
4216
+ throw new Error(`Identity already registered for agent: ${input.agentId}`);
4217
+ }
4218
+ const timestamp = now();
4219
+ const wallets = (input.wallets ?? []).map((w) => ({
4220
+ address: w.address.toLowerCase(),
4221
+ chain: w.chain,
4222
+ verified: false,
4223
+ addedAt: timestamp,
4224
+ label: w.label
4225
+ }));
4226
+ const identity = {
4227
+ agentId: input.agentId,
4228
+ displayName: input.displayName,
4229
+ entityType: input.entityType ?? "unknown",
4230
+ wallets,
4231
+ kycReferences: [],
4232
+ contactUri: input.contactUri,
4233
+ metadata: input.metadata ?? {},
4234
+ createdAt: timestamp,
4235
+ updatedAt: timestamp
4236
+ };
4237
+ this.identities.set(input.agentId, identity);
4238
+ for (const wallet of wallets) {
4239
+ this.walletIndex.set(wallet.address, input.agentId);
4240
+ }
4241
+ return { ...identity, wallets: [...wallets] };
4242
+ }
4243
+ /**
4244
+ * Update an existing agent identity.
4245
+ */
4246
+ update(agentId, input) {
4247
+ const existing = this.identities.get(agentId);
4248
+ if (!existing) {
4249
+ throw new Error(`Identity not found for agent: ${agentId}`);
4250
+ }
4251
+ const updated = {
4252
+ ...existing,
4253
+ updatedAt: now()
4254
+ };
4255
+ if (input.displayName !== void 0) updated.displayName = input.displayName;
4256
+ if (input.entityType !== void 0) updated.entityType = input.entityType;
4257
+ if (input.contactUri !== void 0) updated.contactUri = input.contactUri;
4258
+ if (input.metadata !== void 0) {
4259
+ updated.metadata = { ...existing.metadata, ...input.metadata };
4260
+ }
4261
+ this.identities.set(agentId, updated);
4262
+ return { ...updated, wallets: [...updated.wallets] };
4263
+ }
4264
+ /**
4265
+ * Get an agent identity by agent ID.
4266
+ */
4267
+ get(agentId) {
4268
+ const identity = this.identities.get(agentId);
4269
+ if (!identity) return void 0;
4270
+ return { ...identity, wallets: [...identity.wallets] };
4271
+ }
4272
+ /**
4273
+ * Remove an agent identity and all wallet index entries.
4274
+ */
4275
+ remove(agentId) {
4276
+ const identity = this.identities.get(agentId);
4277
+ if (!identity) return false;
4278
+ for (const wallet of identity.wallets) {
4279
+ this.walletIndex.delete(wallet.address);
4280
+ }
4281
+ this.identities.delete(agentId);
4282
+ return true;
4283
+ }
4284
+ /**
4285
+ * Get all registered identities.
4286
+ */
4287
+ getAll() {
4288
+ return Array.from(this.identities.values()).map((id) => ({
4289
+ ...id,
4290
+ wallets: [...id.wallets]
4291
+ }));
4292
+ }
4293
+ // --------------------------------------------------------------------------
4294
+ // Wallet Operations
4295
+ // --------------------------------------------------------------------------
4296
+ /**
4297
+ * Add a wallet to an existing agent identity.
4298
+ */
4299
+ addWallet(agentId, wallet) {
4300
+ const identity = this.identities.get(agentId);
4301
+ if (!identity) {
4302
+ throw new Error(`Identity not found for agent: ${agentId}`);
4303
+ }
4304
+ const normalized = wallet.address.toLowerCase();
4305
+ const existingOwner = this.walletIndex.get(normalized);
4306
+ if (existingOwner && existingOwner !== agentId) {
4307
+ throw new Error(`Wallet ${normalized} is already registered to agent: ${existingOwner}`);
4308
+ }
4309
+ if (identity.wallets.some((w) => w.address === normalized)) {
4310
+ return { ...identity, wallets: [...identity.wallets] };
4311
+ }
4312
+ const mapping = {
4313
+ address: normalized,
4314
+ chain: wallet.chain,
4315
+ verified: false,
4316
+ addedAt: now(),
4317
+ label: wallet.label
4318
+ };
4319
+ identity.wallets.push(mapping);
4320
+ identity.updatedAt = now();
4321
+ this.walletIndex.set(normalized, agentId);
4322
+ return { ...identity, wallets: [...identity.wallets] };
4323
+ }
4324
+ /**
4325
+ * Remove a wallet from an agent identity.
4326
+ */
4327
+ removeWallet(agentId, address) {
4328
+ const identity = this.identities.get(agentId);
4329
+ if (!identity) {
4330
+ throw new Error(`Identity not found for agent: ${agentId}`);
4331
+ }
4332
+ const normalized = address.toLowerCase();
4333
+ identity.wallets = identity.wallets.filter((w) => w.address !== normalized);
4334
+ identity.updatedAt = now();
4335
+ this.walletIndex.delete(normalized);
4336
+ return { ...identity, wallets: [...identity.wallets] };
4337
+ }
4338
+ /**
4339
+ * Look up an agent identity by wallet address.
4340
+ */
4341
+ lookupByWallet(address) {
4342
+ const agentId = this.walletIndex.get(address.toLowerCase());
4343
+ if (!agentId) return void 0;
4344
+ return this.get(agentId);
4345
+ }
4346
+ // --------------------------------------------------------------------------
4347
+ // KYC Operations
4348
+ // --------------------------------------------------------------------------
4349
+ /**
4350
+ * Add a KYC provider reference to an agent identity.
4351
+ */
4352
+ addKycReference(agentId, reference) {
4353
+ const identity = this.identities.get(agentId);
4354
+ if (!identity) {
4355
+ throw new Error(`Identity not found for agent: ${agentId}`);
4356
+ }
4357
+ identity.kycReferences.push(reference);
4358
+ identity.updatedAt = now();
4359
+ return { ...identity, wallets: [...identity.wallets] };
4360
+ }
4361
+ /**
4362
+ * Get the overall KYC status for an agent.
4363
+ * Returns the best status from all references.
4364
+ */
4365
+ getKycStatus(agentId) {
4366
+ const identity = this.identities.get(agentId);
4367
+ if (!identity || identity.kycReferences.length === 0) return "none";
4368
+ const statusPriority = {
4369
+ verified: 4,
4370
+ pending: 3,
4371
+ expired: 2,
4372
+ rejected: 1,
4373
+ none: 0
4374
+ };
4375
+ let best = "none";
4376
+ for (const ref of identity.kycReferences) {
4377
+ if (statusPriority[ref.status] > statusPriority[best]) {
4378
+ best = ref.status;
4379
+ }
4380
+ }
4381
+ return best;
4382
+ }
4383
+ /**
4384
+ * Check if an agent has at least one verified and non-expired KYC reference.
4385
+ */
4386
+ hasVerifiedKyc(agentId) {
4387
+ const identity = this.identities.get(agentId);
4388
+ if (!identity) return false;
4389
+ const currentTime = (/* @__PURE__ */ new Date()).toISOString();
4390
+ return identity.kycReferences.some(
4391
+ (ref) => ref.status === "verified" && (ref.expiresAt === null || ref.expiresAt > currentTime)
4392
+ );
4393
+ }
4394
+ };
4395
+
4396
+ // src/kya/wallet-clustering.ts
4397
+ var UnionFind = class {
4398
+ parent = /* @__PURE__ */ new Map();
4399
+ rank = /* @__PURE__ */ new Map();
4400
+ /**
4401
+ * Find the representative of the set containing x.
4402
+ * Uses path compression for amortized near-constant time.
4403
+ */
4404
+ find(x) {
4405
+ if (!this.parent.has(x)) {
4406
+ this.parent.set(x, x);
4407
+ this.rank.set(x, 0);
4408
+ }
4409
+ let root = x;
4410
+ while (this.parent.get(root) !== root) {
4411
+ root = this.parent.get(root);
4412
+ }
4413
+ let current = x;
4414
+ while (current !== root) {
4415
+ const next = this.parent.get(current);
4416
+ this.parent.set(current, root);
4417
+ current = next;
4418
+ }
4419
+ return root;
4420
+ }
4421
+ /**
4422
+ * Union the sets containing x and y.
4423
+ * Uses union-by-rank for balanced trees.
4424
+ */
4425
+ union(x, y) {
4426
+ const rootX = this.find(x);
4427
+ const rootY = this.find(y);
4428
+ if (rootX === rootY) return;
4429
+ const rankX = this.rank.get(rootX);
4430
+ const rankY = this.rank.get(rootY);
4431
+ if (rankX < rankY) {
4432
+ this.parent.set(rootX, rootY);
4433
+ } else if (rankX > rankY) {
4434
+ this.parent.set(rootY, rootX);
4435
+ } else {
4436
+ this.parent.set(rootY, rootX);
4437
+ this.rank.set(rootX, rankX + 1);
4438
+ }
4439
+ }
4440
+ /**
4441
+ * Check if x and y are in the same set.
4442
+ */
4443
+ connected(x, y) {
4444
+ return this.find(x) === this.find(y);
4445
+ }
4446
+ /**
4447
+ * Get all components as arrays of elements.
4448
+ */
4449
+ getComponents() {
4450
+ const components = /* @__PURE__ */ new Map();
4451
+ for (const key of this.parent.keys()) {
4452
+ const root = this.find(key);
4453
+ if (!components.has(root)) {
4454
+ components.set(root, []);
4455
+ }
4456
+ components.get(root).push(key);
4457
+ }
4458
+ return Array.from(components.values());
4459
+ }
4460
+ /**
4461
+ * Get the component containing x.
4462
+ */
4463
+ getComponentOf(x) {
4464
+ const root = this.find(x);
4465
+ const component = [];
4466
+ for (const key of this.parent.keys()) {
4467
+ if (this.find(key) === root) {
4468
+ component.push(key);
4469
+ }
4470
+ }
4471
+ return component;
4472
+ }
4473
+ };
4474
+ var WalletClusterer = class {
4475
+ config;
4476
+ cachedClusters = null;
4477
+ constructor(config = {}) {
4478
+ this.config = {
4479
+ temporalWindowSeconds: config.temporalWindowSeconds ?? 60,
4480
+ minDestinationOverlap: config.minDestinationOverlap ?? 0.3,
4481
+ minGasSponsoredWallets: config.minGasSponsoredWallets ?? 3
4482
+ };
4483
+ }
4484
+ /**
4485
+ * Analyze transactions from the store and registry to produce wallet clusters.
4486
+ */
4487
+ analyzeFromStore(store, registry) {
4488
+ const uf = new UnionFind();
4489
+ const evidence = [];
4490
+ const transactions = store.getTransactions();
4491
+ const timestamp = now();
4492
+ for (const tx of transactions) {
4493
+ uf.find(tx.from.toLowerCase());
4494
+ uf.find(tx.to.toLowerCase());
4495
+ }
4496
+ this.applyCommonAgent(store, uf, evidence, timestamp);
4497
+ this.applyFundingChain(store, uf, evidence, timestamp);
4498
+ this.applyGasSponsorship(store, uf, evidence, timestamp);
4499
+ this.applyTemporalCoSpending(store, uf, evidence, timestamp);
4500
+ if (registry) {
4501
+ this.applyDeclaredWallets(registry, uf, evidence, timestamp);
4502
+ }
4503
+ const clusters = this.buildClusters(uf, evidence, store, registry, timestamp);
4504
+ this.cachedClusters = clusters;
4505
+ return clusters;
4506
+ }
4507
+ /**
4508
+ * Get cached clusters (or empty if not analyzed yet).
4509
+ */
4510
+ getClusters() {
4511
+ return this.cachedClusters ?? [];
4512
+ }
4513
+ /**
4514
+ * Invalidate the cached clusters.
4515
+ */
4516
+ invalidateCache() {
4517
+ this.cachedClusters = null;
4518
+ }
4519
+ // --------------------------------------------------------------------------
4520
+ // Heuristics
4521
+ // --------------------------------------------------------------------------
4522
+ /**
4523
+ * Heuristic 1: Common Agent (confidence 0.9)
4524
+ * Same agentId used multiple addresses -> union all from/to per agent.
4525
+ */
4526
+ applyCommonAgent(store, uf, evidence, timestamp) {
4527
+ const transactions = store.getTransactions();
4528
+ const agentAddresses = /* @__PURE__ */ new Map();
4529
+ for (const tx of transactions) {
4530
+ const from = tx.from.toLowerCase();
4531
+ const agentId = tx.agentId;
4532
+ if (!agentAddresses.has(agentId)) {
4533
+ agentAddresses.set(agentId, /* @__PURE__ */ new Set());
4534
+ }
4535
+ agentAddresses.get(agentId).add(from);
4536
+ }
4537
+ for (const [, addresses] of agentAddresses) {
4538
+ const addrs = Array.from(addresses);
4539
+ for (let i = 1; i < addrs.length; i++) {
4540
+ if (!uf.connected(addrs[0], addrs[i])) {
4541
+ uf.union(addrs[0], addrs[i]);
4542
+ evidence.push({
4543
+ heuristic: "common-agent",
4544
+ confidence: 0.9,
4545
+ addresses: [addrs[0], addrs[i]],
4546
+ detectedAt: timestamp
4547
+ });
4548
+ }
4549
+ }
4550
+ }
4551
+ }
4552
+ /**
4553
+ * Heuristic 2: Funding Chain (confidence 0.7)
4554
+ * If A sends to B, and A is agent-associated -> union A and B.
4555
+ */
4556
+ applyFundingChain(store, uf, evidence, timestamp) {
4557
+ const transactions = store.getTransactions();
4558
+ const agentAssociated = /* @__PURE__ */ new Set();
4559
+ for (const tx of transactions) {
4560
+ agentAssociated.add(tx.from.toLowerCase());
4561
+ }
4562
+ for (const tx of transactions) {
4563
+ const from = tx.from.toLowerCase();
4564
+ const to = tx.to.toLowerCase();
4565
+ if (agentAssociated.has(from) && !uf.connected(from, to)) {
4566
+ uf.union(from, to);
4567
+ evidence.push({
4568
+ heuristic: "funding-chain",
4569
+ confidence: 0.7,
4570
+ addresses: [from, to],
4571
+ detectedAt: timestamp
4572
+ });
4573
+ }
4574
+ }
4575
+ }
4576
+ /**
4577
+ * Heuristic 3: Gas Sponsorship (confidence 0.75)
4578
+ * If G is from in transfers to 3+ distinct agent wallets -> union G with all.
4579
+ */
4580
+ applyGasSponsorship(store, uf, evidence, timestamp) {
4581
+ const transactions = store.getTransactions();
4582
+ const agentFromAddresses = /* @__PURE__ */ new Set();
4583
+ for (const tx of transactions) {
4584
+ agentFromAddresses.add(tx.from.toLowerCase());
4585
+ }
4586
+ const senderToRecipients = /* @__PURE__ */ new Map();
4587
+ for (const tx of transactions) {
4588
+ const from = tx.from.toLowerCase();
4589
+ const to = tx.to.toLowerCase();
4590
+ if (!senderToRecipients.has(from)) {
4591
+ senderToRecipients.set(from, /* @__PURE__ */ new Set());
4592
+ }
4593
+ senderToRecipients.get(from).add(to);
4594
+ }
4595
+ for (const [sender, recipients] of senderToRecipients) {
4596
+ const agentRecipients = Array.from(recipients).filter(
4597
+ (r) => agentFromAddresses.has(r)
4598
+ );
4599
+ if (agentRecipients.length >= this.config.minGasSponsoredWallets) {
4600
+ for (const recipient of agentRecipients) {
4601
+ if (!uf.connected(sender, recipient)) {
4602
+ uf.union(sender, recipient);
4603
+ evidence.push({
4604
+ heuristic: "gas-sponsorship",
4605
+ confidence: 0.75,
4606
+ addresses: [sender, recipient],
4607
+ detectedAt: timestamp,
4608
+ detail: `Sender ${sender} sponsors ${agentRecipients.length} agent wallets`
4609
+ });
4610
+ }
4611
+ }
4612
+ }
4613
+ }
4614
+ }
4615
+ /**
4616
+ * Heuristic 4: Temporal Co-Spending (confidence 0.6)
4617
+ * Addresses transacting within a window with destination overlap.
4618
+ */
4619
+ applyTemporalCoSpending(store, uf, evidence, timestamp) {
4620
+ const transactions = store.getTransactions();
4621
+ if (transactions.length < 2) return;
4622
+ const txByFrom = /* @__PURE__ */ new Map();
4623
+ for (const tx of transactions) {
4624
+ const from = tx.from.toLowerCase();
4625
+ const to = tx.to.toLowerCase();
4626
+ if (!txByFrom.has(from)) {
4627
+ txByFrom.set(from, []);
4628
+ }
4629
+ txByFrom.get(from).push({
4630
+ to,
4631
+ time: new Date(tx.timestamp).getTime() / 1e3
4632
+ });
4633
+ }
4634
+ const fromAddresses = Array.from(txByFrom.keys());
4635
+ for (let i = 0; i < fromAddresses.length; i++) {
4636
+ for (let j = i + 1; j < fromAddresses.length; j++) {
4637
+ const addrA = fromAddresses[i];
4638
+ const addrB = fromAddresses[j];
4639
+ const txsA = txByFrom.get(addrA);
4640
+ const txsB = txByFrom.get(addrB);
4641
+ let hasTemporalOverlap = false;
4642
+ for (const a of txsA) {
4643
+ for (const b of txsB) {
4644
+ if (Math.abs(a.time - b.time) <= this.config.temporalWindowSeconds) {
4645
+ hasTemporalOverlap = true;
4646
+ break;
4647
+ }
4648
+ }
4649
+ if (hasTemporalOverlap) break;
4650
+ }
4651
+ if (!hasTemporalOverlap) continue;
4652
+ const destsA = new Set(txsA.map((t) => t.to));
4653
+ const destsB = new Set(txsB.map((t) => t.to));
4654
+ const intersection = new Set([...destsA].filter((d) => destsB.has(d)));
4655
+ const union = /* @__PURE__ */ new Set([...destsA, ...destsB]);
4656
+ const overlapRatio = union.size > 0 ? intersection.size / union.size : 0;
4657
+ if (overlapRatio >= this.config.minDestinationOverlap) {
4658
+ if (!uf.connected(addrA, addrB)) {
4659
+ uf.union(addrA, addrB);
4660
+ evidence.push({
4661
+ heuristic: "temporal-co-spending",
4662
+ confidence: 0.6,
4663
+ addresses: [addrA, addrB],
4664
+ detectedAt: timestamp,
4665
+ detail: `Overlap ratio: ${overlapRatio.toFixed(2)}`
4666
+ });
4667
+ }
4668
+ }
4669
+ }
4670
+ }
4671
+ }
4672
+ /**
4673
+ * Heuristic 5: Declared Wallets (confidence 1.0)
4674
+ * All wallets declared by the same identity are unioned.
4675
+ */
4676
+ applyDeclaredWallets(registry, uf, evidence, timestamp) {
4677
+ const identities = registry.getAll();
4678
+ for (const identity of identities) {
4679
+ const addrs = identity.wallets.map((w) => w.address);
4680
+ for (let i = 1; i < addrs.length; i++) {
4681
+ if (!uf.connected(addrs[0], addrs[i])) {
4682
+ uf.union(addrs[0], addrs[i]);
4683
+ evidence.push({
4684
+ heuristic: "declared-wallets",
4685
+ confidence: 1,
4686
+ addresses: [addrs[0], addrs[i]],
4687
+ detectedAt: timestamp
4688
+ });
4689
+ }
4690
+ }
4691
+ }
4692
+ }
4693
+ // --------------------------------------------------------------------------
4694
+ // Cluster Building
4695
+ // --------------------------------------------------------------------------
4696
+ buildClusters(uf, evidence, store, registry, timestamp) {
4697
+ const components = uf.getComponents();
4698
+ const clusters = [];
4699
+ const evidenceByAddress = /* @__PURE__ */ new Map();
4700
+ for (const e of evidence) {
4701
+ for (const addr of e.addresses) {
4702
+ if (!evidenceByAddress.has(addr)) {
4703
+ evidenceByAddress.set(addr, []);
4704
+ }
4705
+ evidenceByAddress.get(addr).push(e);
4706
+ }
4707
+ }
4708
+ const addressToAgents = /* @__PURE__ */ new Map();
4709
+ for (const tx of store.getTransactions()) {
4710
+ const from = tx.from.toLowerCase();
4711
+ if (!addressToAgents.has(from)) {
4712
+ addressToAgents.set(from, /* @__PURE__ */ new Set());
4713
+ }
4714
+ addressToAgents.get(from).add(tx.agentId);
4715
+ }
4716
+ if (registry) {
4717
+ for (const identity of registry.getAll()) {
4718
+ for (const wallet of identity.wallets) {
4719
+ if (!addressToAgents.has(wallet.address)) {
4720
+ addressToAgents.set(wallet.address, /* @__PURE__ */ new Set());
4721
+ }
4722
+ addressToAgents.get(wallet.address).add(identity.agentId);
4723
+ }
4724
+ }
4725
+ }
4726
+ for (const component of components) {
4727
+ if (component.length < 2) continue;
4728
+ const clusterEvidence = [];
4729
+ const seen = /* @__PURE__ */ new Set();
4730
+ for (const addr of component) {
4731
+ const addrEvidence = evidenceByAddress.get(addr) ?? [];
4732
+ for (const e of addrEvidence) {
4733
+ const key = `${e.heuristic}:${e.addresses[0]}:${e.addresses[1]}`;
4734
+ if (!seen.has(key)) {
4735
+ seen.add(key);
4736
+ clusterEvidence.push(e);
4737
+ }
4738
+ }
4739
+ }
4740
+ const agentIds = /* @__PURE__ */ new Set();
4741
+ for (const addr of component) {
4742
+ const agents = addressToAgents.get(addr);
4743
+ if (agents) {
4744
+ for (const a of agents) agentIds.add(a);
4745
+ }
4746
+ }
4747
+ const maxConfidence = clusterEvidence.length > 0 ? Math.max(...clusterEvidence.map((e) => e.confidence)) : 0;
4748
+ clusters.push({
4749
+ id: generateId(),
4750
+ addresses: component.sort(),
4751
+ agentIds: Array.from(agentIds).sort(),
4752
+ evidence: clusterEvidence,
4753
+ confidence: maxConfidence,
4754
+ createdAt: timestamp
4755
+ });
4756
+ }
4757
+ return clusters;
4758
+ }
4759
+ };
4760
+
4761
+ // src/kya/behavioral-fingerprint.ts
4762
+ var MIN_SAMPLE_SIZE = 5;
4763
+ var BehavioralFingerprinter = class {
4764
+ /**
4765
+ * Compute a behavioral embedding for an agent from their transaction history.
4766
+ * Returns null if the agent has fewer than MIN_SAMPLE_SIZE transactions.
4767
+ */
4768
+ computeEmbedding(agentId, store) {
4769
+ const transactions = store.getTransactionsByAgent(agentId);
4770
+ if (transactions.length < MIN_SAMPLE_SIZE) return null;
4771
+ return {
4772
+ agentId,
4773
+ temporal: this.extractTemporalFeatures(transactions),
4774
+ financial: this.extractFinancialFeatures(transactions),
4775
+ network: this.extractNetworkFeatures(transactions),
4776
+ operational: this.extractOperationalFeatures(transactions),
4777
+ sampleSize: transactions.length,
4778
+ computedAt: now()
4779
+ };
4780
+ }
4781
+ /**
4782
+ * Compute cosine similarity between two behavioral embeddings.
4783
+ * Returns a value in [0, 1] where 1 means identical and 0 means orthogonal.
4784
+ */
4785
+ cosineSimilarity(a, b) {
4786
+ const vecA = this.flattenEmbedding(a);
4787
+ const vecB = this.flattenEmbedding(b);
4788
+ let dot = 0;
4789
+ let magA = 0;
4790
+ let magB = 0;
4791
+ for (let i = 0; i < vecA.length; i++) {
4792
+ dot += vecA[i] * vecB[i];
4793
+ magA += vecA[i] * vecA[i];
4794
+ magB += vecB[i] * vecB[i];
4795
+ }
4796
+ const denominator = Math.sqrt(magA) * Math.sqrt(magB);
4797
+ if (denominator === 0) return 0;
4798
+ return dot / denominator;
4799
+ }
4800
+ // --------------------------------------------------------------------------
4801
+ // Feature Extraction
4802
+ // --------------------------------------------------------------------------
4803
+ extractTemporalFeatures(txs) {
4804
+ const sorted = [...txs].sort(
4805
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
4806
+ );
4807
+ const intervals = [];
4808
+ for (let i = 1; i < sorted.length; i++) {
4809
+ const diff = (new Date(sorted[i].timestamp).getTime() - new Date(sorted[i - 1].timestamp).getTime()) / 1e3;
4810
+ intervals.push(diff);
4811
+ }
4812
+ const meanInterval = intervals.length > 0 ? intervals.reduce((s, v) => s + v, 0) / intervals.length : 0;
4813
+ const stddevInterval = intervals.length > 1 ? Math.sqrt(
4814
+ intervals.reduce((s, v) => s + (v - meanInterval) ** 2, 0) / (intervals.length - 1)
4815
+ ) : 0;
4816
+ const hourCounts = new Array(24).fill(0);
4817
+ for (const tx of txs) {
4818
+ const hour = new Date(tx.timestamp).getUTCHours();
4819
+ hourCounts[hour] = (hourCounts[hour] ?? 0) + 1;
4820
+ }
4821
+ const hourTotal = hourCounts.reduce((s, v) => s + v, 0);
4822
+ const hourHistogram = hourTotal > 0 ? hourCounts.map((c) => c / hourTotal) : hourCounts;
4823
+ const dayCounts = new Array(7).fill(0);
4824
+ for (const tx of txs) {
4825
+ const day = new Date(tx.timestamp).getUTCDay();
4826
+ dayCounts[day] = (dayCounts[day] ?? 0) + 1;
4827
+ }
4828
+ const dayTotal = dayCounts.reduce((s, v) => s + v, 0);
4829
+ const dayHistogram = dayTotal > 0 ? dayCounts.map((c) => c / dayTotal) : dayCounts;
4830
+ return {
4831
+ meanIntervalSeconds: meanInterval,
4832
+ stddevIntervalSeconds: stddevInterval,
4833
+ hourHistogram,
4834
+ dayHistogram
4835
+ };
4836
+ }
4837
+ extractFinancialFeatures(txs) {
4838
+ const amounts = txs.map((tx) => parseFloat(tx.amount)).filter((a) => !isNaN(a));
4839
+ if (amounts.length === 0) {
4840
+ return {
4841
+ meanAmount: 0,
4842
+ stddevAmount: 0,
4843
+ medianAmount: 0,
4844
+ roundAmountRatio: 0,
4845
+ percentiles: [0, 0, 0, 0, 0]
4846
+ };
4847
+ }
4848
+ const sorted = [...amounts].sort((a, b) => a - b);
4849
+ const mean = amounts.reduce((s, v) => s + v, 0) / amounts.length;
4850
+ const stddev = amounts.length > 1 ? Math.sqrt(
4851
+ amounts.reduce((s, v) => s + (v - mean) ** 2, 0) / (amounts.length - 1)
4852
+ ) : 0;
4853
+ const median = this.percentile(sorted, 50);
4854
+ const roundCount = amounts.filter((a) => a % 100 === 0).length;
4855
+ return {
4856
+ meanAmount: mean,
4857
+ stddevAmount: stddev,
4858
+ medianAmount: median,
4859
+ roundAmountRatio: amounts.length > 0 ? roundCount / amounts.length : 0,
4860
+ percentiles: [
4861
+ this.percentile(sorted, 10),
4862
+ this.percentile(sorted, 25),
4863
+ this.percentile(sorted, 50),
4864
+ this.percentile(sorted, 75),
4865
+ this.percentile(sorted, 90)
4866
+ ]
4867
+ };
4868
+ }
4869
+ extractNetworkFeatures(txs) {
4870
+ const destinations = txs.map((tx) => tx.to.toLowerCase());
4871
+ const sources = txs.map((tx) => tx.from.toLowerCase());
4872
+ const uniqueDests = new Set(destinations);
4873
+ const uniqueSrcs = new Set(sources);
4874
+ const reuseRatio = destinations.length > 0 ? 1 - uniqueDests.size / destinations.length : 0;
4875
+ const destCounts = /* @__PURE__ */ new Map();
4876
+ for (const d of destinations) {
4877
+ destCounts.set(d, (destCounts.get(d) ?? 0) + 1);
4878
+ }
4879
+ let hhi = 0;
4880
+ for (const count of destCounts.values()) {
4881
+ const share = count / destinations.length;
4882
+ hhi += share * share;
4883
+ }
4884
+ return {
4885
+ uniqueDestinations: uniqueDests.size,
4886
+ reuseRatio,
4887
+ concentrationIndex: hhi,
4888
+ uniqueSources: uniqueSrcs.size
4889
+ };
4890
+ }
4891
+ extractOperationalFeatures(txs) {
4892
+ const chainCounts = /* @__PURE__ */ new Map();
4893
+ const tokenCounts = /* @__PURE__ */ new Map();
4894
+ for (const tx of txs) {
4895
+ const chain = tx.chain ?? "unknown";
4896
+ const token = tx.token ?? "unknown";
4897
+ chainCounts.set(chain, (chainCounts.get(chain) ?? 0) + 1);
4898
+ tokenCounts.set(token, (tokenCounts.get(token) ?? 0) + 1);
4899
+ }
4900
+ const total = txs.length;
4901
+ const chainDistribution = {};
4902
+ for (const [chain, count] of chainCounts) {
4903
+ chainDistribution[chain] = total > 0 ? count / total : 0;
4904
+ }
4905
+ const tokenDistribution = {};
4906
+ for (const [token, count] of tokenCounts) {
4907
+ tokenDistribution[token] = total > 0 ? count / total : 0;
4908
+ }
4909
+ const primaryChain = chainCounts.size > 0 ? Array.from(chainCounts.entries()).sort((a, b) => b[1] - a[1])[0][0] : "";
4910
+ const primaryToken = tokenCounts.size > 0 ? Array.from(tokenCounts.entries()).sort((a, b) => b[1] - a[1])[0][0] : "";
4911
+ return {
4912
+ chainDistribution,
4913
+ tokenDistribution,
4914
+ primaryChain,
4915
+ primaryToken
4916
+ };
4917
+ }
4918
+ // --------------------------------------------------------------------------
4919
+ // Helpers
4920
+ // --------------------------------------------------------------------------
4921
+ percentile(sorted, p) {
4922
+ if (sorted.length === 0) return 0;
4923
+ const idx = p / 100 * (sorted.length - 1);
4924
+ const lower = Math.floor(idx);
4925
+ const upper = Math.ceil(idx);
4926
+ if (lower === upper) return sorted[lower];
4927
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
4928
+ }
4929
+ /**
4930
+ * Flatten an embedding into a numeric vector for cosine similarity.
4931
+ * Log-scales magnitude values with log(1+x).
4932
+ */
4933
+ flattenEmbedding(e) {
4934
+ const vec = [];
4935
+ vec.push(Math.log(1 + e.temporal.meanIntervalSeconds));
4936
+ vec.push(Math.log(1 + e.temporal.stddevIntervalSeconds));
4937
+ vec.push(...e.temporal.hourHistogram);
4938
+ vec.push(...e.temporal.dayHistogram);
4939
+ vec.push(Math.log(1 + e.financial.meanAmount));
4940
+ vec.push(Math.log(1 + e.financial.stddevAmount));
4941
+ vec.push(Math.log(1 + e.financial.medianAmount));
4942
+ vec.push(e.financial.roundAmountRatio);
4943
+ vec.push(...e.financial.percentiles.map((p) => Math.log(1 + p)));
4944
+ vec.push(Math.log(1 + e.network.uniqueDestinations));
4945
+ vec.push(e.network.reuseRatio);
4946
+ vec.push(e.network.concentrationIndex);
4947
+ vec.push(Math.log(1 + e.network.uniqueSources));
4948
+ const chainValues = Object.values(e.operational.chainDistribution).sort(
4949
+ (a, b) => b - a
4950
+ );
4951
+ const tokenValues = Object.values(e.operational.tokenDistribution).sort(
4952
+ (a, b) => b - a
4953
+ );
4954
+ for (let i = 0; i < 5; i++) {
4955
+ vec.push(chainValues[i] ?? 0);
4956
+ }
4957
+ for (let i = 0; i < 5; i++) {
4958
+ vec.push(tokenValues[i] ?? 0);
4959
+ }
4960
+ return vec;
4961
+ }
4962
+ };
4963
+
4964
+ // src/kya/cross-session-linker.ts
4965
+ var WALLET_OVERLAP_WEIGHT = 0.4;
4966
+ var BEHAVIORAL_SIMILARITY_WEIGHT = 0.35;
4967
+ var DECLARED_IDENTITY_WEIGHT = 0.25;
4968
+ var CrossSessionLinker = class {
4969
+ config;
4970
+ links = /* @__PURE__ */ new Map();
4971
+ constructor(config = {}) {
4972
+ this.config = {
4973
+ minBehavioralSimilarity: config.minBehavioralSimilarity ?? 0.85,
4974
+ minLinkConfidence: config.minLinkConfidence ?? 0.6
4975
+ };
4976
+ }
4977
+ /**
4978
+ * Analyze all agents and create links between those with sufficient signals.
4979
+ */
4980
+ analyzeAndLink(store, registry, clusterer, fingerprinter) {
4981
+ const newLinks = [];
4982
+ const agentIds = /* @__PURE__ */ new Set();
4983
+ for (const tx of store.getTransactions()) {
4984
+ agentIds.add(tx.agentId);
4985
+ }
4986
+ for (const action of store.getActions()) {
4987
+ agentIds.add(action.agentId);
4988
+ }
4989
+ const agents = Array.from(agentIds);
4990
+ const clusters = clusterer.getClusters();
4991
+ for (let i = 0; i < agents.length; i++) {
4992
+ for (let j = i + 1; j < agents.length; j++) {
4993
+ const agentA = agents[i];
4994
+ const agentB = agents[j];
4995
+ const existingKey = this.getLinkKey(agentA, agentB);
4996
+ if (this.links.has(existingKey)) continue;
4997
+ const signals = [];
4998
+ const walletOverlap = this.computeWalletOverlap(
4999
+ agentA,
5000
+ agentB,
5001
+ clusters
5002
+ );
5003
+ if (walletOverlap > 0) {
5004
+ signals.push({
5005
+ type: "wallet-overlap",
5006
+ strength: walletOverlap,
5007
+ weight: WALLET_OVERLAP_WEIGHT
5008
+ });
5009
+ }
5010
+ const embeddingA = fingerprinter.computeEmbedding(agentA, store);
5011
+ const embeddingB = fingerprinter.computeEmbedding(agentB, store);
5012
+ if (embeddingA && embeddingB) {
5013
+ const similarity = fingerprinter.cosineSimilarity(embeddingA, embeddingB);
5014
+ if (similarity >= this.config.minBehavioralSimilarity) {
5015
+ signals.push({
5016
+ type: "behavioral-similarity",
5017
+ strength: similarity,
5018
+ weight: BEHAVIORAL_SIMILARITY_WEIGHT
5019
+ });
5020
+ }
5021
+ }
5022
+ const declaredStrength = this.computeDeclaredIdentitySignal(
5023
+ agentA,
5024
+ agentB,
5025
+ registry
5026
+ );
5027
+ if (declaredStrength > 0) {
5028
+ signals.push({
5029
+ type: "declared-identity",
5030
+ strength: declaredStrength,
5031
+ weight: DECLARED_IDENTITY_WEIGHT
5032
+ });
5033
+ }
5034
+ if (signals.length === 0) continue;
5035
+ const confidence = signals.reduce((sum, s) => sum + s.strength * s.weight, 0) / signals.reduce((sum, s) => sum + s.weight, 0);
5036
+ if (confidence >= this.config.minLinkConfidence) {
5037
+ const link = {
5038
+ id: generateId(),
5039
+ agentIdA: agentA,
5040
+ agentIdB: agentB,
5041
+ confidence,
5042
+ signals,
5043
+ status: "inferred",
5044
+ createdAt: now()
5045
+ };
5046
+ this.links.set(existingKey, link);
5047
+ newLinks.push(link);
5048
+ }
5049
+ }
5050
+ }
5051
+ return newLinks;
5052
+ }
5053
+ /**
5054
+ * Manually link two agents.
5055
+ */
5056
+ manualLink(agentIdA, agentIdB, reviewedBy) {
5057
+ const key = this.getLinkKey(agentIdA, agentIdB);
5058
+ const existing = this.links.get(key);
5059
+ if (existing) {
5060
+ existing.status = "confirmed";
5061
+ existing.reviewedBy = reviewedBy;
5062
+ existing.reviewedAt = now();
5063
+ return { ...existing };
5064
+ }
5065
+ const link = {
5066
+ id: generateId(),
5067
+ agentIdA,
5068
+ agentIdB,
5069
+ confidence: 1,
5070
+ signals: [
5071
+ {
5072
+ type: "declared-identity",
5073
+ strength: 1,
5074
+ weight: 1,
5075
+ detail: `Manually linked by ${reviewedBy}`
5076
+ }
5077
+ ],
5078
+ status: "confirmed",
5079
+ createdAt: now(),
5080
+ reviewedBy,
5081
+ reviewedAt: now()
5082
+ };
5083
+ this.links.set(key, link);
5084
+ return { ...link };
5085
+ }
5086
+ /**
5087
+ * Review a link (confirm or reject).
5088
+ */
5089
+ reviewLink(linkId, decision, reviewedBy) {
5090
+ for (const link of this.links.values()) {
5091
+ if (link.id === linkId) {
5092
+ link.status = decision;
5093
+ link.reviewedBy = reviewedBy;
5094
+ link.reviewedAt = now();
5095
+ return { ...link };
5096
+ }
5097
+ }
5098
+ return void 0;
5099
+ }
5100
+ /**
5101
+ * Get all agents linked to the given agent.
5102
+ */
5103
+ getLinkedAgents(agentId) {
5104
+ const linked = /* @__PURE__ */ new Set();
5105
+ for (const link of this.links.values()) {
5106
+ if (link.status === "rejected") continue;
5107
+ if (link.agentIdA === agentId) linked.add(link.agentIdB);
5108
+ if (link.agentIdB === agentId) linked.add(link.agentIdA);
5109
+ }
5110
+ return Array.from(linked);
5111
+ }
5112
+ /**
5113
+ * Get all links for a specific agent.
5114
+ */
5115
+ getLinksForAgent(agentId) {
5116
+ const result = [];
5117
+ for (const link of this.links.values()) {
5118
+ if (link.agentIdA === agentId || link.agentIdB === agentId) {
5119
+ result.push({ ...link });
5120
+ }
5121
+ }
5122
+ return result;
5123
+ }
5124
+ /**
5125
+ * Get all links.
5126
+ */
5127
+ getAllLinks() {
5128
+ return Array.from(this.links.values()).map((l) => ({ ...l }));
5129
+ }
5130
+ // --------------------------------------------------------------------------
5131
+ // Private Helpers
5132
+ // --------------------------------------------------------------------------
5133
+ getLinkKey(a, b) {
5134
+ return a < b ? `${a}:${b}` : `${b}:${a}`;
5135
+ }
5136
+ computeWalletOverlap(agentA, agentB, clusters) {
5137
+ const clustersA = /* @__PURE__ */ new Set();
5138
+ const clustersB = /* @__PURE__ */ new Set();
5139
+ for (const cluster of clusters) {
5140
+ if (cluster.agentIds.includes(agentA)) {
5141
+ for (const addr of cluster.addresses) clustersA.add(addr);
5142
+ }
5143
+ if (cluster.agentIds.includes(agentB)) {
5144
+ for (const addr of cluster.addresses) clustersB.add(addr);
5145
+ }
5146
+ }
5147
+ if (clustersA.size === 0 || clustersB.size === 0) return 0;
5148
+ const intersection = new Set([...clustersA].filter((a) => clustersB.has(a)));
5149
+ const union = /* @__PURE__ */ new Set([...clustersA, ...clustersB]);
5150
+ return union.size > 0 ? intersection.size / union.size : 0;
5151
+ }
5152
+ computeDeclaredIdentitySignal(agentA, agentB, registry) {
5153
+ const identityA = registry.get(agentA);
5154
+ const identityB = registry.get(agentB);
5155
+ if (!identityA || !identityB) return 0;
5156
+ const walletsA = new Set(identityA.wallets.map((w) => w.address));
5157
+ const walletsB = new Set(identityB.wallets.map((w) => w.address));
5158
+ const sharedWallets = [...walletsA].filter((w) => walletsB.has(w));
5159
+ if (sharedWallets.length > 0) return 1;
5160
+ const kycRefsA = new Set(
5161
+ identityA.kycReferences.map((r) => `${r.provider}:${r.referenceId}`)
5162
+ );
5163
+ const kycRefsB = new Set(
5164
+ identityB.kycReferences.map((r) => `${r.provider}:${r.referenceId}`)
5165
+ );
5166
+ const sharedKyc = [...kycRefsA].filter((r) => kycRefsB.has(r));
5167
+ if (sharedKyc.length > 0) return 1;
5168
+ return 0;
5169
+ }
5170
+ };
5171
+
5172
+ // src/kya/confidence-scorer.ts
5173
+ var KYAConfidenceScorer = class {
5174
+ weights;
5175
+ constructor(config = {}) {
5176
+ this.weights = {
5177
+ declaredIdentity: config.declaredIdentityWeight ?? 0.2,
5178
+ kycVerification: config.kycVerificationWeight ?? 0.3,
5179
+ walletGraph: config.walletGraphWeight ?? 0.2,
5180
+ behavioralConsistency: config.behavioralConsistencyWeight ?? 0.2,
5181
+ externalEnrichment: config.externalEnrichmentWeight ?? 0.1
5182
+ };
5183
+ }
5184
+ /**
5185
+ * Compute a composite confidence score for an agent.
5186
+ */
5187
+ computeScore(agentId, registry, clusterer, fingerprinter, linker, store) {
5188
+ const components = [];
5189
+ const declaredScore = this.scoreDeclaredIdentity(agentId, registry);
5190
+ components.push({
5191
+ name: "Declared Identity",
5192
+ score: declaredScore.score,
5193
+ weight: this.weights.declaredIdentity,
5194
+ weightedScore: declaredScore.score * this.weights.declaredIdentity,
5195
+ detail: declaredScore.detail
5196
+ });
5197
+ const kycScore = this.scoreKycVerification(agentId, registry);
5198
+ components.push({
5199
+ name: "KYC Verification",
5200
+ score: kycScore.score,
5201
+ weight: this.weights.kycVerification,
5202
+ weightedScore: kycScore.score * this.weights.kycVerification,
5203
+ detail: kycScore.detail
5204
+ });
5205
+ const walletScore = this.scoreWalletGraph(agentId, clusterer);
5206
+ components.push({
5207
+ name: "Wallet Graph",
5208
+ score: walletScore.score,
5209
+ weight: this.weights.walletGraph,
5210
+ weightedScore: walletScore.score * this.weights.walletGraph,
5211
+ detail: walletScore.detail
5212
+ });
5213
+ const behavioralScore = this.scoreBehavioralConsistency(
5214
+ agentId,
5215
+ fingerprinter,
5216
+ linker,
5217
+ store
5218
+ );
5219
+ components.push({
5220
+ name: "Behavioral Consistency",
5221
+ score: behavioralScore.score,
5222
+ weight: this.weights.behavioralConsistency,
5223
+ weightedScore: behavioralScore.score * this.weights.behavioralConsistency,
5224
+ detail: behavioralScore.detail
5225
+ });
5226
+ const externalScore = this.scoreExternalEnrichment(agentId, registry);
5227
+ components.push({
5228
+ name: "External Enrichment",
5229
+ score: externalScore.score,
5230
+ weight: this.weights.externalEnrichment,
5231
+ weightedScore: externalScore.score * this.weights.externalEnrichment,
5232
+ detail: externalScore.detail
5233
+ });
5234
+ const totalWeight = components.reduce((sum, c) => sum + c.weight, 0);
5235
+ const overallScore = totalWeight > 0 ? Math.round(components.reduce((sum, c) => sum + c.weightedScore, 0) / totalWeight) : 0;
5236
+ return {
5237
+ agentId,
5238
+ score: overallScore,
5239
+ level: this.scoreToLevel(overallScore),
5240
+ components,
5241
+ computedAt: now()
5242
+ };
5243
+ }
5244
+ // --------------------------------------------------------------------------
5245
+ // Component Scorers
5246
+ // --------------------------------------------------------------------------
5247
+ scoreDeclaredIdentity(agentId, registry) {
5248
+ const identity = registry.get(agentId);
5249
+ if (!identity) return { score: 0, detail: "No declared identity" };
5250
+ let score = 30;
5251
+ const parts = ["identity registered"];
5252
+ if (identity.displayName) {
5253
+ score += 10;
5254
+ parts.push("displayName set");
5255
+ }
5256
+ if (identity.entityType !== "unknown") {
5257
+ score += 10;
5258
+ parts.push(`entityType: ${identity.entityType}`);
5259
+ }
5260
+ if (identity.wallets.length > 0) {
5261
+ score += 20;
5262
+ parts.push(`${identity.wallets.length} wallet(s)`);
5263
+ }
5264
+ if (identity.contactUri) {
5265
+ score += 10;
5266
+ parts.push("contactUri set");
5267
+ }
5268
+ if (Object.keys(identity.metadata).length > 0) {
5269
+ score += 20;
5270
+ parts.push("metadata provided");
5271
+ }
5272
+ return { score: Math.min(100, score), detail: parts.join(", ") };
5273
+ }
5274
+ scoreKycVerification(agentId, registry) {
5275
+ const identity = registry.get(agentId);
5276
+ if (!identity || identity.kycReferences.length === 0) {
5277
+ return { score: 0, detail: "No KYC verification" };
5278
+ }
5279
+ const refs = identity.kycReferences;
5280
+ const hasRejected = refs.some((r) => r.status === "rejected");
5281
+ const verifiedRefs = refs.filter((r) => r.status === "verified");
5282
+ const pendingRefs = refs.filter((r) => r.status === "pending");
5283
+ const currentTime = (/* @__PURE__ */ new Date()).toISOString();
5284
+ const activeVerified = verifiedRefs.filter(
5285
+ (r) => r.expiresAt === null || r.expiresAt > currentTime
5286
+ );
5287
+ if (hasRejected && verifiedRefs.length === 0) {
5288
+ return { score: 10, detail: "KYC rejected, capped at 10" };
5289
+ }
5290
+ if (pendingRefs.length > 0 && verifiedRefs.length === 0) {
5291
+ return { score: 20, detail: "KYC pending" };
5292
+ }
5293
+ if (verifiedRefs.length > 0) {
5294
+ let score = 90;
5295
+ const parts = [`${verifiedRefs.length} verified`];
5296
+ if (activeVerified.length > 0) {
5297
+ score = 100;
5298
+ parts.push("non-expired");
5299
+ }
5300
+ if (verifiedRefs.length > 1) {
5301
+ score = Math.min(100, score + 10);
5302
+ parts.push("multiple providers");
5303
+ }
5304
+ return { score, detail: parts.join(", ") };
5305
+ }
5306
+ return { score: 0, detail: "No KYC verification" };
5307
+ }
5308
+ scoreWalletGraph(agentId, clusterer) {
5309
+ const clusters = clusterer.getClusters();
5310
+ const agentClusters = clusters.filter((c) => c.agentIds.includes(agentId));
5311
+ if (agentClusters.length === 0) {
5312
+ return { score: 20, detail: "No wallet cluster" };
5313
+ }
5314
+ const totalAddresses = new Set(
5315
+ agentClusters.flatMap((c) => c.addresses)
5316
+ ).size;
5317
+ if (totalAddresses === 1) {
5318
+ return { score: 40, detail: "1 address in cluster" };
5319
+ }
5320
+ const heuristics = new Set(
5321
+ agentClusters.flatMap((c) => c.evidence.map((e) => e.heuristic))
5322
+ );
5323
+ const hasDeclared = heuristics.has("declared-wallets");
5324
+ const hasAuto = heuristics.size > (hasDeclared ? 1 : 0);
5325
+ if (hasDeclared && hasAuto) {
5326
+ return {
5327
+ score: 85,
5328
+ detail: `${totalAddresses} addresses, declared + auto heuristics`
5329
+ };
5330
+ }
5331
+ if (heuristics.size > 1) {
5332
+ return {
5333
+ score: 70,
5334
+ detail: `${totalAddresses} addresses, ${heuristics.size} heuristic types`
5335
+ };
5336
+ }
5337
+ return {
5338
+ score: 50,
5339
+ detail: `${totalAddresses} addresses, single heuristic`
5340
+ };
5341
+ }
5342
+ scoreBehavioralConsistency(agentId, fingerprinter, linker, store) {
5343
+ const embedding = fingerprinter.computeEmbedding(agentId, store);
5344
+ if (!embedding) {
5345
+ return { score: 30, detail: "Insufficient data for embedding" };
5346
+ }
5347
+ const links = linker.getLinksForAgent(agentId);
5348
+ const confirmedLinks = links.filter((l) => l.status === "confirmed");
5349
+ if (confirmedLinks.length === 0 && links.length === 0) {
5350
+ return { score: 50, detail: "Embedding computed, no links" };
5351
+ }
5352
+ let maxSimilarity = 0;
5353
+ for (const link of confirmedLinks) {
5354
+ const behavioralSignal = link.signals.find(
5355
+ (s) => s.type === "behavioral-similarity"
5356
+ );
5357
+ if (behavioralSignal && behavioralSignal.strength > maxSimilarity) {
5358
+ maxSimilarity = behavioralSignal.strength;
5359
+ }
5360
+ }
5361
+ if (maxSimilarity > 0.9) {
5362
+ return { score: 95, detail: `Confirmed link similarity: ${maxSimilarity.toFixed(2)}` };
5363
+ }
5364
+ if (maxSimilarity > 0.8) {
5365
+ return { score: 85, detail: `Confirmed link similarity: ${maxSimilarity.toFixed(2)}` };
5366
+ }
5367
+ if (confirmedLinks.length > 0) {
5368
+ return { score: 70, detail: `${confirmedLinks.length} confirmed link(s)` };
5369
+ }
5370
+ return { score: 50, detail: "Embedding computed, no confirmed links" };
5371
+ }
5372
+ scoreExternalEnrichment(agentId, registry) {
5373
+ const identity = registry.get(agentId);
5374
+ if (!identity) {
5375
+ return { score: 50, detail: "Default (neutral), no identity" };
5376
+ }
5377
+ const riskLevels = identity.kycReferences.filter((r) => r.riskLevel).map((r) => r.riskLevel);
5378
+ if (riskLevels.length === 0) {
5379
+ return { score: 50, detail: "Default (neutral), no risk data" };
5380
+ }
5381
+ if (riskLevels.includes("high")) {
5382
+ return { score: 20, detail: "High risk from external provider" };
5383
+ }
5384
+ if (riskLevels.includes("low")) {
5385
+ return { score: 80, detail: "Low risk from external provider" };
5386
+ }
5387
+ return { score: 50, detail: "Medium risk from external provider" };
5388
+ }
5389
+ // --------------------------------------------------------------------------
5390
+ // Helpers
5391
+ // --------------------------------------------------------------------------
5392
+ scoreToLevel(score) {
5393
+ if (score >= 85) return "verified";
5394
+ if (score >= 65) return "high";
5395
+ if (score >= 40) return "medium";
5396
+ if (score >= 20) return "low";
5397
+ return "unknown";
5398
+ }
5399
+ };
5400
+
5401
+ // src/client.ts
5402
+ var PLAN_STORAGE_KEY = "kontext:plan";
5403
+ var Kontext = class _Kontext {
5404
+ config;
5405
+ store;
5406
+ logger;
5407
+ taskManager;
5408
+ auditExporter;
5409
+ mode;
5410
+ planManager;
5411
+ exporter;
5412
+ featureFlagManager;
5413
+ trustScorer;
5414
+ anomalyDetector;
5415
+ screeningAggregator;
5416
+ provenanceManager = null;
5417
+ identityRegistry = null;
5418
+ walletClusterer = null;
5419
+ behavioralFingerprinter = null;
5420
+ crossSessionLinker = null;
5421
+ confidenceScorer = null;
5422
+ constructor(config) {
5423
+ this.config = config;
5424
+ this.mode = config.apiKey ? "cloud" : "local";
5425
+ this.store = new KontextStore();
5426
+ if (config.metadataSchema && typeof config.metadataSchema.parse !== "function") {
5427
+ throw new KontextError(
5428
+ "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
5429
+ "metadataSchema must have a parse() method"
5430
+ );
5431
+ }
5432
+ if (config.storage) {
5433
+ this.store.setStorageAdapter(config.storage);
5434
+ }
5435
+ const planTier = config.plan ?? "free";
5436
+ this.planManager = new PlanManager(planTier, void 0, config.seats ?? 1);
5437
+ if (config.upgradeUrl) {
5438
+ this.planManager.upgradeUrl = config.upgradeUrl;
5439
+ }
5440
+ this.exporter = config.exporter ?? new NoopExporter();
5441
+ this.logger = new ActionLogger(config, this.store);
5442
+ this.taskManager = new TaskManager(config, this.store);
5443
+ this.auditExporter = new AuditExporter(config, this.store);
5444
+ this.trustScorer = new TrustScorer(config, this.store);
5445
+ this.anomalyDetector = new AnomalyDetector(config, this.store);
5446
+ this.featureFlagManager = config.featureFlags ? new FeatureFlagManager(config.featureFlags) : null;
5447
+ this.screeningAggregator = config.screening ? new ScreeningAggregator({
5448
+ providers: config.screening.providers,
5449
+ consensus: config.screening.consensus,
5450
+ blocklist: config.screening.blocklist,
5451
+ allowlist: config.screening.allowlist,
5452
+ providerTimeoutMs: config.screening.providerTimeoutMs,
5453
+ onEvent: () => this.planManager.recordEvent()
5454
+ }) : null;
5455
+ if (config.anomalyRules && config.anomalyRules.length > 0) {
5456
+ const advancedRules = ["newDestination", "offHoursActivity", "rapidSuccession", "roundAmount"];
5457
+ const hasAdvanced = config.anomalyRules.some((r) => advancedRules.includes(r));
5458
+ if (hasAdvanced) {
5459
+ requirePlan("advanced-anomaly-rules", planTier);
5460
+ }
5461
+ this.anomalyDetector.enableAnomalyDetection({
5462
+ rules: config.anomalyRules,
5463
+ thresholds: config.anomalyThresholds
5464
+ });
5465
+ }
5466
+ }
5467
+ /**
5468
+ * Initialize the Kontext SDK.
5469
+ *
5470
+ * @param config - Configuration options
5471
+ * @returns Initialized Kontext client instance
5472
+ *
5473
+ * @example
5474
+ * ```typescript
5475
+ * // Local/OSS mode (no API key)
5476
+ * const kontext = Kontext.init({
5477
+ * projectId: 'my-project',
5478
+ * environment: 'development',
5479
+ * });
5480
+ *
5481
+ * // Cloud mode (with API key)
5482
+ * const kontext = Kontext.init({
5483
+ * apiKey: 'sk_live_...',
5484
+ * projectId: 'my-project',
5485
+ * environment: 'production',
5486
+ * });
5487
+ *
5488
+ * // With persistent file storage
5489
+ * const kontext = Kontext.init({
5490
+ * projectId: 'my-project',
5491
+ * environment: 'development',
5492
+ * storage: new FileStorage('./kontext-data'),
5493
+ * });
5494
+ * ```
5495
+ */
5496
+ static init(config) {
5497
+ if (!config.projectId || config.projectId.trim() === "") {
5498
+ throw new KontextError(
5499
+ "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
5500
+ "projectId is required"
5501
+ );
5502
+ }
5503
+ const validEnvironments = ["development", "staging", "production"];
5504
+ if (!validEnvironments.includes(config.environment)) {
5505
+ throw new KontextError(
5506
+ "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
5507
+ `Invalid environment: ${config.environment}. Must be one of: ${validEnvironments.join(", ")}`
5508
+ );
5509
+ }
5510
+ if (config.debug) {
5511
+ const mode = config.apiKey ? "cloud" : "local";
5512
+ console.debug(
5513
+ `[Kontext] Initializing in ${mode} mode for project ${config.projectId} (${config.environment})`
5514
+ );
5515
+ }
5516
+ return new _Kontext(config);
5517
+ }
5518
+ // --------------------------------------------------------------------------
5519
+ // Mode & Config
5520
+ // --------------------------------------------------------------------------
5521
+ /**
5522
+ * Get the current operating mode.
5523
+ */
5524
+ getMode() {
5525
+ return this.mode;
5526
+ }
5527
+ /**
5528
+ * Get the current configuration (API key is masked).
3543
5529
  */
3544
5530
  getConfig() {
3545
5531
  return {
@@ -3565,6 +5551,147 @@ var Kontext = class _Kontext {
3565
5551
  );
3566
5552
  }
3567
5553
  }
5554
+ /**
5555
+ * Map aggregated screening results into UsdcComplianceCheck format.
5556
+ * Produces the same check/riskLevel/recommendations shape as
5557
+ * UsdcCompliance.checkTransaction() and PaymentCompliance.checkPayment().
5558
+ */
5559
+ buildComplianceFromScreening(input, fromResult, toResult) {
5560
+ const checks = [];
5561
+ const fromHit = fromResult.hit;
5562
+ const fromProviders = fromResult.providerResults.filter((r) => r.hit).map((r) => r.providerId).join(", ");
5563
+ checks.push({
5564
+ name: "sanctions_sender",
5565
+ passed: !fromHit,
5566
+ description: fromHit ? `Sender flagged by: ${fromProviders}` : `Sender cleared (${fromResult.totalProviders} provider${fromResult.totalProviders !== 1 ? "s" : ""} checked)`,
5567
+ severity: fromHit ? "critical" : "low"
5568
+ });
5569
+ const toHit = toResult.hit;
5570
+ const toProviders = toResult.providerResults.filter((r) => r.hit).map((r) => r.providerId).join(", ");
5571
+ checks.push({
5572
+ name: "sanctions_recipient",
5573
+ passed: !toHit,
5574
+ description: toHit ? `Recipient flagged by: ${toProviders}` : `Recipient cleared (${toResult.totalProviders} provider${toResult.totalProviders !== 1 ? "s" : ""} checked)`,
5575
+ severity: toHit ? "critical" : "low"
5576
+ });
5577
+ const allUncovered = [
5578
+ .../* @__PURE__ */ new Set([...fromResult.uncoveredLists, ...toResult.uncoveredLists])
5579
+ ];
5580
+ if (allUncovered.length > 0) {
5581
+ checks.push({
5582
+ name: "jurisdiction_coverage",
5583
+ passed: false,
5584
+ description: `Required lists not covered: ${allUncovered.join(", ")}`,
5585
+ severity: "medium"
5586
+ });
5587
+ }
5588
+ const thresholds = this.config.policy?.thresholds;
5589
+ const eddThreshold = thresholds?.edd ?? 3e3;
5590
+ const reportingThreshold = thresholds?.reporting ?? 1e4;
5591
+ const largeThreshold = thresholds?.largeTransaction ?? 5e4;
5592
+ const amount = parseAmount(input.amount);
5593
+ if (amount >= eddThreshold) {
5594
+ checks.push({
5595
+ name: "enhanced_due_diligence",
5596
+ passed: false,
5597
+ description: `Amount $${amount.toLocaleString()} meets EDD threshold ($${eddThreshold.toLocaleString()})`,
5598
+ severity: "low"
5599
+ });
5600
+ }
5601
+ if (amount >= reportingThreshold) {
5602
+ checks.push({
5603
+ name: "reporting_threshold",
5604
+ passed: false,
5605
+ description: `Amount $${amount.toLocaleString()} meets CTR threshold ($${reportingThreshold.toLocaleString()})`,
5606
+ severity: "low"
5607
+ });
5608
+ }
5609
+ if (amount >= largeThreshold) {
5610
+ checks.push({
5611
+ name: "large_transaction",
5612
+ passed: false,
5613
+ description: `Large transaction: $${amount.toLocaleString()} exceeds $${largeThreshold.toLocaleString()}`,
5614
+ severity: "medium"
5615
+ });
5616
+ }
5617
+ const allErrors = [...fromResult.errors, ...toResult.errors];
5618
+ if (allErrors.length > 0) {
5619
+ checks.push({
5620
+ name: "screening_errors",
5621
+ passed: false,
5622
+ description: `Provider errors: ${allErrors.map((e) => `${e.providerId}: ${e.error}`).join("; ")}`,
5623
+ severity: "medium"
5624
+ });
5625
+ }
5626
+ const failedChecks = checks.filter((c) => !c.passed);
5627
+ const compliant = failedChecks.every((c) => c.severity === "low");
5628
+ const severityOrder = ["low", "medium", "high", "critical"];
5629
+ const highestSeverity = failedChecks.reduce(
5630
+ (max, c) => severityOrder.indexOf(c.severity) > severityOrder.indexOf(max) ? c.severity : max,
5631
+ "low"
5632
+ );
5633
+ const recommendations = [];
5634
+ if (fromHit || toHit) {
5635
+ recommendations.push("Block transaction: sanctions match detected");
5636
+ }
5637
+ if (allUncovered.length > 0) {
5638
+ recommendations.push(`Add providers covering: ${allUncovered.join(", ")}`);
5639
+ }
5640
+ if (amount >= eddThreshold && amount < reportingThreshold) {
5641
+ recommendations.push("Collect enhanced due diligence information");
5642
+ }
5643
+ if (amount >= reportingThreshold) {
5644
+ recommendations.push("File Currency Transaction Report (CTR)");
5645
+ }
5646
+ return {
5647
+ compliant,
5648
+ checks,
5649
+ riskLevel: highestSeverity,
5650
+ recommendations
5651
+ };
5652
+ }
5653
+ /** Lazy-init ProvenanceManager on first use. */
5654
+ getProvenanceManager() {
5655
+ if (!this.provenanceManager) {
5656
+ this.provenanceManager = new ProvenanceManager(this.store, this.logger);
5657
+ }
5658
+ return this.provenanceManager;
5659
+ }
5660
+ /** Lazy-init AgentIdentityRegistry on first use. */
5661
+ getIdentityRegistry() {
5662
+ if (!this.identityRegistry) {
5663
+ this.identityRegistry = new AgentIdentityRegistry();
5664
+ }
5665
+ return this.identityRegistry;
5666
+ }
5667
+ /** Lazy-init WalletClusterer on first use. */
5668
+ getWalletClusterer() {
5669
+ if (!this.walletClusterer) {
5670
+ this.walletClusterer = new WalletClusterer();
5671
+ }
5672
+ return this.walletClusterer;
5673
+ }
5674
+ /** Lazy-init BehavioralFingerprinter on first use. */
5675
+ getBehavioralFingerprinter() {
5676
+ if (!this.behavioralFingerprinter) {
5677
+ this.behavioralFingerprinter = new BehavioralFingerprinter();
5678
+ }
5679
+ return this.behavioralFingerprinter;
5680
+ }
5681
+ /** Lazy-init CrossSessionLinker on first use. */
5682
+ getCrossSessionLinker() {
5683
+ if (!this.crossSessionLinker) {
5684
+ this.crossSessionLinker = new CrossSessionLinker();
5685
+ }
5686
+ return this.crossSessionLinker;
5687
+ }
5688
+ /** Lazy-init KYAConfidenceScorer on first use. */
5689
+ getConfidenceScorer() {
5690
+ if (!this.confidenceScorer) {
5691
+ this.confidenceScorer = new KYAConfidenceScorer();
5692
+ }
5693
+ return this.confidenceScorer;
5694
+ }
3568
5695
  // --------------------------------------------------------------------------
3569
5696
  // Action Logging
3570
5697
  // --------------------------------------------------------------------------
@@ -3595,7 +5722,7 @@ var Kontext = class _Kontext {
3595
5722
  * @returns The created transaction record
3596
5723
  */
3597
5724
  async logTransaction(input) {
3598
- if (input.chain && input.chain !== "base") {
5725
+ if (input.chain && input.chain !== "base" && input.chain !== "arc") {
3599
5726
  requirePlan("multi-chain", this.planManager.getTier());
3600
5727
  }
3601
5728
  this.validateMetadata(input.metadata);
@@ -3950,7 +6077,28 @@ var Kontext = class _Kontext {
3950
6077
  */
3951
6078
  async verify(input) {
3952
6079
  const transaction = await this.logTransaction(input);
3953
- const compliance = isCryptoTransaction(input) ? UsdcCompliance.checkTransaction(input) : PaymentCompliance.checkPayment(input);
6080
+ let compliance;
6081
+ if (this.screeningAggregator) {
6082
+ const fromResult = await this.screeningAggregator.screen(input.from, {
6083
+ chain: input.chain,
6084
+ token: input.token,
6085
+ currency: input.currency,
6086
+ amount: input.amount,
6087
+ agentId: input.agentId
6088
+ });
6089
+ const toResult = await this.screeningAggregator.screen(input.to, {
6090
+ chain: input.chain,
6091
+ token: input.token,
6092
+ currency: input.currency,
6093
+ amount: input.amount,
6094
+ agentId: input.agentId
6095
+ });
6096
+ compliance = this.buildComplianceFromScreening(input, fromResult, toResult);
6097
+ } else if (isCryptoTransaction(input)) {
6098
+ compliance = UsdcCompliance.checkTransaction(input);
6099
+ } else {
6100
+ compliance = PaymentCompliance.checkPayment(input);
6101
+ }
3954
6102
  let reasoningId;
3955
6103
  if (input.reasoning) {
3956
6104
  const entry = await this.logReasoning({
@@ -3990,6 +6138,11 @@ var Kontext = class _Kontext {
3990
6138
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3991
6139
  });
3992
6140
  }
6141
+ let attribution;
6142
+ if (input.erc8021 && input.txHash) {
6143
+ const { fetchTransactionAttribution: fetchTransactionAttribution2 } = await Promise.resolve().then(() => (init_erc8021(), erc8021_exports));
6144
+ attribution = await fetchTransactionAttribution2(input.erc8021.rpcUrl, input.txHash) ?? void 0;
6145
+ }
3993
6146
  let requiresApproval;
3994
6147
  let task;
3995
6148
  if (this.config.approvalThreshold) {
@@ -4033,7 +6186,8 @@ var Kontext = class _Kontext {
4033
6186
  ...reasoningId ? { reasoningId } : {},
4034
6187
  ...requiresApproval ? { requiresApproval, task } : {},
4035
6188
  ...anchorProof ? { anchorProof } : {},
4036
- ...counterpartyResult ? { counterparty: counterpartyResult } : {}
6189
+ ...counterpartyResult ? { counterparty: counterpartyResult } : {},
6190
+ ...attribution ? { attribution } : {}
4037
6191
  };
4038
6192
  }
4039
6193
  // --------------------------------------------------------------------------
@@ -4213,10 +6367,211 @@ var Kontext = class _Kontext {
4213
6367
  actions: actionSummary,
4214
6368
  reasoning: reasoningEntries
4215
6369
  };
4216
- const hash = createHash("sha256");
4217
- hash.update(JSON.stringify(certificateContent));
4218
- const contentHash = hash.digest("hex");
4219
- return { ...certificateContent, contentHash };
6370
+ const hash = createHash("sha256");
6371
+ hash.update(JSON.stringify(certificateContent));
6372
+ const contentHash = hash.digest("hex");
6373
+ return { ...certificateContent, contentHash };
6374
+ }
6375
+ // --------------------------------------------------------------------------
6376
+ // Agent Provenance
6377
+ // --------------------------------------------------------------------------
6378
+ /**
6379
+ * Create a delegated agent session. Records the delegation in the
6380
+ * tamper-evident digest chain as the session's genesis event.
6381
+ */
6382
+ async createAgentSession(input) {
6383
+ return this.getProvenanceManager().createSession(input);
6384
+ }
6385
+ /**
6386
+ * Get an agent session by ID. Automatically marks expired sessions.
6387
+ */
6388
+ getAgentSession(sessionId) {
6389
+ return this.getProvenanceManager().getSession(sessionId);
6390
+ }
6391
+ /**
6392
+ * Get all agent sessions.
6393
+ */
6394
+ getAgentSessions() {
6395
+ return this.getProvenanceManager().getSessions();
6396
+ }
6397
+ /**
6398
+ * End an active agent session. Records the termination in the digest chain.
6399
+ */
6400
+ async endAgentSession(sessionId) {
6401
+ return this.getProvenanceManager().endSession(sessionId);
6402
+ }
6403
+ /**
6404
+ * Check whether an action is within a session's delegated scope.
6405
+ */
6406
+ validateSessionScope(sessionId, action) {
6407
+ return this.getProvenanceManager().validateScope(sessionId, action);
6408
+ }
6409
+ /**
6410
+ * Get all actions bound to a session (via sessionId on log/verify calls).
6411
+ */
6412
+ getSessionActions(sessionId) {
6413
+ return this.store.getActionsBySession(sessionId);
6414
+ }
6415
+ /**
6416
+ * Create a provenance checkpoint -- a review point where a human
6417
+ * can attest to a batch of agent actions.
6418
+ */
6419
+ async createCheckpoint(input) {
6420
+ return this.getProvenanceManager().createCheckpoint(input);
6421
+ }
6422
+ /**
6423
+ * Attach an externally-produced human attestation to a checkpoint.
6424
+ * The attestation includes a cryptographic signature that the agent
6425
+ * never touches -- key separation is the critical security property.
6426
+ */
6427
+ async attachAttestation(checkpointId, attestation) {
6428
+ return this.getProvenanceManager().attachAttestation(checkpointId, attestation);
6429
+ }
6430
+ /**
6431
+ * Get a checkpoint by ID.
6432
+ */
6433
+ getCheckpoint(checkpointId) {
6434
+ return this.getProvenanceManager().getCheckpoint(checkpointId);
6435
+ }
6436
+ /**
6437
+ * Get all checkpoints, optionally filtered by session.
6438
+ */
6439
+ getCheckpoints(sessionId) {
6440
+ return this.getProvenanceManager().getCheckpoints(sessionId);
6441
+ }
6442
+ /**
6443
+ * Export the full provenance bundle for a session: session record,
6444
+ * all bound actions, all checkpoints, and verification stats.
6445
+ */
6446
+ getProvenanceBundle(sessionId) {
6447
+ return this.getProvenanceManager().getProvenanceBundle(sessionId);
6448
+ }
6449
+ // --------------------------------------------------------------------------
6450
+ // Agent Forensics (KYA)
6451
+ // --------------------------------------------------------------------------
6452
+ /**
6453
+ * Register a new agent identity with optional wallet mappings.
6454
+ * Requires Pro plan.
6455
+ */
6456
+ registerAgentIdentity(input) {
6457
+ requirePlan("kya-identity", this.planManager.getTier());
6458
+ return this.getIdentityRegistry().register(input);
6459
+ }
6460
+ /**
6461
+ * Get a registered agent identity by ID.
6462
+ * Requires Pro plan.
6463
+ */
6464
+ getAgentIdentity(agentId) {
6465
+ requirePlan("kya-identity", this.planManager.getTier());
6466
+ return this.getIdentityRegistry().get(agentId);
6467
+ }
6468
+ /**
6469
+ * Update an existing agent identity.
6470
+ * Requires Pro plan.
6471
+ */
6472
+ updateAgentIdentity(agentId, input) {
6473
+ requirePlan("kya-identity", this.planManager.getTier());
6474
+ return this.getIdentityRegistry().update(agentId, input);
6475
+ }
6476
+ /**
6477
+ * Remove an agent identity.
6478
+ * Requires Pro plan.
6479
+ */
6480
+ removeAgentIdentity(agentId) {
6481
+ requirePlan("kya-identity", this.planManager.getTier());
6482
+ return this.getIdentityRegistry().remove(agentId);
6483
+ }
6484
+ /**
6485
+ * Add a wallet to an existing agent identity.
6486
+ * Requires Pro plan.
6487
+ */
6488
+ addAgentWallet(agentId, wallet) {
6489
+ requirePlan("kya-identity", this.planManager.getTier());
6490
+ return this.getIdentityRegistry().addWallet(agentId, wallet);
6491
+ }
6492
+ /**
6493
+ * Look up which agent owns a wallet address.
6494
+ * Requires Pro plan.
6495
+ */
6496
+ lookupAgentByWallet(address) {
6497
+ requirePlan("kya-identity", this.planManager.getTier());
6498
+ return this.getIdentityRegistry().lookupByWallet(address);
6499
+ }
6500
+ /**
6501
+ * Compute wallet clusters from transaction patterns and declared identities.
6502
+ * Requires Pro plan.
6503
+ */
6504
+ getWalletClusters() {
6505
+ requirePlan("kya-identity", this.planManager.getTier());
6506
+ const clusterer = this.getWalletClusterer();
6507
+ clusterer.analyzeFromStore(this.store, this.getIdentityRegistry());
6508
+ return clusterer.getClusters();
6509
+ }
6510
+ /**
6511
+ * Export all KYA data as a single envelope.
6512
+ * Requires Pro plan.
6513
+ */
6514
+ getKYAExport() {
6515
+ requirePlan("kya-identity", this.planManager.getTier());
6516
+ const registry = this.getIdentityRegistry();
6517
+ const clusterer = this.getWalletClusterer();
6518
+ clusterer.analyzeFromStore(this.store, registry);
6519
+ return {
6520
+ identities: registry.getAll(),
6521
+ clusters: clusterer.getClusters(),
6522
+ embeddings: [],
6523
+ links: [],
6524
+ scores: [],
6525
+ generatedAt: now()
6526
+ };
6527
+ }
6528
+ /**
6529
+ * Compute a behavioral embedding for an agent from transaction history.
6530
+ * Returns null if insufficient data. Requires Enterprise plan.
6531
+ */
6532
+ computeBehavioralEmbedding(agentId) {
6533
+ requirePlan("kya-behavioral", this.planManager.getTier());
6534
+ return this.getBehavioralFingerprinter().computeEmbedding(agentId, this.store);
6535
+ }
6536
+ /**
6537
+ * Analyze all agents and create cross-session links.
6538
+ * Requires Enterprise plan.
6539
+ */
6540
+ analyzeAgentLinks() {
6541
+ requirePlan("kya-behavioral", this.planManager.getTier());
6542
+ const clusterer = this.getWalletClusterer();
6543
+ clusterer.analyzeFromStore(this.store, this.getIdentityRegistry());
6544
+ return this.getCrossSessionLinker().analyzeAndLink(
6545
+ this.store,
6546
+ this.getIdentityRegistry(),
6547
+ clusterer,
6548
+ this.getBehavioralFingerprinter()
6549
+ );
6550
+ }
6551
+ /**
6552
+ * Get agents linked to a specific agent.
6553
+ * Requires Enterprise plan.
6554
+ */
6555
+ getLinkedAgents(agentId) {
6556
+ requirePlan("kya-behavioral", this.planManager.getTier());
6557
+ return this.getCrossSessionLinker().getLinkedAgents(agentId);
6558
+ }
6559
+ /**
6560
+ * Compute a composite identity confidence score for an agent.
6561
+ * Requires Enterprise plan.
6562
+ */
6563
+ getKYAConfidenceScore(agentId) {
6564
+ requirePlan("kya-behavioral", this.planManager.getTier());
6565
+ const clusterer = this.getWalletClusterer();
6566
+ clusterer.analyzeFromStore(this.store, this.getIdentityRegistry());
6567
+ return this.getConfidenceScorer().computeScore(
6568
+ agentId,
6569
+ this.getIdentityRegistry(),
6570
+ clusterer,
6571
+ this.getBehavioralFingerprinter(),
6572
+ this.getCrossSessionLinker(),
6573
+ this.store
6574
+ );
4220
6575
  }
4221
6576
  // --------------------------------------------------------------------------
4222
6577
  // Plan & Usage Metering
@@ -4416,8 +6771,712 @@ var FileStorage = class {
4416
6771
 
4417
6772
  // src/index.ts
4418
6773
  init_onchain();
6774
+ init_erc8021();
4419
6775
  init_attestation();
4420
6776
 
4421
- export { AnomalyDetector, ConsoleExporter, DigestChain, FeatureFlagManager, FileStorage, JsonFileExporter, Kontext, KontextError, KontextErrorCode, MemoryStorage, NoopExporter, OnChainExporter, PLAN_LIMITS, PaymentCompliance, PlanManager, TrustScorer, UsdcCompliance, anchorDigest, exchangeAttestation, fetchAgentCard, getAnchor, isCryptoTransaction, isFeatureAvailable, requirePlan, verifyAnchor, verifyExportedChain };
6777
+ // src/integrations/provider-treasury-sdn.ts
6778
+ var ACTIVE_SET = new Set(
6779
+ OFAC_SDN_ACTIVE_ADDRESSES.map((a) => a.toLowerCase())
6780
+ );
6781
+ var DELISTED_SET = new Set(
6782
+ OFAC_SDN_DELISTED_ADDRESSES.map((a) => a.toLowerCase())
6783
+ );
6784
+ var ALL_SET = new Set(
6785
+ OFAC_SDN_ADDRESSES.map((a) => a.toLowerCase())
6786
+ );
6787
+ var ORIGINAL_CASE = /* @__PURE__ */ new Map();
6788
+ for (const addr of OFAC_SDN_ADDRESSES) {
6789
+ ORIGINAL_CASE.set(addr.toLowerCase(), addr);
6790
+ }
6791
+ var OFACAddressProvider = class {
6792
+ id = "ofac-sdn-address";
6793
+ name = "OFAC SDN Address Screener";
6794
+ lists = ["OFAC_SDN"];
6795
+ requiresApiKey = false;
6796
+ browserCompatible = true;
6797
+ queryTypes = ["address"];
6798
+ async screen(query, _context) {
6799
+ const start = Date.now();
6800
+ const lower = query.toLowerCase();
6801
+ const matches = [];
6802
+ if (ACTIVE_SET.has(lower)) {
6803
+ const originalAddr = ORIGINAL_CASE.get(lower) ?? query;
6804
+ matches.push({
6805
+ list: "OFAC_SDN",
6806
+ matchType: "exact_address",
6807
+ similarity: 1,
6808
+ matchedValue: originalAddr,
6809
+ entityStatus: "active",
6810
+ program: "SDN"
6811
+ });
6812
+ } else if (DELISTED_SET.has(lower)) {
6813
+ const originalAddr = ORIGINAL_CASE.get(lower) ?? query;
6814
+ matches.push({
6815
+ list: "OFAC_SDN",
6816
+ matchType: "exact_address",
6817
+ similarity: 1,
6818
+ matchedValue: originalAddr,
6819
+ entityStatus: "delisted",
6820
+ program: "SDN_DELISTED"
6821
+ });
6822
+ }
6823
+ const hasActiveHit = matches.some((m) => m.entityStatus === "active");
6824
+ return {
6825
+ providerId: this.id,
6826
+ hit: hasActiveHit,
6827
+ matches,
6828
+ listsChecked: this.lists,
6829
+ entriesSearched: ALL_SET.size,
6830
+ durationMs: Date.now() - start
6831
+ };
6832
+ }
6833
+ isAvailable() {
6834
+ return true;
6835
+ }
6836
+ getEntryCount() {
6837
+ return ALL_SET.size;
6838
+ }
6839
+ };
6840
+
6841
+ // src/integrations/provider-ofac-entity.ts
6842
+ var NAME_MATCH_THRESHOLD2 = 0.85;
6843
+ var MIN_MATCH_LENGTH = 4;
6844
+ var _screener2 = null;
6845
+ var _screenerLoaded2 = false;
6846
+ function getScreener2() {
6847
+ if (!_screenerLoaded2) {
6848
+ _screenerLoaded2 = true;
6849
+ try {
6850
+ const mod = __require("./ofac-sanctions.js");
6851
+ if (mod.OFACSanctionsScreener) {
6852
+ _screener2 = new mod.OFACSanctionsScreener();
6853
+ }
6854
+ } catch {
6855
+ }
6856
+ }
6857
+ return _screener2;
6858
+ }
6859
+ var OFACEntityProvider = class {
6860
+ id = "ofac-sdn-entity";
6861
+ name = "OFAC SDN Entity Screener";
6862
+ lists = ["OFAC_SDN"];
6863
+ requiresApiKey = false;
6864
+ browserCompatible = false;
6865
+ queryTypes = ["entity_name"];
6866
+ async screen(query, _context) {
6867
+ const start = Date.now();
6868
+ const screener = getScreener2();
6869
+ if (!screener) {
6870
+ return {
6871
+ providerId: this.id,
6872
+ hit: false,
6873
+ matches: [],
6874
+ listsChecked: this.lists,
6875
+ entriesSearched: 0,
6876
+ durationMs: Date.now() - start,
6877
+ error: "OFACSanctionsScreener not available"
6878
+ };
6879
+ }
6880
+ const rawMatches = screener.searchEntityName(query, NAME_MATCH_THRESHOLD2);
6881
+ const filteredMatches = rawMatches.filter(
6882
+ (m) => m.similarity >= NAME_MATCH_THRESHOLD2 && m.matchedOn.length >= MIN_MATCH_LENGTH
6883
+ );
6884
+ const matches = filteredMatches.map((m) => {
6885
+ const isActive = m.entity.list !== "DELISTED";
6886
+ return {
6887
+ list: "OFAC_SDN",
6888
+ matchType: m.similarity >= 0.99 ? "exact_address" : "fuzzy_name",
6889
+ similarity: m.similarity,
6890
+ matchedValue: m.matchedOn,
6891
+ entityStatus: isActive ? "active" : "delisted",
6892
+ entityName: m.entity.name,
6893
+ program: m.entity.programs?.[0]
6894
+ };
6895
+ });
6896
+ const hasActiveHit = matches.some((m) => m.entityStatus === "active");
6897
+ return {
6898
+ providerId: this.id,
6899
+ hit: hasActiveHit,
6900
+ matches,
6901
+ listsChecked: this.lists,
6902
+ entriesSearched: screener.getEntityCount?.() ?? 0,
6903
+ durationMs: Date.now() - start
6904
+ };
6905
+ }
6906
+ isAvailable() {
6907
+ return getScreener2() !== null;
6908
+ }
6909
+ getEntryCount() {
6910
+ const screener = getScreener2();
6911
+ return screener?.getEntityCount?.() ?? 0;
6912
+ }
6913
+ };
6914
+
6915
+ // src/integrations/data/uk-ofsi-addresses.ts
6916
+ var UK_OFSI_ADDRESSES = [
6917
+ // Garantex (designated under Russia sanctions regime, May 2024)
6918
+ "0x6F1cA141A28907F78Ebaa64f83E4AE6038d3cbe7",
6919
+ // Lazarus Group / DPRK (overlaps with OFAC SDN but independently listed by OFSI)
6920
+ "0x098B716B8Aaf21512996dC57EB0615e2383E2f96",
6921
+ "0xa0e1c89Ef1a489c9C7dE96311eD5Ce5D32c20E4B",
6922
+ "0x3Cffd56B47B7b41c56258D9C7731ABaDc360E460",
6923
+ "0x53b6936513e738f44FB50d2b9476730C0Ab3Bfc1",
6924
+ // DPRK-linked addresses (OFSI cyber programme)
6925
+ "0x7F367cC41522cE07553e823bf3be79A889DEbe1B",
6926
+ "0x01e2919679362dFBC9ee1644Ba9C6da6D6245BB1"
6927
+ ];
6928
+
6929
+ // src/integrations/provider-uk-ofsi.ts
6930
+ var ADDRESS_SET = new Set(
6931
+ UK_OFSI_ADDRESSES.map((a) => a.toLowerCase())
6932
+ );
6933
+ var ORIGINAL_CASE2 = /* @__PURE__ */ new Map();
6934
+ for (const addr of UK_OFSI_ADDRESSES) {
6935
+ ORIGINAL_CASE2.set(addr.toLowerCase(), addr);
6936
+ }
6937
+ var UKOFSIProvider = class {
6938
+ id = "uk-ofsi-address";
6939
+ name = "UK OFSI Address Screener";
6940
+ lists = ["UK_OFSI"];
6941
+ requiresApiKey = false;
6942
+ browserCompatible = true;
6943
+ queryTypes = ["address"];
6944
+ async screen(query, _context) {
6945
+ const start = Date.now();
6946
+ const lower = query.toLowerCase();
6947
+ const matches = [];
6948
+ if (ADDRESS_SET.has(lower)) {
6949
+ const originalAddr = ORIGINAL_CASE2.get(lower) ?? query;
6950
+ matches.push({
6951
+ list: "UK_OFSI",
6952
+ matchType: "exact_address",
6953
+ similarity: 1,
6954
+ matchedValue: originalAddr,
6955
+ entityStatus: "active",
6956
+ program: "OFSI_CONSOLIDATED"
6957
+ });
6958
+ }
6959
+ return {
6960
+ providerId: this.id,
6961
+ hit: matches.length > 0,
6962
+ matches,
6963
+ listsChecked: this.lists,
6964
+ entriesSearched: ADDRESS_SET.size,
6965
+ durationMs: Date.now() - start
6966
+ };
6967
+ }
6968
+ isAvailable() {
6969
+ return true;
6970
+ }
6971
+ getEntryCount() {
6972
+ return ADDRESS_SET.size;
6973
+ }
6974
+ };
6975
+
6976
+ // src/integrations/provider-opensanctions-local.ts
6977
+ var NAME_MATCH_THRESHOLD3 = 0.85;
6978
+ var MIN_MATCH_LENGTH2 = 4;
6979
+ var DEFAULT_DATA_DIR = ".kontext/sanctions";
6980
+ function trigramSimilarity(a, b) {
6981
+ const aNorm = a.toLowerCase().trim();
6982
+ const bNorm = b.toLowerCase().trim();
6983
+ if (aNorm === bNorm) return 1;
6984
+ if (aNorm.length < 2 || bNorm.length < 2) return 0;
6985
+ const trigramsA = /* @__PURE__ */ new Set();
6986
+ const trigramsB = /* @__PURE__ */ new Set();
6987
+ for (let i = 0; i <= aNorm.length - 3; i++) {
6988
+ trigramsA.add(aNorm.slice(i, i + 3));
6989
+ }
6990
+ for (let i = 0; i <= bNorm.length - 3; i++) {
6991
+ trigramsB.add(bNorm.slice(i, i + 3));
6992
+ }
6993
+ if (trigramsA.size === 0 || trigramsB.size === 0) return 0;
6994
+ let intersection = 0;
6995
+ for (const t of trigramsA) {
6996
+ if (trigramsB.has(t)) intersection++;
6997
+ }
6998
+ return 2 * intersection / (trigramsA.size + trigramsB.size);
6999
+ }
7000
+ var OpenSanctionsLocalProvider = class {
7001
+ id = "opensanctions-local";
7002
+ name = "OpenSanctions (Local Data)";
7003
+ lists = ["OPENSANCTIONS"];
7004
+ requiresApiKey = false;
7005
+ browserCompatible = false;
7006
+ queryTypes = ["both"];
7007
+ dataDir;
7008
+ entities = [];
7009
+ addressSet = /* @__PURE__ */ new Set();
7010
+ addressToEntity = /* @__PURE__ */ new Map();
7011
+ loaded = false;
7012
+ constructor(dataDir) {
7013
+ this.dataDir = dataDir ?? this.resolveDataDir();
7014
+ }
7015
+ async screen(query, _context) {
7016
+ const start = Date.now();
7017
+ if (!this.loaded) {
7018
+ this.loadData();
7019
+ }
7020
+ if (this.entities.length === 0) {
7021
+ return {
7022
+ providerId: this.id,
7023
+ hit: false,
7024
+ matches: [],
7025
+ listsChecked: this.lists,
7026
+ entriesSearched: 0,
7027
+ durationMs: Date.now() - start,
7028
+ error: "No local OpenSanctions data. Run: kontext sync --lists default"
7029
+ };
7030
+ }
7031
+ const matches = isBlockchainAddress(query) ? this.screenAddress(query) : this.screenEntityName(query);
7032
+ const hasActiveHit = matches.some((m) => m.entityStatus === "active");
7033
+ return {
7034
+ providerId: this.id,
7035
+ hit: hasActiveHit,
7036
+ matches,
7037
+ listsChecked: this.lists,
7038
+ entriesSearched: this.entities.length,
7039
+ durationMs: Date.now() - start
7040
+ };
7041
+ }
7042
+ isAvailable() {
7043
+ if (!this.loaded) {
7044
+ this.loadData();
7045
+ }
7046
+ return this.entities.length > 0;
7047
+ }
7048
+ getEntryCount() {
7049
+ if (!this.loaded) {
7050
+ this.loadData();
7051
+ }
7052
+ return this.entities.length;
7053
+ }
7054
+ async sync() {
7055
+ this.loaded = false;
7056
+ this.loadData();
7057
+ return { updated: true, count: this.entities.length };
7058
+ }
7059
+ // --------------------------------------------------------------------------
7060
+ // Private
7061
+ // --------------------------------------------------------------------------
7062
+ screenAddress(address) {
7063
+ const lower = address.toLowerCase();
7064
+ const entity = this.addressToEntity.get(lower);
7065
+ if (!entity) return [];
7066
+ const entityStatus = this.getEntityStatus(entity);
7067
+ return [
7068
+ {
7069
+ list: "OPENSANCTIONS",
7070
+ matchType: "exact_address",
7071
+ similarity: 1,
7072
+ matchedValue: address,
7073
+ entityStatus,
7074
+ entityName: entity.caption,
7075
+ program: entity.datasets.join(", ")
7076
+ }
7077
+ ];
7078
+ }
7079
+ screenEntityName(query) {
7080
+ const matches = [];
7081
+ const queryLower = query.toLowerCase().trim();
7082
+ for (const entity of this.entities) {
7083
+ const captionSim = trigramSimilarity(queryLower, entity.caption);
7084
+ if (captionSim >= NAME_MATCH_THRESHOLD3 && entity.caption.length >= MIN_MATCH_LENGTH2) {
7085
+ matches.push(this.entityToMatch(entity, entity.caption, captionSim));
7086
+ continue;
7087
+ }
7088
+ const aliases = entity.properties["alias"] ?? [];
7089
+ for (const alias of aliases) {
7090
+ const aliasSim = trigramSimilarity(queryLower, alias);
7091
+ if (aliasSim >= NAME_MATCH_THRESHOLD3 && alias.length >= MIN_MATCH_LENGTH2) {
7092
+ matches.push(this.entityToMatch(entity, alias, aliasSim));
7093
+ break;
7094
+ }
7095
+ }
7096
+ }
7097
+ matches.sort((a, b) => b.similarity - a.similarity);
7098
+ return matches.slice(0, 5);
7099
+ }
7100
+ entityToMatch(entity, matchedValue, similarity) {
7101
+ return {
7102
+ list: "OPENSANCTIONS",
7103
+ matchType: similarity >= 0.99 ? "exact_address" : "fuzzy_name",
7104
+ similarity,
7105
+ matchedValue,
7106
+ entityStatus: this.getEntityStatus(entity),
7107
+ entityName: entity.caption,
7108
+ program: entity.datasets.join(", ")
7109
+ };
7110
+ }
7111
+ getEntityStatus(entity) {
7112
+ return "active";
7113
+ }
7114
+ loadData() {
7115
+ this.loaded = true;
7116
+ this.entities = [];
7117
+ this.addressSet.clear();
7118
+ this.addressToEntity.clear();
7119
+ try {
7120
+ const fs5 = __require("fs");
7121
+ const pathMod = __require("path");
7122
+ const dataPath = pathMod.resolve(this.dataDir);
7123
+ if (!fs5.existsSync(dataPath)) return;
7124
+ const files = fs5.readdirSync(dataPath).filter(
7125
+ (f) => f.endsWith(".json")
7126
+ );
7127
+ for (const file of files) {
7128
+ const filePath = pathMod.join(dataPath, file);
7129
+ const content = fs5.readFileSync(filePath, "utf-8");
7130
+ const lines = content.split("\n").filter((l) => l.trim());
7131
+ for (const line of lines) {
7132
+ try {
7133
+ const entity = JSON.parse(line);
7134
+ this.entities.push(entity);
7135
+ const addresses = entity.properties["cryptoAddress"] ?? [];
7136
+ for (const addr of addresses) {
7137
+ const lower = addr.toLowerCase();
7138
+ this.addressSet.add(lower);
7139
+ this.addressToEntity.set(lower, entity);
7140
+ }
7141
+ } catch {
7142
+ }
7143
+ }
7144
+ }
7145
+ } catch {
7146
+ }
7147
+ }
7148
+ resolveDataDir() {
7149
+ try {
7150
+ const os = __require("os");
7151
+ const pathMod = __require("path");
7152
+ return pathMod.join(os.homedir(), DEFAULT_DATA_DIR);
7153
+ } catch {
7154
+ return DEFAULT_DATA_DIR;
7155
+ }
7156
+ }
7157
+ };
7158
+
7159
+ // src/integrations/provider-apis.ts
7160
+ var OpenSanctionsProvider = class {
7161
+ id = "opensanctions-api";
7162
+ name = "OpenSanctions (API)";
7163
+ lists = ["OPENSANCTIONS"];
7164
+ requiresApiKey = true;
7165
+ browserCompatible = false;
7166
+ queryTypes = ["both"];
7167
+ apiKey;
7168
+ baseUrl;
7169
+ dataset;
7170
+ constructor(config) {
7171
+ this.apiKey = config.apiKey;
7172
+ this.baseUrl = config.baseUrl ?? "https://api.opensanctions.org";
7173
+ this.dataset = config.dataset ?? "default";
7174
+ }
7175
+ async screen(query, _context) {
7176
+ const start = Date.now();
7177
+ const isAddr = isBlockchainAddress(query);
7178
+ const schema = isAddr ? "CryptoWallet" : "LegalEntity";
7179
+ const properties = isAddr ? { cryptoAddress: [query] } : { name: [query] };
7180
+ const body = {
7181
+ queries: {
7182
+ q: {
7183
+ schema,
7184
+ properties
7185
+ }
7186
+ }
7187
+ };
7188
+ try {
7189
+ const response = await fetch(
7190
+ `${this.baseUrl}/match/${this.dataset}`,
7191
+ {
7192
+ method: "POST",
7193
+ headers: {
7194
+ "Content-Type": "application/json",
7195
+ "Authorization": `ApiKey ${this.apiKey}`
7196
+ },
7197
+ body: JSON.stringify(body)
7198
+ }
7199
+ );
7200
+ if (!response.ok) {
7201
+ return {
7202
+ providerId: this.id,
7203
+ hit: false,
7204
+ matches: [],
7205
+ listsChecked: this.lists,
7206
+ entriesSearched: 0,
7207
+ durationMs: Date.now() - start,
7208
+ error: `OpenSanctions API error: ${response.status} ${response.statusText}`
7209
+ };
7210
+ }
7211
+ const data = await response.json();
7212
+ const queryResult = data.responses?.["q"];
7213
+ if (!queryResult) {
7214
+ return {
7215
+ providerId: this.id,
7216
+ hit: false,
7217
+ matches: [],
7218
+ listsChecked: this.lists,
7219
+ entriesSearched: 0,
7220
+ durationMs: Date.now() - start
7221
+ };
7222
+ }
7223
+ const matches = queryResult.results.filter((r) => r.match && r.score >= 0.7).map((r) => ({
7224
+ list: "OPENSANCTIONS",
7225
+ matchType: r.score >= 0.99 ? "exact_address" : "fuzzy_name",
7226
+ similarity: r.score,
7227
+ matchedValue: r.caption,
7228
+ entityStatus: "active",
7229
+ entityName: r.caption,
7230
+ program: r.datasets.join(", ")
7231
+ }));
7232
+ const hasActiveHit = matches.some((m) => m.entityStatus === "active");
7233
+ return {
7234
+ providerId: this.id,
7235
+ hit: hasActiveHit,
7236
+ matches,
7237
+ listsChecked: this.lists,
7238
+ entriesSearched: queryResult.total?.value ?? 0,
7239
+ durationMs: Date.now() - start
7240
+ };
7241
+ } catch (err) {
7242
+ return {
7243
+ providerId: this.id,
7244
+ hit: false,
7245
+ matches: [],
7246
+ listsChecked: this.lists,
7247
+ entriesSearched: 0,
7248
+ durationMs: Date.now() - start,
7249
+ error: `OpenSanctions API error: ${err instanceof Error ? err.message : String(err)}`
7250
+ };
7251
+ }
7252
+ }
7253
+ isAvailable() {
7254
+ return !!this.apiKey;
7255
+ }
7256
+ };
7257
+ var ChainalysisFreeAPIProvider = class {
7258
+ id = "chainalysis-free-api";
7259
+ name = "Chainalysis Free API";
7260
+ lists = ["CHAINALYSIS"];
7261
+ requiresApiKey = true;
7262
+ browserCompatible = false;
7263
+ queryTypes = ["address"];
7264
+ apiKey;
7265
+ baseUrl;
7266
+ constructor(config) {
7267
+ this.apiKey = config.apiKey;
7268
+ this.baseUrl = config.baseUrl ?? "https://public.chainalysis.com/api/v1";
7269
+ }
7270
+ async screen(query, _context) {
7271
+ const start = Date.now();
7272
+ try {
7273
+ const response = await fetch(
7274
+ `${this.baseUrl}/address/${query}`,
7275
+ {
7276
+ method: "GET",
7277
+ headers: {
7278
+ "X-API-Key": this.apiKey,
7279
+ "Accept": "application/json"
7280
+ }
7281
+ }
7282
+ );
7283
+ if (!response.ok) {
7284
+ return {
7285
+ providerId: this.id,
7286
+ hit: false,
7287
+ matches: [],
7288
+ listsChecked: this.lists,
7289
+ entriesSearched: 0,
7290
+ durationMs: Date.now() - start,
7291
+ error: `Chainalysis API error: ${response.status} ${response.statusText}`
7292
+ };
7293
+ }
7294
+ const data = await response.json();
7295
+ const identifications = data.identifications ?? [];
7296
+ const matches = identifications.map((id) => ({
7297
+ list: "CHAINALYSIS",
7298
+ matchType: "exact_address",
7299
+ similarity: 1,
7300
+ matchedValue: query,
7301
+ entityStatus: "active",
7302
+ entityName: id.name,
7303
+ program: id.category
7304
+ }));
7305
+ return {
7306
+ providerId: this.id,
7307
+ hit: matches.length > 0,
7308
+ matches,
7309
+ listsChecked: this.lists,
7310
+ entriesSearched: 1,
7311
+ durationMs: Date.now() - start
7312
+ };
7313
+ } catch (err) {
7314
+ return {
7315
+ providerId: this.id,
7316
+ hit: false,
7317
+ matches: [],
7318
+ listsChecked: this.lists,
7319
+ entriesSearched: 0,
7320
+ durationMs: Date.now() - start,
7321
+ error: `Chainalysis API error: ${err instanceof Error ? err.message : String(err)}`
7322
+ };
7323
+ }
7324
+ }
7325
+ isAvailable() {
7326
+ return !!this.apiKey;
7327
+ }
7328
+ };
7329
+
7330
+ // src/integrations/provider-ofac.ts
7331
+ var ORACLE_CONTRACT = "0x40C57923924B5c5c5455c48D93317139ADDaC8fb";
7332
+ var IS_SANCTIONED_SELECTOR = "0xdfb80831";
7333
+ var ChainalysisOracleProvider = class {
7334
+ id = "chainalysis-oracle";
7335
+ name = "Chainalysis OFAC Oracle";
7336
+ lists = ["OFAC_SDN", "CHAINALYSIS"];
7337
+ requiresApiKey = true;
7338
+ browserCompatible = false;
7339
+ queryTypes = ["address"];
7340
+ apiKey;
7341
+ rpcUrl;
7342
+ apiBaseUrl;
7343
+ constructor(config) {
7344
+ this.apiKey = config.apiKey;
7345
+ this.rpcUrl = config.rpcUrl;
7346
+ this.apiBaseUrl = config.apiBaseUrl ?? "https://public.chainalysis.com/api/v1";
7347
+ }
7348
+ async screen(query, _context) {
7349
+ const start = Date.now();
7350
+ if (this.rpcUrl) {
7351
+ return this.screenOnChain(query, start);
7352
+ }
7353
+ if (this.apiKey) {
7354
+ return this.screenApi(query, start);
7355
+ }
7356
+ return {
7357
+ providerId: this.id,
7358
+ hit: false,
7359
+ matches: [],
7360
+ listsChecked: this.lists,
7361
+ entriesSearched: 0,
7362
+ durationMs: Date.now() - start,
7363
+ error: "No API key or RPC URL configured"
7364
+ };
7365
+ }
7366
+ isAvailable() {
7367
+ return !!(this.apiKey || this.rpcUrl);
7368
+ }
7369
+ // --------------------------------------------------------------------------
7370
+ // On-chain oracle query
7371
+ // --------------------------------------------------------------------------
7372
+ async screenOnChain(address, start) {
7373
+ try {
7374
+ const paddedAddress = address.toLowerCase().replace("0x", "").padStart(64, "0");
7375
+ const callData = IS_SANCTIONED_SELECTOR + paddedAddress;
7376
+ const response = await fetch(this.rpcUrl, {
7377
+ method: "POST",
7378
+ headers: { "Content-Type": "application/json" },
7379
+ body: JSON.stringify({
7380
+ jsonrpc: "2.0",
7381
+ id: 1,
7382
+ method: "eth_call",
7383
+ params: [
7384
+ { to: ORACLE_CONTRACT, data: callData },
7385
+ "latest"
7386
+ ]
7387
+ })
7388
+ });
7389
+ if (!response.ok) {
7390
+ return this.errorResult(start, `RPC error: ${response.status}`);
7391
+ }
7392
+ const data = await response.json();
7393
+ if (data.error) {
7394
+ return this.errorResult(start, `RPC error: ${data.error.message}`);
7395
+ }
7396
+ const isSanctioned = data.result ? parseInt(data.result.slice(-2), 16) === 1 : false;
7397
+ const matches = isSanctioned ? [{
7398
+ list: "OFAC_SDN",
7399
+ matchType: "exact_address",
7400
+ similarity: 1,
7401
+ matchedValue: address,
7402
+ entityStatus: "active",
7403
+ program: "CHAINALYSIS_ORACLE"
7404
+ }] : [];
7405
+ return {
7406
+ providerId: this.id,
7407
+ hit: isSanctioned,
7408
+ matches,
7409
+ listsChecked: this.lists,
7410
+ entriesSearched: 1,
7411
+ durationMs: Date.now() - start
7412
+ };
7413
+ } catch (err) {
7414
+ return this.errorResult(
7415
+ start,
7416
+ `Oracle query failed: ${err instanceof Error ? err.message : String(err)}`
7417
+ );
7418
+ }
7419
+ }
7420
+ // --------------------------------------------------------------------------
7421
+ // REST API query
7422
+ // --------------------------------------------------------------------------
7423
+ async screenApi(address, start) {
7424
+ try {
7425
+ const response = await fetch(
7426
+ `${this.apiBaseUrl}/address/${address}`,
7427
+ {
7428
+ method: "GET",
7429
+ headers: {
7430
+ "X-API-Key": this.apiKey,
7431
+ "Accept": "application/json"
7432
+ }
7433
+ }
7434
+ );
7435
+ if (!response.ok) {
7436
+ return this.errorResult(
7437
+ start,
7438
+ `Chainalysis API error: ${response.status} ${response.statusText}`
7439
+ );
7440
+ }
7441
+ const data = await response.json();
7442
+ const identifications = data.identifications ?? [];
7443
+ const matches = identifications.map((id) => ({
7444
+ list: "OFAC_SDN",
7445
+ matchType: "exact_address",
7446
+ similarity: 1,
7447
+ matchedValue: address,
7448
+ entityStatus: "active",
7449
+ entityName: id.name,
7450
+ program: id.category
7451
+ }));
7452
+ return {
7453
+ providerId: this.id,
7454
+ hit: matches.length > 0,
7455
+ matches,
7456
+ listsChecked: this.lists,
7457
+ entriesSearched: 1,
7458
+ durationMs: Date.now() - start
7459
+ };
7460
+ } catch (err) {
7461
+ return this.errorResult(
7462
+ start,
7463
+ `Chainalysis API error: ${err instanceof Error ? err.message : String(err)}`
7464
+ );
7465
+ }
7466
+ }
7467
+ errorResult(start, error) {
7468
+ return {
7469
+ providerId: this.id,
7470
+ hit: false,
7471
+ matches: [],
7472
+ listsChecked: this.lists,
7473
+ entriesSearched: 0,
7474
+ durationMs: Date.now() - start,
7475
+ error
7476
+ };
7477
+ }
7478
+ };
7479
+
7480
+ export { AgentIdentityRegistry, AnomalyDetector, BehavioralFingerprinter, CURRENCY_REQUIRED_LISTS, ChainalysisFreeAPIProvider, ChainalysisOracleProvider, ConsoleExporter, CrossSessionLinker, DigestChain, FeatureFlagManager, FileStorage, JsonFileExporter, KONTEXT_BUILDER_CODE, KYAConfidenceScorer, Kontext, KontextError, KontextErrorCode, MemoryStorage, NoopExporter, OFACAddressProvider, OFACEntityProvider, OnChainExporter, OpenSanctionsLocalProvider, OpenSanctionsProvider, PLAN_LIMITS, PaymentCompliance, PlanManager, ProvenanceManager, ScreeningAggregator, TOKEN_REQUIRED_LISTS, TrustScorer, UKOFSIProvider, UsdcCompliance, WalletClusterer, anchorDigest, encodeERC8021Suffix, exchangeAttestation, fetchAgentCard, fetchTransactionAttribution, getAnchor, getRequiredLists, isBlockchainAddress, isCryptoTransaction, isFeatureAvailable, parseERC8021Suffix, providerSupportsQuery, requirePlan, verifyAnchor, verifyExportedChain };
4422
7481
  //# sourceMappingURL=index.mjs.map
4423
7482
  //# sourceMappingURL=index.mjs.map