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