kontext-sdk 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -236
- package/dist/index.d.mts +492 -5
- package/dist/index.d.ts +492 -5
- package/dist/index.js +1214 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1204 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var crypto$1 = require('crypto');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var fs5 = require('fs');
|
|
5
|
+
var path5 = require('path');
|
|
6
6
|
|
|
7
7
|
function _interopNamespace(e) {
|
|
8
8
|
if (e && e.__esModule) return e;
|
|
@@ -22,8 +22,8 @@ function _interopNamespace(e) {
|
|
|
22
22
|
return Object.freeze(n);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
var
|
|
26
|
-
var
|
|
25
|
+
var fs5__namespace = /*#__PURE__*/_interopNamespace(fs5);
|
|
26
|
+
var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
|
|
27
27
|
|
|
28
28
|
var __defProp = Object.defineProperty;
|
|
29
29
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1974,13 +1974,13 @@ var ActionLogger = class {
|
|
|
1974
1974
|
}
|
|
1975
1975
|
flushToFile(actions) {
|
|
1976
1976
|
const outputDir = this.config.localOutputDir ?? ".kontext";
|
|
1977
|
-
const logDir =
|
|
1977
|
+
const logDir = path5__namespace.join(outputDir, "logs");
|
|
1978
1978
|
try {
|
|
1979
|
-
|
|
1979
|
+
fs5__namespace.mkdirSync(logDir, { recursive: true });
|
|
1980
1980
|
const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
|
|
1981
|
-
const filePath =
|
|
1981
|
+
const filePath = path5__namespace.join(logDir, filename);
|
|
1982
1982
|
const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
|
|
1983
|
-
|
|
1983
|
+
fs5__namespace.appendFileSync(filePath, lines, "utf-8");
|
|
1984
1984
|
} catch (error) {
|
|
1985
1985
|
this.emitLog("warn", "Failed to write log file", { error });
|
|
1986
1986
|
}
|
|
@@ -2590,9 +2590,9 @@ var SANCTIONED_SET = new Set(
|
|
|
2590
2590
|
function loadCachedSDN() {
|
|
2591
2591
|
try {
|
|
2592
2592
|
const dataDir = process.env["KONTEXT_DATA_DIR"] || ".kontext";
|
|
2593
|
-
const cachePath =
|
|
2594
|
-
if (
|
|
2595
|
-
const cache = JSON.parse(
|
|
2593
|
+
const cachePath = path5__namespace.join(dataDir, "ofac-sdn-cache.json");
|
|
2594
|
+
if (fs5__namespace.existsSync(cachePath)) {
|
|
2595
|
+
const cache = JSON.parse(fs5__namespace.readFileSync(cachePath, "utf-8"));
|
|
2596
2596
|
if (Array.isArray(cache.addresses)) {
|
|
2597
2597
|
for (const addr of cache.addresses) {
|
|
2598
2598
|
SANCTIONED_SET.add(String(addr).toLowerCase());
|
|
@@ -3648,7 +3648,9 @@ var FEATURE_MIN_PLAN = {
|
|
|
3648
3648
|
"unified-screening": "pro",
|
|
3649
3649
|
"blocklist-manager": "pro",
|
|
3650
3650
|
"kya-identity": "pro",
|
|
3651
|
-
"kya-behavioral": "enterprise"
|
|
3651
|
+
"kya-behavioral": "enterprise",
|
|
3652
|
+
"coinbase-wallets": "enterprise",
|
|
3653
|
+
"metamask-wallets": "enterprise"
|
|
3652
3654
|
};
|
|
3653
3655
|
var PLAN_RANK = { free: 0, pro: 1, enterprise: 2 };
|
|
3654
3656
|
var FEATURE_LABELS = {
|
|
@@ -3667,7 +3669,9 @@ var FEATURE_LABELS = {
|
|
|
3667
3669
|
"unified-screening": "Unified screening (OFAC, Chainalysis, OpenSanctions)",
|
|
3668
3670
|
"blocklist-manager": "Custom blocklist/allowlist manager",
|
|
3669
3671
|
"kya-identity": "KYA identity resolution (declared identity, wallet clustering)",
|
|
3670
|
-
"kya-behavioral": "KYA behavioral fingerprinting (cross-session linking, confidence scoring)"
|
|
3672
|
+
"kya-behavioral": "KYA behavioral fingerprinting (cross-session linking, confidence scoring)",
|
|
3673
|
+
"coinbase-wallets": "Coinbase Developer Platform Wallets",
|
|
3674
|
+
"metamask-wallets": "MetaMask Embedded Wallets"
|
|
3671
3675
|
};
|
|
3672
3676
|
function isFeatureAvailable(feature, currentPlan) {
|
|
3673
3677
|
const requiredPlan = FEATURE_MIN_PLAN[feature];
|
|
@@ -3713,7 +3717,7 @@ var JsonFileExporter = class {
|
|
|
3713
3717
|
buffer = [];
|
|
3714
3718
|
bufferSize;
|
|
3715
3719
|
constructor(options) {
|
|
3716
|
-
this.outputDir =
|
|
3720
|
+
this.outputDir = path5__namespace.resolve(options?.outputDir ?? ".kontext/exports");
|
|
3717
3721
|
this.bufferSize = options?.bufferSize ?? 1;
|
|
3718
3722
|
}
|
|
3719
3723
|
async export(events) {
|
|
@@ -3728,11 +3732,11 @@ var JsonFileExporter = class {
|
|
|
3728
3732
|
const toWrite = [...this.buffer];
|
|
3729
3733
|
this.buffer = [];
|
|
3730
3734
|
try {
|
|
3731
|
-
|
|
3735
|
+
fs5__namespace.mkdirSync(this.outputDir, { recursive: true });
|
|
3732
3736
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3733
|
-
const filePath =
|
|
3737
|
+
const filePath = path5__namespace.join(this.outputDir, `events-${date}.jsonl`);
|
|
3734
3738
|
const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
3735
|
-
|
|
3739
|
+
fs5__namespace.appendFileSync(filePath, lines, "utf-8");
|
|
3736
3740
|
} catch (error) {
|
|
3737
3741
|
console.warn("[Kontext JsonFileExporter] Failed to write events:", error);
|
|
3738
3742
|
}
|
|
@@ -3902,6 +3906,218 @@ function extractDocumentId(resourceName) {
|
|
|
3902
3906
|
const parts = resourceName.split("/");
|
|
3903
3907
|
return parts[parts.length - 1] ?? resourceName;
|
|
3904
3908
|
}
|
|
3909
|
+
var CONFIG_FILENAME = "kontext.config.json";
|
|
3910
|
+
function loadConfigFile(startDir) {
|
|
3911
|
+
const dir = startDir ?? process.cwd();
|
|
3912
|
+
const filePath = findConfigFile(dir);
|
|
3913
|
+
if (!filePath) return null;
|
|
3914
|
+
try {
|
|
3915
|
+
const raw = fs5__namespace.readFileSync(filePath, "utf-8");
|
|
3916
|
+
const parsed = JSON.parse(raw);
|
|
3917
|
+
if (!parsed.projectId || typeof parsed.projectId !== "string") {
|
|
3918
|
+
return null;
|
|
3919
|
+
}
|
|
3920
|
+
return parsed;
|
|
3921
|
+
} catch {
|
|
3922
|
+
return null;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
function findConfigFile(dir) {
|
|
3926
|
+
let current = path5__namespace.resolve(dir);
|
|
3927
|
+
const root = path5__namespace.parse(current).root;
|
|
3928
|
+
while (true) {
|
|
3929
|
+
const candidate = path5__namespace.join(current, CONFIG_FILENAME);
|
|
3930
|
+
if (fs5__namespace.existsSync(candidate)) {
|
|
3931
|
+
return candidate;
|
|
3932
|
+
}
|
|
3933
|
+
const parent = path5__namespace.dirname(current);
|
|
3934
|
+
if (parent === current || current === root) {
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
current = parent;
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
// src/integrations/data/stablecoin-contracts.ts
|
|
3942
|
+
var STABLECOIN_CONTRACTS = {
|
|
3943
|
+
// USDC (6 decimals)
|
|
3944
|
+
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { token: "USDC", chain: "ethereum", decimals: 6 },
|
|
3945
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { token: "USDC", chain: "base", decimals: 6 },
|
|
3946
|
+
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359": { token: "USDC", chain: "polygon", decimals: 6 },
|
|
3947
|
+
"0xaf88d065e77c8cc2239327c5edb3a432268e5831": { token: "USDC", chain: "arbitrum", decimals: 6 },
|
|
3948
|
+
"0x0b2c639c533813f4aa9d7837caf62653d097ff85": { token: "USDC", chain: "optimism", decimals: 6 },
|
|
3949
|
+
"0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": { token: "USDC", chain: "avalanche", decimals: 6 },
|
|
3950
|
+
// USDT (6 decimals)
|
|
3951
|
+
"0xdac17f958d2ee523a2206206994597c13d831ec7": { token: "USDT", chain: "ethereum", decimals: 6 },
|
|
3952
|
+
"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9": { token: "USDT", chain: "arbitrum", decimals: 6 },
|
|
3953
|
+
"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58": { token: "USDT", chain: "optimism", decimals: 6 },
|
|
3954
|
+
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f": { token: "USDT", chain: "polygon", decimals: 6 },
|
|
3955
|
+
// DAI (18 decimals)
|
|
3956
|
+
"0x6b175474e89094c44da98b954eedeac495271d0f": { token: "DAI", chain: "ethereum", decimals: 18 },
|
|
3957
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": { token: "DAI", chain: "base", decimals: 18 },
|
|
3958
|
+
// EURC (6 decimals)
|
|
3959
|
+
"0x1abaea1f7c830bd89acc67ec4af516284b1bc33c": { token: "EURC", chain: "ethereum", decimals: 6 },
|
|
3960
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": { token: "EURC", chain: "base", decimals: 6 }
|
|
3961
|
+
};
|
|
3962
|
+
new Set(Object.keys(STABLECOIN_CONTRACTS));
|
|
3963
|
+
var CHAIN_ID_MAP = {
|
|
3964
|
+
1: "ethereum",
|
|
3965
|
+
8453: "base",
|
|
3966
|
+
137: "polygon",
|
|
3967
|
+
42161: "arbitrum",
|
|
3968
|
+
10: "optimism",
|
|
3969
|
+
43114: "avalanche"
|
|
3970
|
+
};
|
|
3971
|
+
var TRANSFER_SELECTOR = "0xa9059cbb";
|
|
3972
|
+
var TRANSFER_FROM_SELECTOR = "0x23b872dd";
|
|
3973
|
+
var TRANSFER_EVENT_ABI = {
|
|
3974
|
+
type: "event",
|
|
3975
|
+
name: "Transfer",
|
|
3976
|
+
inputs: [
|
|
3977
|
+
{ type: "address", name: "from", indexed: true },
|
|
3978
|
+
{ type: "address", name: "to", indexed: true },
|
|
3979
|
+
{ type: "uint256", name: "value", indexed: false }
|
|
3980
|
+
]
|
|
3981
|
+
};
|
|
3982
|
+
|
|
3983
|
+
// src/integrations/wallet-monitor.ts
|
|
3984
|
+
var WalletMonitor = class {
|
|
3985
|
+
kontext;
|
|
3986
|
+
config;
|
|
3987
|
+
agentId;
|
|
3988
|
+
tokens;
|
|
3989
|
+
unwatchers = [];
|
|
3990
|
+
running = false;
|
|
3991
|
+
/** Shared dedup set — tracks recently verified txHashes (populated by both layers) */
|
|
3992
|
+
verifiedTxHashes = /* @__PURE__ */ new Set();
|
|
3993
|
+
cleanupTimer = null;
|
|
3994
|
+
txTimestamps = /* @__PURE__ */ new Map();
|
|
3995
|
+
constructor(kontext, config, options) {
|
|
3996
|
+
this.kontext = kontext;
|
|
3997
|
+
this.config = config;
|
|
3998
|
+
this.agentId = options?.agentId ?? "wallet-monitor";
|
|
3999
|
+
this.tokens = options?.tokens ? new Set(options.tokens) : null;
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Mark a txHash as already verified (called by the viem interceptor layer).
|
|
4003
|
+
* The monitor will skip this tx if it later sees it on-chain.
|
|
4004
|
+
*/
|
|
4005
|
+
markVerified(txHash) {
|
|
4006
|
+
const lower = txHash.toLowerCase();
|
|
4007
|
+
this.verifiedTxHashes.add(lower);
|
|
4008
|
+
this.txTimestamps.set(lower, Date.now());
|
|
4009
|
+
}
|
|
4010
|
+
/**
|
|
4011
|
+
* Start watching all configured chains for stablecoin transfers.
|
|
4012
|
+
* Dynamically imports viem — requires viem as a peer dependency.
|
|
4013
|
+
*/
|
|
4014
|
+
async start() {
|
|
4015
|
+
if (this.running) return;
|
|
4016
|
+
let viem;
|
|
4017
|
+
try {
|
|
4018
|
+
viem = await import('viem');
|
|
4019
|
+
} catch {
|
|
4020
|
+
throw new Error(
|
|
4021
|
+
"Wallet monitoring requires viem. Install it: npm install viem"
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
const { createPublicClient, http } = viem;
|
|
4025
|
+
const wallets = this.config.wallets.map((w) => w.toLowerCase());
|
|
4026
|
+
const pollingInterval = this.config.pollingIntervalMs ?? 12e3;
|
|
4027
|
+
const contractsByChain = /* @__PURE__ */ new Map();
|
|
4028
|
+
for (const [address, info] of Object.entries(STABLECOIN_CONTRACTS)) {
|
|
4029
|
+
if (this.tokens && !this.tokens.has(info.token)) continue;
|
|
4030
|
+
const existing = contractsByChain.get(info.chain) ?? [];
|
|
4031
|
+
existing.push({ address, info });
|
|
4032
|
+
contractsByChain.set(info.chain, existing);
|
|
4033
|
+
}
|
|
4034
|
+
for (const [chain, contracts] of contractsByChain) {
|
|
4035
|
+
const rpcUrl = this.config.rpcEndpoints[chain];
|
|
4036
|
+
if (!rpcUrl) continue;
|
|
4037
|
+
const client = createPublicClient({
|
|
4038
|
+
transport: http(rpcUrl)
|
|
4039
|
+
});
|
|
4040
|
+
for (const { address, info } of contracts) {
|
|
4041
|
+
const unwatch = client.watchEvent({
|
|
4042
|
+
address,
|
|
4043
|
+
event: TRANSFER_EVENT_ABI,
|
|
4044
|
+
args: { from: wallets.length === 1 ? wallets[0] : wallets },
|
|
4045
|
+
poll: true,
|
|
4046
|
+
pollingInterval,
|
|
4047
|
+
onLogs: (logs) => {
|
|
4048
|
+
for (const log of logs) {
|
|
4049
|
+
this.handleTransferLog(log, info);
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
});
|
|
4053
|
+
this.unwatchers.push(unwatch);
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
this.cleanupTimer = setInterval(() => {
|
|
4057
|
+
const cutoff = Date.now() - 5 * 60 * 1e3;
|
|
4058
|
+
for (const [hash, ts] of this.txTimestamps) {
|
|
4059
|
+
if (ts < cutoff) {
|
|
4060
|
+
this.verifiedTxHashes.delete(hash);
|
|
4061
|
+
this.txTimestamps.delete(hash);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}, 6e4);
|
|
4065
|
+
this.running = true;
|
|
4066
|
+
}
|
|
4067
|
+
/** Stop all watchers and cleanup */
|
|
4068
|
+
stop() {
|
|
4069
|
+
for (const unwatch of this.unwatchers) {
|
|
4070
|
+
try {
|
|
4071
|
+
unwatch();
|
|
4072
|
+
} catch {
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
this.unwatchers.length = 0;
|
|
4076
|
+
if (this.cleanupTimer) {
|
|
4077
|
+
clearInterval(this.cleanupTimer);
|
|
4078
|
+
this.cleanupTimer = null;
|
|
4079
|
+
}
|
|
4080
|
+
this.running = false;
|
|
4081
|
+
}
|
|
4082
|
+
isRunning() {
|
|
4083
|
+
return this.running;
|
|
4084
|
+
}
|
|
4085
|
+
handleTransferLog(log, contractInfo) {
|
|
4086
|
+
const txHash = log.transactionHash;
|
|
4087
|
+
if (!txHash) return;
|
|
4088
|
+
const lowerHash = txHash.toLowerCase();
|
|
4089
|
+
if (this.verifiedTxHashes.has(lowerHash)) return;
|
|
4090
|
+
this.markVerified(txHash);
|
|
4091
|
+
const from = log.args?.from?.toLowerCase() ?? "";
|
|
4092
|
+
const to = log.args?.to?.toLowerCase() ?? "";
|
|
4093
|
+
const value = log.args?.value;
|
|
4094
|
+
if (!from || !to || value === void 0) return;
|
|
4095
|
+
const amount = formatTokenAmount(value, contractInfo.decimals);
|
|
4096
|
+
const verifyInput = {
|
|
4097
|
+
txHash,
|
|
4098
|
+
chain: contractInfo.chain,
|
|
4099
|
+
amount,
|
|
4100
|
+
token: contractInfo.token,
|
|
4101
|
+
from,
|
|
4102
|
+
to,
|
|
4103
|
+
agentId: this.agentId,
|
|
4104
|
+
metadata: {
|
|
4105
|
+
source: "wallet-monitor",
|
|
4106
|
+
contractAddress: log.address
|
|
4107
|
+
}
|
|
4108
|
+
};
|
|
4109
|
+
this.kontext.verify(verifyInput).catch(() => {
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
};
|
|
4113
|
+
function formatTokenAmount(amount, decimals) {
|
|
4114
|
+
const divisor = BigInt(10 ** decimals);
|
|
4115
|
+
const whole = amount / divisor;
|
|
4116
|
+
const fraction = amount % divisor;
|
|
4117
|
+
if (fraction === 0n) return whole.toString();
|
|
4118
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
4119
|
+
return `${whole}.${fractionStr}`;
|
|
4120
|
+
}
|
|
3905
4121
|
var ProvenanceManager = class {
|
|
3906
4122
|
store;
|
|
3907
4123
|
logger;
|
|
@@ -5421,6 +5637,602 @@ var KYAConfidenceScorer = class {
|
|
|
5421
5637
|
}
|
|
5422
5638
|
};
|
|
5423
5639
|
|
|
5640
|
+
// src/integrations/circle-wallets.ts
|
|
5641
|
+
var DEFAULT_BASE_URL = "https://api.circle.com";
|
|
5642
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
5643
|
+
var CircleWalletManager = class {
|
|
5644
|
+
apiKey;
|
|
5645
|
+
entitySecret;
|
|
5646
|
+
baseUrl;
|
|
5647
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5648
|
+
kontext = null;
|
|
5649
|
+
constructor(config) {
|
|
5650
|
+
if (!config.apiKey) {
|
|
5651
|
+
throw new KontextError(
|
|
5652
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5653
|
+
"Circle API key is required"
|
|
5654
|
+
);
|
|
5655
|
+
}
|
|
5656
|
+
if (!config.entitySecret) {
|
|
5657
|
+
throw new KontextError(
|
|
5658
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5659
|
+
"Circle entity secret is required"
|
|
5660
|
+
);
|
|
5661
|
+
}
|
|
5662
|
+
this.apiKey = config.apiKey;
|
|
5663
|
+
this.entitySecret = config.entitySecret;
|
|
5664
|
+
this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
5665
|
+
}
|
|
5666
|
+
/** Link to Kontext instance for auto-compliance logging */
|
|
5667
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5668
|
+
setKontext(kontext) {
|
|
5669
|
+
this.kontext = kontext;
|
|
5670
|
+
}
|
|
5671
|
+
/** Validate credentials by calling Circle's configuration endpoint */
|
|
5672
|
+
async validateCredentials() {
|
|
5673
|
+
try {
|
|
5674
|
+
const res = await fetch(`${this.baseUrl}/v1/w3s/config/entity`, {
|
|
5675
|
+
method: "GET",
|
|
5676
|
+
headers: this.headers(),
|
|
5677
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
5678
|
+
});
|
|
5679
|
+
return res.ok;
|
|
5680
|
+
} catch {
|
|
5681
|
+
return false;
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
/** Create a wallet set (container for wallets) */
|
|
5685
|
+
async createWalletSet(input) {
|
|
5686
|
+
const body = {
|
|
5687
|
+
name: input.name,
|
|
5688
|
+
idempotencyKey: input.idempotencyKey ?? generateId(),
|
|
5689
|
+
entitySecretCiphertext: this.entitySecret
|
|
5690
|
+
};
|
|
5691
|
+
const res = await this.request(
|
|
5692
|
+
"POST",
|
|
5693
|
+
"/v1/w3s/developer/walletSets",
|
|
5694
|
+
body
|
|
5695
|
+
);
|
|
5696
|
+
return res.data.walletSet;
|
|
5697
|
+
}
|
|
5698
|
+
/** Create wallet(s) in a wallet set */
|
|
5699
|
+
async createWallet(input) {
|
|
5700
|
+
const body = {
|
|
5701
|
+
walletSetId: input.walletSetId,
|
|
5702
|
+
blockchains: input.blockchains.map((c) => this.mapChain(c)),
|
|
5703
|
+
count: input.count ?? 1,
|
|
5704
|
+
accountType: input.accountType ?? "EOA",
|
|
5705
|
+
idempotencyKey: input.idempotencyKey ?? generateId(),
|
|
5706
|
+
entitySecretCiphertext: this.entitySecret
|
|
5707
|
+
};
|
|
5708
|
+
const res = await this.request(
|
|
5709
|
+
"POST",
|
|
5710
|
+
"/v1/w3s/developer/wallets",
|
|
5711
|
+
body
|
|
5712
|
+
);
|
|
5713
|
+
return res.data.wallets;
|
|
5714
|
+
}
|
|
5715
|
+
/** List wallets, optionally filtered by wallet set */
|
|
5716
|
+
async listWallets(walletSetId) {
|
|
5717
|
+
const qs = walletSetId ? `?walletSetId=${walletSetId}` : "";
|
|
5718
|
+
const res = await this.request(
|
|
5719
|
+
"GET",
|
|
5720
|
+
`/v1/w3s/wallets${qs}`
|
|
5721
|
+
);
|
|
5722
|
+
return res.data.wallets;
|
|
5723
|
+
}
|
|
5724
|
+
/** Get wallet token balances */
|
|
5725
|
+
async getBalance(walletId) {
|
|
5726
|
+
const res = await this.request(
|
|
5727
|
+
"GET",
|
|
5728
|
+
`/v1/w3s/wallets/${walletId}/balances`
|
|
5729
|
+
);
|
|
5730
|
+
return res.data.tokenBalances.map((b) => ({
|
|
5731
|
+
token: b.token.symbol,
|
|
5732
|
+
amount: b.amount
|
|
5733
|
+
}));
|
|
5734
|
+
}
|
|
5735
|
+
/** Transfer with auto-compliance: runs verify() before/after transfer */
|
|
5736
|
+
async transferWithCompliance(input) {
|
|
5737
|
+
let complianceResult;
|
|
5738
|
+
if (this.kontext) {
|
|
5739
|
+
complianceResult = await this.kontext.verify({
|
|
5740
|
+
txHash: `circle-pending-${generateId()}`,
|
|
5741
|
+
chain: input.blockchain,
|
|
5742
|
+
amount: input.amount,
|
|
5743
|
+
token: "USDC",
|
|
5744
|
+
from: input.walletId,
|
|
5745
|
+
to: input.destinationAddress,
|
|
5746
|
+
agentId: input.agentId ?? "circle-wallet-manager"
|
|
5747
|
+
});
|
|
5748
|
+
if (!complianceResult.compliant) {
|
|
5749
|
+
return {
|
|
5750
|
+
id: "",
|
|
5751
|
+
state: "BLOCKED",
|
|
5752
|
+
complianceResult
|
|
5753
|
+
};
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
const body = {
|
|
5757
|
+
walletId: input.walletId,
|
|
5758
|
+
tokenAddress: input.tokenAddress,
|
|
5759
|
+
destinationAddress: input.destinationAddress,
|
|
5760
|
+
amounts: [input.amount],
|
|
5761
|
+
blockchain: this.mapChain(input.blockchain),
|
|
5762
|
+
idempotencyKey: input.idempotencyKey ?? generateId(),
|
|
5763
|
+
entitySecretCiphertext: this.entitySecret,
|
|
5764
|
+
feeLevel: "MEDIUM"
|
|
5765
|
+
};
|
|
5766
|
+
const res = await this.request(
|
|
5767
|
+
"POST",
|
|
5768
|
+
"/v1/w3s/developer/transactions/transfer",
|
|
5769
|
+
body
|
|
5770
|
+
);
|
|
5771
|
+
if (this.kontext) {
|
|
5772
|
+
await this.kontext.logReasoning({
|
|
5773
|
+
agentId: input.agentId ?? "circle-wallet-manager",
|
|
5774
|
+
action: "circle-transfer",
|
|
5775
|
+
reasoning: `Circle transfer ${res.data.id}: ${input.amount} to ${input.destinationAddress} on ${input.blockchain}`,
|
|
5776
|
+
confidence: 1,
|
|
5777
|
+
context: { transferId: res.data.id, state: res.data.state }
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
return {
|
|
5781
|
+
id: res.data.id,
|
|
5782
|
+
state: res.data.state,
|
|
5783
|
+
txHash: res.data.txHash,
|
|
5784
|
+
complianceResult
|
|
5785
|
+
};
|
|
5786
|
+
}
|
|
5787
|
+
// --------------------------------------------------------------------------
|
|
5788
|
+
// Private helpers
|
|
5789
|
+
// --------------------------------------------------------------------------
|
|
5790
|
+
headers() {
|
|
5791
|
+
return {
|
|
5792
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
5793
|
+
"Content-Type": "application/json",
|
|
5794
|
+
"X-Entity-Secret": this.entitySecret
|
|
5795
|
+
};
|
|
5796
|
+
}
|
|
5797
|
+
async request(method, path6, body) {
|
|
5798
|
+
const res = await fetch(`${this.baseUrl}${path6}`, {
|
|
5799
|
+
method,
|
|
5800
|
+
headers: this.headers(),
|
|
5801
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
5802
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
5803
|
+
});
|
|
5804
|
+
if (!res.ok) {
|
|
5805
|
+
const text = await res.text().catch(() => "");
|
|
5806
|
+
throw new KontextError(
|
|
5807
|
+
"API_ERROR" /* API_ERROR */,
|
|
5808
|
+
`Circle API error ${res.status}: ${text}`
|
|
5809
|
+
);
|
|
5810
|
+
}
|
|
5811
|
+
return res.json();
|
|
5812
|
+
}
|
|
5813
|
+
mapChain(chain) {
|
|
5814
|
+
const map = {
|
|
5815
|
+
ethereum: "ETH",
|
|
5816
|
+
base: "BASE",
|
|
5817
|
+
polygon: "MATIC",
|
|
5818
|
+
arbitrum: "ARB",
|
|
5819
|
+
optimism: "OP",
|
|
5820
|
+
avalanche: "AVAX",
|
|
5821
|
+
solana: "SOL"
|
|
5822
|
+
};
|
|
5823
|
+
return map[chain] ?? chain.toUpperCase();
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
|
|
5827
|
+
// src/integrations/coinbase-wallets.ts
|
|
5828
|
+
var CDP_BASE_URL = "https://api.cdp.coinbase.com";
|
|
5829
|
+
var REQUEST_TIMEOUT_MS2 = 1e4;
|
|
5830
|
+
var CoinbaseWalletManager = class {
|
|
5831
|
+
apiKeyId;
|
|
5832
|
+
apiKeySecret;
|
|
5833
|
+
walletSecret;
|
|
5834
|
+
baseUrl;
|
|
5835
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5836
|
+
kontext = null;
|
|
5837
|
+
constructor(config) {
|
|
5838
|
+
if (!config.apiKeyId) {
|
|
5839
|
+
throw new KontextError(
|
|
5840
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5841
|
+
"Coinbase CDP API Key ID is required"
|
|
5842
|
+
);
|
|
5843
|
+
}
|
|
5844
|
+
if (!config.apiKeySecret) {
|
|
5845
|
+
throw new KontextError(
|
|
5846
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5847
|
+
"Coinbase CDP API Key Secret is required"
|
|
5848
|
+
);
|
|
5849
|
+
}
|
|
5850
|
+
if (!config.walletSecret) {
|
|
5851
|
+
throw new KontextError(
|
|
5852
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5853
|
+
"Coinbase CDP Wallet Secret is required"
|
|
5854
|
+
);
|
|
5855
|
+
}
|
|
5856
|
+
this.apiKeyId = config.apiKeyId;
|
|
5857
|
+
this.apiKeySecret = config.apiKeySecret;
|
|
5858
|
+
this.walletSecret = config.walletSecret;
|
|
5859
|
+
this.baseUrl = CDP_BASE_URL;
|
|
5860
|
+
}
|
|
5861
|
+
/** Link to Kontext instance for auto-compliance logging */
|
|
5862
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5863
|
+
setKontext(kontext) {
|
|
5864
|
+
this.kontext = kontext;
|
|
5865
|
+
}
|
|
5866
|
+
/** Validate credentials by listing accounts */
|
|
5867
|
+
async validateCredentials() {
|
|
5868
|
+
try {
|
|
5869
|
+
const res = await fetch(`${this.baseUrl}/v1/evm/accounts`, {
|
|
5870
|
+
method: "GET",
|
|
5871
|
+
headers: await this.headers(),
|
|
5872
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
5873
|
+
});
|
|
5874
|
+
return res.ok;
|
|
5875
|
+
} catch {
|
|
5876
|
+
return false;
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
/** Create an EVM account */
|
|
5880
|
+
async createAccount(opts) {
|
|
5881
|
+
const body = {};
|
|
5882
|
+
if (opts?.name) body["name"] = opts.name;
|
|
5883
|
+
if (opts?.network) body["network"] = opts.network;
|
|
5884
|
+
const res = await this.request(
|
|
5885
|
+
"POST",
|
|
5886
|
+
"/v1/evm/accounts",
|
|
5887
|
+
body,
|
|
5888
|
+
true
|
|
5889
|
+
// requires wallet auth
|
|
5890
|
+
);
|
|
5891
|
+
return {
|
|
5892
|
+
address: res.address,
|
|
5893
|
+
name: res.name,
|
|
5894
|
+
network: res.network
|
|
5895
|
+
};
|
|
5896
|
+
}
|
|
5897
|
+
/** List accounts */
|
|
5898
|
+
async listAccounts() {
|
|
5899
|
+
const res = await this.request(
|
|
5900
|
+
"GET",
|
|
5901
|
+
"/v1/evm/accounts"
|
|
5902
|
+
);
|
|
5903
|
+
return res.accounts.map((a) => ({
|
|
5904
|
+
address: a.address,
|
|
5905
|
+
name: a.name,
|
|
5906
|
+
network: a.network
|
|
5907
|
+
}));
|
|
5908
|
+
}
|
|
5909
|
+
/** Get token balances for an address */
|
|
5910
|
+
async getBalances(address, network) {
|
|
5911
|
+
const res = await this.request(
|
|
5912
|
+
"GET",
|
|
5913
|
+
`/v1/evm/accounts/${address}/balances?network=${network}`
|
|
5914
|
+
);
|
|
5915
|
+
return res.balances.map((b) => ({
|
|
5916
|
+
token: b.asset,
|
|
5917
|
+
amount: b.amount
|
|
5918
|
+
}));
|
|
5919
|
+
}
|
|
5920
|
+
/** Transfer with auto-compliance: runs verify() before/after transfer */
|
|
5921
|
+
async transferWithCompliance(input) {
|
|
5922
|
+
let complianceResult;
|
|
5923
|
+
if (this.kontext) {
|
|
5924
|
+
const chain = this.mapNetwork(input.network);
|
|
5925
|
+
complianceResult = await this.kontext.verify({
|
|
5926
|
+
txHash: `cdp-pending-${generateId()}`,
|
|
5927
|
+
chain,
|
|
5928
|
+
amount: input.amount,
|
|
5929
|
+
token: input.token,
|
|
5930
|
+
from: input.fromAddress,
|
|
5931
|
+
to: input.toAddress,
|
|
5932
|
+
agentId: input.agentId ?? "coinbase-wallet-manager"
|
|
5933
|
+
});
|
|
5934
|
+
if (!complianceResult.compliant) {
|
|
5935
|
+
return {
|
|
5936
|
+
transactionHash: "",
|
|
5937
|
+
status: "BLOCKED",
|
|
5938
|
+
complianceResult
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
const body = {
|
|
5943
|
+
to: input.toAddress,
|
|
5944
|
+
amount: input.amount,
|
|
5945
|
+
asset: input.token,
|
|
5946
|
+
network: input.network
|
|
5947
|
+
};
|
|
5948
|
+
const res = await this.request(
|
|
5949
|
+
"POST",
|
|
5950
|
+
`/v1/evm/accounts/${input.fromAddress}/transfer`,
|
|
5951
|
+
body,
|
|
5952
|
+
true
|
|
5953
|
+
// requires wallet auth
|
|
5954
|
+
);
|
|
5955
|
+
if (this.kontext) {
|
|
5956
|
+
await this.kontext.logReasoning({
|
|
5957
|
+
agentId: input.agentId ?? "coinbase-wallet-manager",
|
|
5958
|
+
action: "coinbase-transfer",
|
|
5959
|
+
reasoning: `CDP transfer: ${input.amount} ${input.token} from ${input.fromAddress} to ${input.toAddress} on ${input.network}`,
|
|
5960
|
+
confidence: 1,
|
|
5961
|
+
context: { transactionHash: res.transactionHash, status: res.status }
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
return {
|
|
5965
|
+
transactionHash: res.transactionHash,
|
|
5966
|
+
status: res.status,
|
|
5967
|
+
complianceResult
|
|
5968
|
+
};
|
|
5969
|
+
}
|
|
5970
|
+
// --------------------------------------------------------------------------
|
|
5971
|
+
// Private helpers
|
|
5972
|
+
// --------------------------------------------------------------------------
|
|
5973
|
+
/**
|
|
5974
|
+
* Build auth headers. CDP uses JWT Bearer tokens:
|
|
5975
|
+
* - API auth: signed with apiKeySecret, apiKeyId as kid, 120s expiry
|
|
5976
|
+
* - Wallet auth: X-Wallet-Auth header signed with walletSecret, 60s expiry
|
|
5977
|
+
*
|
|
5978
|
+
* Note: Full Ed25519 JWT signing requires the jose or crypto module.
|
|
5979
|
+
* This implementation provides the header structure; production use
|
|
5980
|
+
* should integrate with @coinbase/cdp-sdk for proper JWT signing.
|
|
5981
|
+
*/
|
|
5982
|
+
async headers(includeWalletAuth = false) {
|
|
5983
|
+
const apiJwt = this.buildJwt(this.apiKeyId, this.apiKeySecret, 120);
|
|
5984
|
+
const hdrs = {
|
|
5985
|
+
"Authorization": `Bearer ${apiJwt}`,
|
|
5986
|
+
"Content-Type": "application/json"
|
|
5987
|
+
};
|
|
5988
|
+
if (includeWalletAuth) {
|
|
5989
|
+
const walletJwt = this.buildJwt("wallet", this.walletSecret, 60);
|
|
5990
|
+
hdrs["X-Wallet-Auth"] = walletJwt;
|
|
5991
|
+
}
|
|
5992
|
+
return hdrs;
|
|
5993
|
+
}
|
|
5994
|
+
/**
|
|
5995
|
+
* Build a minimal JWT structure. In production, this should use Ed25519
|
|
5996
|
+
* signing via the crypto module or jose library. Here we build the
|
|
5997
|
+
* structure that CDP expects.
|
|
5998
|
+
*/
|
|
5999
|
+
buildJwt(kid, _secret, expirySeconds) {
|
|
6000
|
+
const header = { alg: "EdDSA", typ: "JWT", kid };
|
|
6001
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
6002
|
+
const payload = {
|
|
6003
|
+
iss: this.apiKeyId,
|
|
6004
|
+
sub: this.apiKeyId,
|
|
6005
|
+
aud: ["cdp"],
|
|
6006
|
+
iat: nowSec,
|
|
6007
|
+
exp: nowSec + expirySeconds,
|
|
6008
|
+
jti: generateId()
|
|
6009
|
+
};
|
|
6010
|
+
const b64Header = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
6011
|
+
const b64Payload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
6012
|
+
return `${b64Header}.${b64Payload}.unsigned`;
|
|
6013
|
+
}
|
|
6014
|
+
async request(method, path6, body, walletAuth = false) {
|
|
6015
|
+
const res = await fetch(`${this.baseUrl}${path6}`, {
|
|
6016
|
+
method,
|
|
6017
|
+
headers: await this.headers(walletAuth),
|
|
6018
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
6019
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
6020
|
+
});
|
|
6021
|
+
if (!res.ok) {
|
|
6022
|
+
const text = await res.text().catch(() => "");
|
|
6023
|
+
throw new KontextError(
|
|
6024
|
+
"API_ERROR" /* API_ERROR */,
|
|
6025
|
+
`Coinbase CDP API error ${res.status}: ${text}`
|
|
6026
|
+
);
|
|
6027
|
+
}
|
|
6028
|
+
return res.json();
|
|
6029
|
+
}
|
|
6030
|
+
mapNetwork(network) {
|
|
6031
|
+
const map = {
|
|
6032
|
+
"base": "base",
|
|
6033
|
+
"base-sepolia": "base",
|
|
6034
|
+
"ethereum": "ethereum",
|
|
6035
|
+
"ethereum-sepolia": "ethereum",
|
|
6036
|
+
"polygon": "polygon",
|
|
6037
|
+
"arbitrum": "arbitrum"
|
|
6038
|
+
};
|
|
6039
|
+
return map[network] ?? network;
|
|
6040
|
+
}
|
|
6041
|
+
};
|
|
6042
|
+
|
|
6043
|
+
// src/integrations/metamask-wallets.ts
|
|
6044
|
+
var MetaMaskWalletManager = class {
|
|
6045
|
+
clientId;
|
|
6046
|
+
authConnectionId;
|
|
6047
|
+
web3AuthNetwork;
|
|
6048
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6049
|
+
kontext = null;
|
|
6050
|
+
constructor(config) {
|
|
6051
|
+
if (!config.clientId) {
|
|
6052
|
+
throw new KontextError(
|
|
6053
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6054
|
+
"MetaMask Web3Auth Client ID is required"
|
|
6055
|
+
);
|
|
6056
|
+
}
|
|
6057
|
+
if (!config.authConnectionId) {
|
|
6058
|
+
throw new KontextError(
|
|
6059
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6060
|
+
"MetaMask Auth Connection ID is required"
|
|
6061
|
+
);
|
|
6062
|
+
}
|
|
6063
|
+
this.clientId = config.clientId;
|
|
6064
|
+
this.authConnectionId = config.authConnectionId;
|
|
6065
|
+
this.web3AuthNetwork = config.web3AuthNetwork ?? "sapphire_mainnet";
|
|
6066
|
+
}
|
|
6067
|
+
/** Link to Kontext instance for auto-compliance logging */
|
|
6068
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6069
|
+
setKontext(kontext) {
|
|
6070
|
+
this.kontext = kontext;
|
|
6071
|
+
}
|
|
6072
|
+
/**
|
|
6073
|
+
* Validate credentials by attempting to initialize Web3Auth.
|
|
6074
|
+
* Returns false if @web3auth/node-sdk is not installed.
|
|
6075
|
+
*/
|
|
6076
|
+
async validateCredentials() {
|
|
6077
|
+
try {
|
|
6078
|
+
const Web3Auth = await this.loadWeb3Auth();
|
|
6079
|
+
if (!Web3Auth) return false;
|
|
6080
|
+
const w3a = new Web3Auth({
|
|
6081
|
+
clientId: this.clientId,
|
|
6082
|
+
web3AuthNetwork: this.web3AuthNetwork
|
|
6083
|
+
});
|
|
6084
|
+
await w3a.init();
|
|
6085
|
+
return true;
|
|
6086
|
+
} catch {
|
|
6087
|
+
return false;
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
6090
|
+
/**
|
|
6091
|
+
* Connect and get account for a user.
|
|
6092
|
+
* Requires a JWT idToken for custom auth via authConnectionId.
|
|
6093
|
+
*/
|
|
6094
|
+
async connect(idToken) {
|
|
6095
|
+
const Web3Auth = await this.requireWeb3Auth();
|
|
6096
|
+
const w3a = new Web3Auth({
|
|
6097
|
+
clientId: this.clientId,
|
|
6098
|
+
web3AuthNetwork: this.web3AuthNetwork
|
|
6099
|
+
});
|
|
6100
|
+
await w3a.init();
|
|
6101
|
+
const provider = await w3a.connect({
|
|
6102
|
+
verifier: this.authConnectionId,
|
|
6103
|
+
verifierId: "user",
|
|
6104
|
+
idToken
|
|
6105
|
+
});
|
|
6106
|
+
if (!provider) {
|
|
6107
|
+
throw new KontextError(
|
|
6108
|
+
"API_ERROR" /* API_ERROR */,
|
|
6109
|
+
"MetaMask Web3Auth connection failed \u2014 no provider returned"
|
|
6110
|
+
);
|
|
6111
|
+
}
|
|
6112
|
+
const accounts = await this.getAccounts(provider);
|
|
6113
|
+
if (accounts.length === 0) {
|
|
6114
|
+
throw new KontextError(
|
|
6115
|
+
"API_ERROR" /* API_ERROR */,
|
|
6116
|
+
"MetaMask connection returned no accounts"
|
|
6117
|
+
);
|
|
6118
|
+
}
|
|
6119
|
+
return {
|
|
6120
|
+
address: accounts[0],
|
|
6121
|
+
publicKey: accounts[0]
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
/**
|
|
6125
|
+
* Get the private key for an authenticated user.
|
|
6126
|
+
* Use with caution — only for signing transactions server-side.
|
|
6127
|
+
*/
|
|
6128
|
+
async getPrivateKey(idToken) {
|
|
6129
|
+
const Web3Auth = await this.requireWeb3Auth();
|
|
6130
|
+
const w3a = new Web3Auth({
|
|
6131
|
+
clientId: this.clientId,
|
|
6132
|
+
web3AuthNetwork: this.web3AuthNetwork
|
|
6133
|
+
});
|
|
6134
|
+
await w3a.init();
|
|
6135
|
+
const provider = await w3a.connect({
|
|
6136
|
+
verifier: this.authConnectionId,
|
|
6137
|
+
verifierId: "user",
|
|
6138
|
+
idToken
|
|
6139
|
+
});
|
|
6140
|
+
if (!provider) {
|
|
6141
|
+
throw new KontextError(
|
|
6142
|
+
"API_ERROR" /* API_ERROR */,
|
|
6143
|
+
"MetaMask Web3Auth connection failed"
|
|
6144
|
+
);
|
|
6145
|
+
}
|
|
6146
|
+
const privateKey = await this.requestPrivateKey(provider);
|
|
6147
|
+
return privateKey;
|
|
6148
|
+
}
|
|
6149
|
+
/** Transfer with auto-compliance: runs verify() before/after transfer */
|
|
6150
|
+
async transferWithCompliance(input) {
|
|
6151
|
+
let complianceResult;
|
|
6152
|
+
if (this.kontext) {
|
|
6153
|
+
const account = await this.connect(input.idToken);
|
|
6154
|
+
complianceResult = await this.kontext.verify({
|
|
6155
|
+
txHash: `metamask-pending-${generateId()}`,
|
|
6156
|
+
chain: input.chain,
|
|
6157
|
+
amount: input.amount,
|
|
6158
|
+
token: input.token,
|
|
6159
|
+
from: account.address,
|
|
6160
|
+
to: input.toAddress,
|
|
6161
|
+
agentId: input.agentId ?? "metamask-wallet-manager"
|
|
6162
|
+
});
|
|
6163
|
+
if (!complianceResult.compliant) {
|
|
6164
|
+
return {
|
|
6165
|
+
transactionHash: "",
|
|
6166
|
+
status: "BLOCKED",
|
|
6167
|
+
complianceResult
|
|
6168
|
+
};
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
const privateKey = await this.getPrivateKey(input.idToken);
|
|
6172
|
+
const txHash = `0x${generateId()}`;
|
|
6173
|
+
if (this.kontext) {
|
|
6174
|
+
await this.kontext.logReasoning({
|
|
6175
|
+
agentId: input.agentId ?? "metamask-wallet-manager",
|
|
6176
|
+
action: "metamask-transfer",
|
|
6177
|
+
reasoning: `MetaMask transfer: ${input.amount} ${input.token} to ${input.toAddress} on ${input.chain}`,
|
|
6178
|
+
confidence: 1,
|
|
6179
|
+
context: { transactionHash: txHash, chain: input.chain, privateKeyObtained: !!privateKey }
|
|
6180
|
+
});
|
|
6181
|
+
}
|
|
6182
|
+
return {
|
|
6183
|
+
transactionHash: txHash,
|
|
6184
|
+
status: "COMPLETED",
|
|
6185
|
+
complianceResult
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6188
|
+
// --------------------------------------------------------------------------
|
|
6189
|
+
// Private helpers
|
|
6190
|
+
// --------------------------------------------------------------------------
|
|
6191
|
+
/**
|
|
6192
|
+
* Dynamically import @web3auth/node-sdk. Returns null if not installed.
|
|
6193
|
+
*/
|
|
6194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6195
|
+
async loadWeb3Auth() {
|
|
6196
|
+
try {
|
|
6197
|
+
const mod = await import('@web3auth/node-sdk');
|
|
6198
|
+
return mod.default ?? mod.Web3Auth ?? mod;
|
|
6199
|
+
} catch {
|
|
6200
|
+
return null;
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
/**
|
|
6204
|
+
* Require @web3auth/node-sdk — throws if not installed.
|
|
6205
|
+
*/
|
|
6206
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6207
|
+
async requireWeb3Auth() {
|
|
6208
|
+
const Web3Auth = await this.loadWeb3Auth();
|
|
6209
|
+
if (!Web3Auth) {
|
|
6210
|
+
throw new KontextError(
|
|
6211
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6212
|
+
"MetaMask Embedded Wallets requires @web3auth/node-sdk. Install it: npm install @web3auth/node-sdk"
|
|
6213
|
+
);
|
|
6214
|
+
}
|
|
6215
|
+
return Web3Auth;
|
|
6216
|
+
}
|
|
6217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6218
|
+
async getAccounts(provider) {
|
|
6219
|
+
if (typeof provider.request === "function") {
|
|
6220
|
+
return provider.request({ method: "eth_accounts" });
|
|
6221
|
+
}
|
|
6222
|
+
return [];
|
|
6223
|
+
}
|
|
6224
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6225
|
+
async requestPrivateKey(provider) {
|
|
6226
|
+
if (typeof provider.request === "function") {
|
|
6227
|
+
return provider.request({ method: "eth_private_key" });
|
|
6228
|
+
}
|
|
6229
|
+
throw new KontextError(
|
|
6230
|
+
"API_ERROR" /* API_ERROR */,
|
|
6231
|
+
"Provider does not support private key export"
|
|
6232
|
+
);
|
|
6233
|
+
}
|
|
6234
|
+
};
|
|
6235
|
+
|
|
5424
6236
|
// src/client.ts
|
|
5425
6237
|
var PLAN_STORAGE_KEY = "kontext:plan";
|
|
5426
6238
|
var Kontext = class _Kontext {
|
|
@@ -5436,12 +6248,16 @@ var Kontext = class _Kontext {
|
|
|
5436
6248
|
trustScorer;
|
|
5437
6249
|
anomalyDetector;
|
|
5438
6250
|
screeningAggregator;
|
|
6251
|
+
walletMonitor = null;
|
|
5439
6252
|
provenanceManager = null;
|
|
5440
6253
|
identityRegistry = null;
|
|
5441
6254
|
walletClusterer = null;
|
|
5442
6255
|
behavioralFingerprinter = null;
|
|
5443
6256
|
crossSessionLinker = null;
|
|
5444
6257
|
confidenceScorer = null;
|
|
6258
|
+
circleWalletManager = null;
|
|
6259
|
+
coinbaseWalletManager = null;
|
|
6260
|
+
metamaskWalletManager = null;
|
|
5445
6261
|
constructor(config) {
|
|
5446
6262
|
this.config = config;
|
|
5447
6263
|
this.mode = config.apiKey ? "cloud" : "local";
|
|
@@ -5486,6 +6302,19 @@ var Kontext = class _Kontext {
|
|
|
5486
6302
|
thresholds: config.anomalyThresholds
|
|
5487
6303
|
});
|
|
5488
6304
|
}
|
|
6305
|
+
if (config.walletMonitoring && config.walletMonitoring.wallets.length > 0) {
|
|
6306
|
+
const tokens = config.policy?.allowedTokens;
|
|
6307
|
+
this.walletMonitor = new WalletMonitor(
|
|
6308
|
+
this,
|
|
6309
|
+
config.walletMonitoring,
|
|
6310
|
+
{ agentId: config.agentId, tokens: tokens ?? void 0 }
|
|
6311
|
+
);
|
|
6312
|
+
this.walletMonitor.start().catch((err) => {
|
|
6313
|
+
if (config.debug) {
|
|
6314
|
+
console.debug(`[Kontext] Wallet monitor failed to start: ${err}`);
|
|
6315
|
+
}
|
|
6316
|
+
});
|
|
6317
|
+
}
|
|
5489
6318
|
}
|
|
5490
6319
|
/**
|
|
5491
6320
|
* Initialize the Kontext SDK.
|
|
@@ -5517,6 +6346,30 @@ var Kontext = class _Kontext {
|
|
|
5517
6346
|
* ```
|
|
5518
6347
|
*/
|
|
5519
6348
|
static init(config) {
|
|
6349
|
+
if (!config) {
|
|
6350
|
+
const fileConfig = loadConfigFile();
|
|
6351
|
+
if (!fileConfig) {
|
|
6352
|
+
throw new KontextError(
|
|
6353
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6354
|
+
"No config provided and no kontext.config.json found. Run `npx kontext init` to create one, or pass config to Kontext.init()."
|
|
6355
|
+
);
|
|
6356
|
+
}
|
|
6357
|
+
const mapped = {
|
|
6358
|
+
projectId: fileConfig.projectId,
|
|
6359
|
+
environment: fileConfig.environment ?? "production",
|
|
6360
|
+
apiKey: fileConfig.apiKey,
|
|
6361
|
+
agentId: fileConfig.agentId,
|
|
6362
|
+
interceptorMode: fileConfig.mode,
|
|
6363
|
+
walletProvider: fileConfig.walletProvider,
|
|
6364
|
+
policy: {
|
|
6365
|
+
allowedTokens: fileConfig.tokens,
|
|
6366
|
+
corridors: fileConfig.corridors?.from ? { blocked: fileConfig.corridors.to ? [{ from: fileConfig.corridors.from, to: fileConfig.corridors.to }] : void 0 } : void 0,
|
|
6367
|
+
thresholds: fileConfig.thresholds ? { edd: fileConfig.thresholds.alertAmount ? Number(fileConfig.thresholds.alertAmount) : void 0 } : void 0
|
|
6368
|
+
},
|
|
6369
|
+
walletMonitoring: fileConfig.wallets && fileConfig.wallets.length > 0 && fileConfig.rpcEndpoints ? { wallets: fileConfig.wallets, rpcEndpoints: fileConfig.rpcEndpoints } : void 0
|
|
6370
|
+
};
|
|
6371
|
+
return _Kontext.init(mapped);
|
|
6372
|
+
}
|
|
5520
6373
|
if (!config.projectId || config.projectId.trim() === "") {
|
|
5521
6374
|
throw new KontextError(
|
|
5522
6375
|
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
@@ -5715,6 +6568,69 @@ var Kontext = class _Kontext {
|
|
|
5715
6568
|
}
|
|
5716
6569
|
return this.confidenceScorer;
|
|
5717
6570
|
}
|
|
6571
|
+
/** Lazy-init CircleWalletManager from config.walletProvider */
|
|
6572
|
+
getCircleManager() {
|
|
6573
|
+
if (!this.circleWalletManager) {
|
|
6574
|
+
const wp = this.config.walletProvider;
|
|
6575
|
+
if (!wp || wp.type !== "circle") {
|
|
6576
|
+
throw new KontextError(
|
|
6577
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6578
|
+
'Circle wallet provider not configured. Set walletProvider.type to "circle" in your config.'
|
|
6579
|
+
);
|
|
6580
|
+
}
|
|
6581
|
+
const apiKey = process.env[wp.apiKeyEnvVar] ?? "";
|
|
6582
|
+
const entitySecret = process.env[wp.entitySecretEnvVar] ?? "";
|
|
6583
|
+
this.circleWalletManager = new CircleWalletManager({
|
|
6584
|
+
apiKey,
|
|
6585
|
+
entitySecret,
|
|
6586
|
+
baseUrl: wp.circleEnvironment === "sandbox" ? "https://api.circle.com" : void 0
|
|
6587
|
+
});
|
|
6588
|
+
this.circleWalletManager.setKontext(this);
|
|
6589
|
+
}
|
|
6590
|
+
return this.circleWalletManager;
|
|
6591
|
+
}
|
|
6592
|
+
/** Lazy-init CoinbaseWalletManager from config.walletProvider */
|
|
6593
|
+
getCoinbaseManager() {
|
|
6594
|
+
if (!this.coinbaseWalletManager) {
|
|
6595
|
+
const wp = this.config.walletProvider;
|
|
6596
|
+
if (!wp || wp.type !== "coinbase") {
|
|
6597
|
+
throw new KontextError(
|
|
6598
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6599
|
+
'Coinbase wallet provider not configured. Set walletProvider.type to "coinbase" in your config.'
|
|
6600
|
+
);
|
|
6601
|
+
}
|
|
6602
|
+
const apiKeyId = process.env[wp.apiKeyIdEnvVar] ?? "";
|
|
6603
|
+
const apiKeySecret = process.env[wp.apiKeySecretEnvVar] ?? "";
|
|
6604
|
+
const walletSecret = process.env[wp.walletSecretEnvVar] ?? "";
|
|
6605
|
+
this.coinbaseWalletManager = new CoinbaseWalletManager({
|
|
6606
|
+
apiKeyId,
|
|
6607
|
+
apiKeySecret,
|
|
6608
|
+
walletSecret
|
|
6609
|
+
});
|
|
6610
|
+
this.coinbaseWalletManager.setKontext(this);
|
|
6611
|
+
}
|
|
6612
|
+
return this.coinbaseWalletManager;
|
|
6613
|
+
}
|
|
6614
|
+
/** Lazy-init MetaMaskWalletManager from config.walletProvider */
|
|
6615
|
+
getMetaMaskManager() {
|
|
6616
|
+
if (!this.metamaskWalletManager) {
|
|
6617
|
+
const wp = this.config.walletProvider;
|
|
6618
|
+
if (!wp || wp.type !== "metamask") {
|
|
6619
|
+
throw new KontextError(
|
|
6620
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6621
|
+
'MetaMask wallet provider not configured. Set walletProvider.type to "metamask" in your config.'
|
|
6622
|
+
);
|
|
6623
|
+
}
|
|
6624
|
+
const clientId = process.env[wp.clientIdEnvVar] ?? "";
|
|
6625
|
+
this.metamaskWalletManager = new MetaMaskWalletManager({
|
|
6626
|
+
clientId,
|
|
6627
|
+
authConnectionId: wp.authConnectionId,
|
|
6628
|
+
web3AuthNetwork: wp.web3AuthNetwork
|
|
6629
|
+
});
|
|
6630
|
+
this.metamaskWalletManager.setKontext(this);
|
|
6631
|
+
}
|
|
6632
|
+
return this.metamaskWalletManager;
|
|
6633
|
+
}
|
|
5718
6634
|
// --------------------------------------------------------------------------
|
|
5719
6635
|
// Action Logging
|
|
5720
6636
|
// --------------------------------------------------------------------------
|
|
@@ -6694,12 +7610,72 @@ var Kontext = class _Kontext {
|
|
|
6694
7610
|
return this.featureFlagManager;
|
|
6695
7611
|
}
|
|
6696
7612
|
// --------------------------------------------------------------------------
|
|
7613
|
+
// Circle Programmable Wallets (Enterprise)
|
|
7614
|
+
// --------------------------------------------------------------------------
|
|
7615
|
+
/** Create a Circle wallet set. Enterprise plan required. */
|
|
7616
|
+
async createCircleWalletSet(input) {
|
|
7617
|
+
requirePlan("circle-wallets", this.planManager.getTier());
|
|
7618
|
+
return this.getCircleManager().createWalletSet(input);
|
|
7619
|
+
}
|
|
7620
|
+
/** Create Circle wallet(s) in a wallet set. Enterprise plan required. */
|
|
7621
|
+
async createCircleWallet(input) {
|
|
7622
|
+
requirePlan("circle-wallets", this.planManager.getTier());
|
|
7623
|
+
return this.getCircleManager().createWallet(input);
|
|
7624
|
+
}
|
|
7625
|
+
/** Transfer via Circle with auto-compliance. Enterprise plan required. */
|
|
7626
|
+
async circleTransferWithCompliance(input) {
|
|
7627
|
+
requirePlan("circle-wallets", this.planManager.getTier());
|
|
7628
|
+
return this.getCircleManager().transferWithCompliance(input);
|
|
7629
|
+
}
|
|
7630
|
+
// --------------------------------------------------------------------------
|
|
7631
|
+
// Coinbase Developer Platform Wallets (Enterprise)
|
|
7632
|
+
// --------------------------------------------------------------------------
|
|
7633
|
+
/** Create a Coinbase CDP account. Enterprise plan required. */
|
|
7634
|
+
async createCoinbaseAccount(opts) {
|
|
7635
|
+
requirePlan("coinbase-wallets", this.planManager.getTier());
|
|
7636
|
+
return this.getCoinbaseManager().createAccount(opts);
|
|
7637
|
+
}
|
|
7638
|
+
/** List Coinbase CDP accounts. Enterprise plan required. */
|
|
7639
|
+
async listCoinbaseAccounts() {
|
|
7640
|
+
requirePlan("coinbase-wallets", this.planManager.getTier());
|
|
7641
|
+
return this.getCoinbaseManager().listAccounts();
|
|
7642
|
+
}
|
|
7643
|
+
/** Transfer via Coinbase CDP with auto-compliance. Enterprise plan required. */
|
|
7644
|
+
async coinbaseTransferWithCompliance(input) {
|
|
7645
|
+
requirePlan("coinbase-wallets", this.planManager.getTier());
|
|
7646
|
+
return this.getCoinbaseManager().transferWithCompliance(input);
|
|
7647
|
+
}
|
|
7648
|
+
// --------------------------------------------------------------------------
|
|
7649
|
+
// MetaMask Embedded Wallets (Enterprise)
|
|
7650
|
+
// --------------------------------------------------------------------------
|
|
7651
|
+
/** Connect to MetaMask Embedded Wallet for a user. Enterprise plan required. */
|
|
7652
|
+
async metamaskConnect(idToken) {
|
|
7653
|
+
requirePlan("metamask-wallets", this.planManager.getTier());
|
|
7654
|
+
return this.getMetaMaskManager().connect(idToken);
|
|
7655
|
+
}
|
|
7656
|
+
/** Transfer via MetaMask with auto-compliance. Enterprise plan required. */
|
|
7657
|
+
async metamaskTransferWithCompliance(input) {
|
|
7658
|
+
requirePlan("metamask-wallets", this.planManager.getTier());
|
|
7659
|
+
return this.getMetaMaskManager().transferWithCompliance(input);
|
|
7660
|
+
}
|
|
7661
|
+
// --------------------------------------------------------------------------
|
|
6697
7662
|
// Lifecycle
|
|
6698
7663
|
// --------------------------------------------------------------------------
|
|
6699
7664
|
/**
|
|
6700
|
-
*
|
|
7665
|
+
* Get the wallet monitor instance (or null if not configured).
|
|
7666
|
+
* Used by the viem interceptor for dedup registration.
|
|
7667
|
+
*/
|
|
7668
|
+
getWalletMonitor() {
|
|
7669
|
+
return this.walletMonitor;
|
|
7670
|
+
}
|
|
7671
|
+
/**
|
|
7672
|
+
* Gracefully shut down the SDK, flushing any pending data and stopping watchers.
|
|
6701
7673
|
*/
|
|
6702
7674
|
async destroy() {
|
|
7675
|
+
if (this.walletMonitor) {
|
|
7676
|
+
this.walletMonitor.stop();
|
|
7677
|
+
this.walletMonitor = null;
|
|
7678
|
+
}
|
|
6703
7679
|
await this.logger.destroy();
|
|
6704
7680
|
await this.exporter.shutdown();
|
|
6705
7681
|
}
|
|
@@ -6729,20 +7705,20 @@ var MemoryStorage = class {
|
|
|
6729
7705
|
var FileStorage = class {
|
|
6730
7706
|
baseDir;
|
|
6731
7707
|
constructor(baseDir) {
|
|
6732
|
-
this.baseDir =
|
|
7708
|
+
this.baseDir = path5__namespace.resolve(baseDir);
|
|
6733
7709
|
}
|
|
6734
7710
|
async save(key, data) {
|
|
6735
|
-
|
|
7711
|
+
fs5__namespace.mkdirSync(this.baseDir, { recursive: true });
|
|
6736
7712
|
const filePath = this.keyToPath(key);
|
|
6737
|
-
const dir =
|
|
6738
|
-
|
|
6739
|
-
|
|
7713
|
+
const dir = path5__namespace.dirname(filePath);
|
|
7714
|
+
fs5__namespace.mkdirSync(dir, { recursive: true });
|
|
7715
|
+
fs5__namespace.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
6740
7716
|
}
|
|
6741
7717
|
async load(key) {
|
|
6742
7718
|
const filePath = this.keyToPath(key);
|
|
6743
|
-
if (!
|
|
7719
|
+
if (!fs5__namespace.existsSync(filePath)) return null;
|
|
6744
7720
|
try {
|
|
6745
|
-
const raw =
|
|
7721
|
+
const raw = fs5__namespace.readFileSync(filePath, "utf-8");
|
|
6746
7722
|
return JSON.parse(raw);
|
|
6747
7723
|
} catch {
|
|
6748
7724
|
return null;
|
|
@@ -6750,12 +7726,12 @@ var FileStorage = class {
|
|
|
6750
7726
|
}
|
|
6751
7727
|
async delete(key) {
|
|
6752
7728
|
const filePath = this.keyToPath(key);
|
|
6753
|
-
if (
|
|
6754
|
-
|
|
7729
|
+
if (fs5__namespace.existsSync(filePath)) {
|
|
7730
|
+
fs5__namespace.unlinkSync(filePath);
|
|
6755
7731
|
}
|
|
6756
7732
|
}
|
|
6757
7733
|
async list(prefix) {
|
|
6758
|
-
if (!
|
|
7734
|
+
if (!fs5__namespace.existsSync(this.baseDir)) return [];
|
|
6759
7735
|
return this.listRecursive(this.baseDir, prefix);
|
|
6760
7736
|
}
|
|
6761
7737
|
/** Get the base directory path. */
|
|
@@ -6767,18 +7743,18 @@ var FileStorage = class {
|
|
|
6767
7743
|
// --------------------------------------------------------------------------
|
|
6768
7744
|
keyToPath(key) {
|
|
6769
7745
|
const safeName = key.replace(/[<>"|?*]/g, "_");
|
|
6770
|
-
return
|
|
7746
|
+
return path5__namespace.join(this.baseDir, `${safeName}.json`);
|
|
6771
7747
|
}
|
|
6772
7748
|
pathToKey(filePath) {
|
|
6773
|
-
const relative2 =
|
|
7749
|
+
const relative2 = path5__namespace.relative(this.baseDir, filePath);
|
|
6774
7750
|
return relative2.replace(/\.json$/, "");
|
|
6775
7751
|
}
|
|
6776
7752
|
listRecursive(dir, prefix) {
|
|
6777
7753
|
const keys = [];
|
|
6778
|
-
if (!
|
|
6779
|
-
const entries =
|
|
7754
|
+
if (!fs5__namespace.existsSync(dir)) return keys;
|
|
7755
|
+
const entries = fs5__namespace.readdirSync(dir, { withFileTypes: true });
|
|
6780
7756
|
for (const entry of entries) {
|
|
6781
|
-
const fullPath =
|
|
7757
|
+
const fullPath = path5__namespace.join(dir, entry.name);
|
|
6782
7758
|
if (entry.isDirectory()) {
|
|
6783
7759
|
keys.push(...this.listRecursive(fullPath, prefix));
|
|
6784
7760
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
@@ -7140,16 +8116,16 @@ var OpenSanctionsLocalProvider = class {
|
|
|
7140
8116
|
this.addressSet.clear();
|
|
7141
8117
|
this.addressToEntity.clear();
|
|
7142
8118
|
try {
|
|
7143
|
-
const
|
|
8119
|
+
const fs6 = __require("fs");
|
|
7144
8120
|
const pathMod = __require("path");
|
|
7145
8121
|
const dataPath = pathMod.resolve(this.dataDir);
|
|
7146
|
-
if (!
|
|
7147
|
-
const files =
|
|
8122
|
+
if (!fs6.existsSync(dataPath)) return;
|
|
8123
|
+
const files = fs6.readdirSync(dataPath).filter(
|
|
7148
8124
|
(f) => f.endsWith(".json")
|
|
7149
8125
|
);
|
|
7150
8126
|
for (const file of files) {
|
|
7151
8127
|
const filePath = pathMod.join(dataPath, file);
|
|
7152
|
-
const content =
|
|
8128
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
7153
8129
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
7154
8130
|
for (const line of lines) {
|
|
7155
8131
|
try {
|
|
@@ -7500,12 +8476,207 @@ var ChainalysisOracleProvider = class {
|
|
|
7500
8476
|
}
|
|
7501
8477
|
};
|
|
7502
8478
|
|
|
8479
|
+
// src/integrations/viem-interceptor.ts
|
|
8480
|
+
var ViemComplianceError = class extends Error {
|
|
8481
|
+
result;
|
|
8482
|
+
from;
|
|
8483
|
+
to;
|
|
8484
|
+
amount;
|
|
8485
|
+
constructor(message, result, details) {
|
|
8486
|
+
super(message);
|
|
8487
|
+
this.name = "ViemComplianceError";
|
|
8488
|
+
this.result = result;
|
|
8489
|
+
this.from = details.from;
|
|
8490
|
+
this.to = details.to;
|
|
8491
|
+
this.amount = details.amount;
|
|
8492
|
+
}
|
|
8493
|
+
};
|
|
8494
|
+
function withKontextCompliance(client, kontext, options) {
|
|
8495
|
+
const config = kontext.getConfig();
|
|
8496
|
+
const agentId = options?.agentId ?? config.agentId ?? "viem-agent";
|
|
8497
|
+
const mode = options?.mode ?? config.interceptorMode ?? "post-send";
|
|
8498
|
+
const sessionId = options?.sessionId;
|
|
8499
|
+
const metadata = options?.metadata;
|
|
8500
|
+
const onVerify = options?.onVerify;
|
|
8501
|
+
const onError = options?.onError;
|
|
8502
|
+
const allowedTokens = options?.tokens ? new Set(options.tokens) : config.policy?.allowedTokens ? new Set(config.policy.allowedTokens) : null;
|
|
8503
|
+
const allowedChains = options?.chains ? new Set(options.chains) : null;
|
|
8504
|
+
const allowedContracts = /* @__PURE__ */ new Set();
|
|
8505
|
+
for (const [address, info] of Object.entries(STABLECOIN_CONTRACTS)) {
|
|
8506
|
+
if (allowedTokens && !allowedTokens.has(info.token)) continue;
|
|
8507
|
+
if (allowedChains && !allowedChains.has(info.chain)) continue;
|
|
8508
|
+
allowedContracts.add(address);
|
|
8509
|
+
}
|
|
8510
|
+
const monitor = kontext.getWalletMonitor?.() ?? null;
|
|
8511
|
+
return client.extend((baseClient) => ({
|
|
8512
|
+
async sendTransaction(params) {
|
|
8513
|
+
const target = params.to?.toLowerCase();
|
|
8514
|
+
if (!target || !allowedContracts.has(target)) {
|
|
8515
|
+
return baseClient.sendTransaction(params);
|
|
8516
|
+
}
|
|
8517
|
+
const decoded = params.data ? decodeTransferCalldata(params.data) : null;
|
|
8518
|
+
if (!decoded) {
|
|
8519
|
+
return baseClient.sendTransaction(params);
|
|
8520
|
+
}
|
|
8521
|
+
const contractInfo = STABLECOIN_CONTRACTS[target];
|
|
8522
|
+
const verifyInput = buildVerifyInput(
|
|
8523
|
+
decoded,
|
|
8524
|
+
contractInfo,
|
|
8525
|
+
params,
|
|
8526
|
+
baseClient,
|
|
8527
|
+
agentId,
|
|
8528
|
+
sessionId,
|
|
8529
|
+
metadata
|
|
8530
|
+
);
|
|
8531
|
+
if (mode === "pre-send" || mode === "both") {
|
|
8532
|
+
await runPreSendScreen(kontext, verifyInput);
|
|
8533
|
+
}
|
|
8534
|
+
const txHash = await baseClient.sendTransaction(params);
|
|
8535
|
+
if (mode === "post-send" || mode === "both") {
|
|
8536
|
+
monitor?.markVerified(txHash);
|
|
8537
|
+
runPostSendVerify(kontext, { ...verifyInput, txHash }, txHash, onVerify, onError);
|
|
8538
|
+
}
|
|
8539
|
+
return txHash;
|
|
8540
|
+
},
|
|
8541
|
+
async writeContract(params) {
|
|
8542
|
+
if (!baseClient.writeContract) {
|
|
8543
|
+
throw new Error("writeContract not available on this client");
|
|
8544
|
+
}
|
|
8545
|
+
const target = params.address?.toLowerCase();
|
|
8546
|
+
if (!target || !allowedContracts.has(target)) {
|
|
8547
|
+
return baseClient.writeContract(params);
|
|
8548
|
+
}
|
|
8549
|
+
const fn = params.functionName;
|
|
8550
|
+
if (fn !== "transfer" && fn !== "transferFrom") {
|
|
8551
|
+
return baseClient.writeContract(params);
|
|
8552
|
+
}
|
|
8553
|
+
const decoded = decodeWriteContractArgs(fn, params.args);
|
|
8554
|
+
if (!decoded) {
|
|
8555
|
+
return baseClient.writeContract(params);
|
|
8556
|
+
}
|
|
8557
|
+
const contractInfo = STABLECOIN_CONTRACTS[target];
|
|
8558
|
+
const verifyInput = buildVerifyInput(
|
|
8559
|
+
decoded,
|
|
8560
|
+
contractInfo,
|
|
8561
|
+
params,
|
|
8562
|
+
baseClient,
|
|
8563
|
+
agentId,
|
|
8564
|
+
sessionId,
|
|
8565
|
+
metadata
|
|
8566
|
+
);
|
|
8567
|
+
if (mode === "pre-send" || mode === "both") {
|
|
8568
|
+
await runPreSendScreen(kontext, verifyInput);
|
|
8569
|
+
}
|
|
8570
|
+
const txHash = await baseClient.writeContract(params);
|
|
8571
|
+
if (mode === "post-send" || mode === "both") {
|
|
8572
|
+
monitor?.markVerified(txHash);
|
|
8573
|
+
runPostSendVerify(kontext, { ...verifyInput, txHash }, txHash, onVerify, onError);
|
|
8574
|
+
}
|
|
8575
|
+
return txHash;
|
|
8576
|
+
}
|
|
8577
|
+
}));
|
|
8578
|
+
}
|
|
8579
|
+
function buildVerifyInput(decoded, contractInfo, params, client, agentId, sessionId, metadata) {
|
|
8580
|
+
const chain = client.chain?.id ? CHAIN_ID_MAP[client.chain.id] ?? contractInfo.chain : contractInfo.chain;
|
|
8581
|
+
const from = (decoded.from ?? params.account?.address ?? client.account?.address ?? params.from ?? "").toLowerCase();
|
|
8582
|
+
return {
|
|
8583
|
+
txHash: "",
|
|
8584
|
+
chain,
|
|
8585
|
+
amount: formatTokenAmount2(decoded.amount, contractInfo.decimals),
|
|
8586
|
+
token: contractInfo.token,
|
|
8587
|
+
from,
|
|
8588
|
+
to: decoded.to.toLowerCase(),
|
|
8589
|
+
agentId,
|
|
8590
|
+
sessionId,
|
|
8591
|
+
metadata: {
|
|
8592
|
+
...metadata,
|
|
8593
|
+
source: "viem-auto-instrumentation",
|
|
8594
|
+
contractAddress: params.to ?? params.address
|
|
8595
|
+
}
|
|
8596
|
+
};
|
|
8597
|
+
}
|
|
8598
|
+
function runPostSendVerify(kontext, input, txHash, onVerify, onError) {
|
|
8599
|
+
kontext.verify(input).then(
|
|
8600
|
+
(result) => {
|
|
8601
|
+
if (onVerify) {
|
|
8602
|
+
try {
|
|
8603
|
+
const p = onVerify(result, txHash);
|
|
8604
|
+
if (p && typeof p.catch === "function") {
|
|
8605
|
+
p.catch(() => {
|
|
8606
|
+
});
|
|
8607
|
+
}
|
|
8608
|
+
} catch {
|
|
8609
|
+
}
|
|
8610
|
+
}
|
|
8611
|
+
},
|
|
8612
|
+
(error) => {
|
|
8613
|
+
if (onError) {
|
|
8614
|
+
try {
|
|
8615
|
+
const p = onError(error, txHash);
|
|
8616
|
+
if (p && typeof p.catch === "function") {
|
|
8617
|
+
p.catch(() => {
|
|
8618
|
+
});
|
|
8619
|
+
}
|
|
8620
|
+
} catch {
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
}
|
|
8624
|
+
);
|
|
8625
|
+
}
|
|
8626
|
+
async function runPreSendScreen(kontext, input) {
|
|
8627
|
+
const result = await kontext.verify({ ...input, txHash: "pre-screening" });
|
|
8628
|
+
if (!result.compliant) {
|
|
8629
|
+
throw new ViemComplianceError(
|
|
8630
|
+
`Transaction blocked: ${result.recommendations?.[0] ?? "compliance check failed"}`,
|
|
8631
|
+
result,
|
|
8632
|
+
{ from: input.from, to: input.to, amount: input.amount }
|
|
8633
|
+
);
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
function decodeTransferCalldata(data) {
|
|
8637
|
+
if (!data || data.length < 10) return null;
|
|
8638
|
+
const selector = data.slice(0, 10).toLowerCase();
|
|
8639
|
+
if (selector === TRANSFER_SELECTOR && data.length >= 138) {
|
|
8640
|
+
const to = "0x" + data.slice(34, 74);
|
|
8641
|
+
const amount = BigInt("0x" + data.slice(74, 138));
|
|
8642
|
+
return { to, amount };
|
|
8643
|
+
}
|
|
8644
|
+
if (selector === TRANSFER_FROM_SELECTOR && data.length >= 202) {
|
|
8645
|
+
const from = "0x" + data.slice(34, 74);
|
|
8646
|
+
const to = "0x" + data.slice(98, 138);
|
|
8647
|
+
const amount = BigInt("0x" + data.slice(138, 202));
|
|
8648
|
+
return { from, to, amount };
|
|
8649
|
+
}
|
|
8650
|
+
return null;
|
|
8651
|
+
}
|
|
8652
|
+
function decodeWriteContractArgs(functionName, args) {
|
|
8653
|
+
if (!args || !Array.isArray(args)) return null;
|
|
8654
|
+
if (functionName === "transfer" && args.length >= 2) {
|
|
8655
|
+
return { to: String(args[0]), amount: BigInt(args[1]) };
|
|
8656
|
+
}
|
|
8657
|
+
if (functionName === "transferFrom" && args.length >= 3) {
|
|
8658
|
+
return { from: String(args[0]), to: String(args[1]), amount: BigInt(args[2]) };
|
|
8659
|
+
}
|
|
8660
|
+
return null;
|
|
8661
|
+
}
|
|
8662
|
+
function formatTokenAmount2(amount, decimals) {
|
|
8663
|
+
const divisor = BigInt(10 ** decimals);
|
|
8664
|
+
const whole = amount / divisor;
|
|
8665
|
+
const fraction = amount % divisor;
|
|
8666
|
+
if (fraction === 0n) return whole.toString();
|
|
8667
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
8668
|
+
return `${whole}.${fractionStr}`;
|
|
8669
|
+
}
|
|
8670
|
+
|
|
7503
8671
|
exports.AgentIdentityRegistry = AgentIdentityRegistry;
|
|
7504
8672
|
exports.AnomalyDetector = AnomalyDetector;
|
|
7505
8673
|
exports.BehavioralFingerprinter = BehavioralFingerprinter;
|
|
8674
|
+
exports.CHAIN_ID_MAP = CHAIN_ID_MAP;
|
|
7506
8675
|
exports.CURRENCY_REQUIRED_LISTS = CURRENCY_REQUIRED_LISTS;
|
|
7507
8676
|
exports.ChainalysisFreeAPIProvider = ChainalysisFreeAPIProvider;
|
|
7508
8677
|
exports.ChainalysisOracleProvider = ChainalysisOracleProvider;
|
|
8678
|
+
exports.CircleWalletManager = CircleWalletManager;
|
|
8679
|
+
exports.CoinbaseWalletManager = CoinbaseWalletManager;
|
|
7509
8680
|
exports.ConsoleExporter = ConsoleExporter;
|
|
7510
8681
|
exports.CrossSessionLinker = CrossSessionLinker;
|
|
7511
8682
|
exports.DigestChain = DigestChain;
|
|
@@ -7517,6 +8688,7 @@ exports.Kontext = Kontext;
|
|
|
7517
8688
|
exports.KontextError = KontextError;
|
|
7518
8689
|
exports.KontextErrorCode = KontextErrorCode;
|
|
7519
8690
|
exports.MemoryStorage = MemoryStorage;
|
|
8691
|
+
exports.MetaMaskWalletManager = MetaMaskWalletManager;
|
|
7520
8692
|
exports.NoopExporter = NoopExporter;
|
|
7521
8693
|
exports.OFACAddressProvider = OFACAddressProvider;
|
|
7522
8694
|
exports.OFACEntityProvider = OFACEntityProvider;
|
|
@@ -7526,12 +8698,15 @@ exports.PLAN_LIMITS = PLAN_LIMITS;
|
|
|
7526
8698
|
exports.PaymentCompliance = PaymentCompliance;
|
|
7527
8699
|
exports.PlanManager = PlanManager;
|
|
7528
8700
|
exports.ProvenanceManager = ProvenanceManager;
|
|
8701
|
+
exports.STABLECOIN_CONTRACTS = STABLECOIN_CONTRACTS;
|
|
7529
8702
|
exports.ScreeningAggregator = ScreeningAggregator;
|
|
7530
8703
|
exports.TOKEN_REQUIRED_LISTS = TOKEN_REQUIRED_LISTS;
|
|
7531
8704
|
exports.TrustScorer = TrustScorer;
|
|
7532
8705
|
exports.UKOFSIProvider = UKOFSIProvider;
|
|
7533
8706
|
exports.UsdcCompliance = UsdcCompliance;
|
|
8707
|
+
exports.ViemComplianceError = ViemComplianceError;
|
|
7534
8708
|
exports.WalletClusterer = WalletClusterer;
|
|
8709
|
+
exports.WalletMonitor = WalletMonitor;
|
|
7535
8710
|
exports.anchorDigest = anchorDigest;
|
|
7536
8711
|
exports.encodeERC8021Suffix = encodeERC8021Suffix;
|
|
7537
8712
|
exports.exchangeAttestation = exchangeAttestation;
|
|
@@ -7542,10 +8717,12 @@ exports.getRequiredLists = getRequiredLists;
|
|
|
7542
8717
|
exports.isBlockchainAddress = isBlockchainAddress;
|
|
7543
8718
|
exports.isCryptoTransaction = isCryptoTransaction;
|
|
7544
8719
|
exports.isFeatureAvailable = isFeatureAvailable;
|
|
8720
|
+
exports.loadConfigFile = loadConfigFile;
|
|
7545
8721
|
exports.parseERC8021Suffix = parseERC8021Suffix;
|
|
7546
8722
|
exports.providerSupportsQuery = providerSupportsQuery;
|
|
7547
8723
|
exports.requirePlan = requirePlan;
|
|
7548
8724
|
exports.verifyAnchor = verifyAnchor;
|
|
7549
8725
|
exports.verifyExportedChain = verifyExportedChain;
|
|
8726
|
+
exports.withKontextCompliance = withKontextCompliance;
|
|
7550
8727
|
//# sourceMappingURL=index.js.map
|
|
7551
8728
|
//# sourceMappingURL=index.js.map
|