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.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fs5 from 'fs';
|
|
3
|
+
import * as path5 from 'path';
|
|
4
4
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -1951,13 +1951,13 @@ var ActionLogger = class {
|
|
|
1951
1951
|
}
|
|
1952
1952
|
flushToFile(actions) {
|
|
1953
1953
|
const outputDir = this.config.localOutputDir ?? ".kontext";
|
|
1954
|
-
const logDir =
|
|
1954
|
+
const logDir = path5.join(outputDir, "logs");
|
|
1955
1955
|
try {
|
|
1956
|
-
|
|
1956
|
+
fs5.mkdirSync(logDir, { recursive: true });
|
|
1957
1957
|
const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
|
|
1958
|
-
const filePath =
|
|
1958
|
+
const filePath = path5.join(logDir, filename);
|
|
1959
1959
|
const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
|
|
1960
|
-
|
|
1960
|
+
fs5.appendFileSync(filePath, lines, "utf-8");
|
|
1961
1961
|
} catch (error) {
|
|
1962
1962
|
this.emitLog("warn", "Failed to write log file", { error });
|
|
1963
1963
|
}
|
|
@@ -2567,9 +2567,9 @@ var SANCTIONED_SET = new Set(
|
|
|
2567
2567
|
function loadCachedSDN() {
|
|
2568
2568
|
try {
|
|
2569
2569
|
const dataDir = process.env["KONTEXT_DATA_DIR"] || ".kontext";
|
|
2570
|
-
const cachePath =
|
|
2571
|
-
if (
|
|
2572
|
-
const cache = JSON.parse(
|
|
2570
|
+
const cachePath = path5.join(dataDir, "ofac-sdn-cache.json");
|
|
2571
|
+
if (fs5.existsSync(cachePath)) {
|
|
2572
|
+
const cache = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
|
|
2573
2573
|
if (Array.isArray(cache.addresses)) {
|
|
2574
2574
|
for (const addr of cache.addresses) {
|
|
2575
2575
|
SANCTIONED_SET.add(String(addr).toLowerCase());
|
|
@@ -3625,7 +3625,9 @@ var FEATURE_MIN_PLAN = {
|
|
|
3625
3625
|
"unified-screening": "pro",
|
|
3626
3626
|
"blocklist-manager": "pro",
|
|
3627
3627
|
"kya-identity": "pro",
|
|
3628
|
-
"kya-behavioral": "enterprise"
|
|
3628
|
+
"kya-behavioral": "enterprise",
|
|
3629
|
+
"coinbase-wallets": "enterprise",
|
|
3630
|
+
"metamask-wallets": "enterprise"
|
|
3629
3631
|
};
|
|
3630
3632
|
var PLAN_RANK = { free: 0, pro: 1, enterprise: 2 };
|
|
3631
3633
|
var FEATURE_LABELS = {
|
|
@@ -3644,7 +3646,9 @@ var FEATURE_LABELS = {
|
|
|
3644
3646
|
"unified-screening": "Unified screening (OFAC, Chainalysis, OpenSanctions)",
|
|
3645
3647
|
"blocklist-manager": "Custom blocklist/allowlist manager",
|
|
3646
3648
|
"kya-identity": "KYA identity resolution (declared identity, wallet clustering)",
|
|
3647
|
-
"kya-behavioral": "KYA behavioral fingerprinting (cross-session linking, confidence scoring)"
|
|
3649
|
+
"kya-behavioral": "KYA behavioral fingerprinting (cross-session linking, confidence scoring)",
|
|
3650
|
+
"coinbase-wallets": "Coinbase Developer Platform Wallets",
|
|
3651
|
+
"metamask-wallets": "MetaMask Embedded Wallets"
|
|
3648
3652
|
};
|
|
3649
3653
|
function isFeatureAvailable(feature, currentPlan) {
|
|
3650
3654
|
const requiredPlan = FEATURE_MIN_PLAN[feature];
|
|
@@ -3690,7 +3694,7 @@ var JsonFileExporter = class {
|
|
|
3690
3694
|
buffer = [];
|
|
3691
3695
|
bufferSize;
|
|
3692
3696
|
constructor(options) {
|
|
3693
|
-
this.outputDir =
|
|
3697
|
+
this.outputDir = path5.resolve(options?.outputDir ?? ".kontext/exports");
|
|
3694
3698
|
this.bufferSize = options?.bufferSize ?? 1;
|
|
3695
3699
|
}
|
|
3696
3700
|
async export(events) {
|
|
@@ -3705,11 +3709,11 @@ var JsonFileExporter = class {
|
|
|
3705
3709
|
const toWrite = [...this.buffer];
|
|
3706
3710
|
this.buffer = [];
|
|
3707
3711
|
try {
|
|
3708
|
-
|
|
3712
|
+
fs5.mkdirSync(this.outputDir, { recursive: true });
|
|
3709
3713
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3710
|
-
const filePath =
|
|
3714
|
+
const filePath = path5.join(this.outputDir, `events-${date}.jsonl`);
|
|
3711
3715
|
const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
3712
|
-
|
|
3716
|
+
fs5.appendFileSync(filePath, lines, "utf-8");
|
|
3713
3717
|
} catch (error) {
|
|
3714
3718
|
console.warn("[Kontext JsonFileExporter] Failed to write events:", error);
|
|
3715
3719
|
}
|
|
@@ -3879,6 +3883,218 @@ function extractDocumentId(resourceName) {
|
|
|
3879
3883
|
const parts = resourceName.split("/");
|
|
3880
3884
|
return parts[parts.length - 1] ?? resourceName;
|
|
3881
3885
|
}
|
|
3886
|
+
var CONFIG_FILENAME = "kontext.config.json";
|
|
3887
|
+
function loadConfigFile(startDir) {
|
|
3888
|
+
const dir = startDir ?? process.cwd();
|
|
3889
|
+
const filePath = findConfigFile(dir);
|
|
3890
|
+
if (!filePath) return null;
|
|
3891
|
+
try {
|
|
3892
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
3893
|
+
const parsed = JSON.parse(raw);
|
|
3894
|
+
if (!parsed.projectId || typeof parsed.projectId !== "string") {
|
|
3895
|
+
return null;
|
|
3896
|
+
}
|
|
3897
|
+
return parsed;
|
|
3898
|
+
} catch {
|
|
3899
|
+
return null;
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
function findConfigFile(dir) {
|
|
3903
|
+
let current = path5.resolve(dir);
|
|
3904
|
+
const root = path5.parse(current).root;
|
|
3905
|
+
while (true) {
|
|
3906
|
+
const candidate = path5.join(current, CONFIG_FILENAME);
|
|
3907
|
+
if (fs5.existsSync(candidate)) {
|
|
3908
|
+
return candidate;
|
|
3909
|
+
}
|
|
3910
|
+
const parent = path5.dirname(current);
|
|
3911
|
+
if (parent === current || current === root) {
|
|
3912
|
+
return null;
|
|
3913
|
+
}
|
|
3914
|
+
current = parent;
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
// src/integrations/data/stablecoin-contracts.ts
|
|
3919
|
+
var STABLECOIN_CONTRACTS = {
|
|
3920
|
+
// USDC (6 decimals)
|
|
3921
|
+
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { token: "USDC", chain: "ethereum", decimals: 6 },
|
|
3922
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { token: "USDC", chain: "base", decimals: 6 },
|
|
3923
|
+
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359": { token: "USDC", chain: "polygon", decimals: 6 },
|
|
3924
|
+
"0xaf88d065e77c8cc2239327c5edb3a432268e5831": { token: "USDC", chain: "arbitrum", decimals: 6 },
|
|
3925
|
+
"0x0b2c639c533813f4aa9d7837caf62653d097ff85": { token: "USDC", chain: "optimism", decimals: 6 },
|
|
3926
|
+
"0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": { token: "USDC", chain: "avalanche", decimals: 6 },
|
|
3927
|
+
// USDT (6 decimals)
|
|
3928
|
+
"0xdac17f958d2ee523a2206206994597c13d831ec7": { token: "USDT", chain: "ethereum", decimals: 6 },
|
|
3929
|
+
"0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9": { token: "USDT", chain: "arbitrum", decimals: 6 },
|
|
3930
|
+
"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58": { token: "USDT", chain: "optimism", decimals: 6 },
|
|
3931
|
+
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f": { token: "USDT", chain: "polygon", decimals: 6 },
|
|
3932
|
+
// DAI (18 decimals)
|
|
3933
|
+
"0x6b175474e89094c44da98b954eedeac495271d0f": { token: "DAI", chain: "ethereum", decimals: 18 },
|
|
3934
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": { token: "DAI", chain: "base", decimals: 18 },
|
|
3935
|
+
// EURC (6 decimals)
|
|
3936
|
+
"0x1abaea1f7c830bd89acc67ec4af516284b1bc33c": { token: "EURC", chain: "ethereum", decimals: 6 },
|
|
3937
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": { token: "EURC", chain: "base", decimals: 6 }
|
|
3938
|
+
};
|
|
3939
|
+
new Set(Object.keys(STABLECOIN_CONTRACTS));
|
|
3940
|
+
var CHAIN_ID_MAP = {
|
|
3941
|
+
1: "ethereum",
|
|
3942
|
+
8453: "base",
|
|
3943
|
+
137: "polygon",
|
|
3944
|
+
42161: "arbitrum",
|
|
3945
|
+
10: "optimism",
|
|
3946
|
+
43114: "avalanche"
|
|
3947
|
+
};
|
|
3948
|
+
var TRANSFER_SELECTOR = "0xa9059cbb";
|
|
3949
|
+
var TRANSFER_FROM_SELECTOR = "0x23b872dd";
|
|
3950
|
+
var TRANSFER_EVENT_ABI = {
|
|
3951
|
+
type: "event",
|
|
3952
|
+
name: "Transfer",
|
|
3953
|
+
inputs: [
|
|
3954
|
+
{ type: "address", name: "from", indexed: true },
|
|
3955
|
+
{ type: "address", name: "to", indexed: true },
|
|
3956
|
+
{ type: "uint256", name: "value", indexed: false }
|
|
3957
|
+
]
|
|
3958
|
+
};
|
|
3959
|
+
|
|
3960
|
+
// src/integrations/wallet-monitor.ts
|
|
3961
|
+
var WalletMonitor = class {
|
|
3962
|
+
kontext;
|
|
3963
|
+
config;
|
|
3964
|
+
agentId;
|
|
3965
|
+
tokens;
|
|
3966
|
+
unwatchers = [];
|
|
3967
|
+
running = false;
|
|
3968
|
+
/** Shared dedup set — tracks recently verified txHashes (populated by both layers) */
|
|
3969
|
+
verifiedTxHashes = /* @__PURE__ */ new Set();
|
|
3970
|
+
cleanupTimer = null;
|
|
3971
|
+
txTimestamps = /* @__PURE__ */ new Map();
|
|
3972
|
+
constructor(kontext, config, options) {
|
|
3973
|
+
this.kontext = kontext;
|
|
3974
|
+
this.config = config;
|
|
3975
|
+
this.agentId = options?.agentId ?? "wallet-monitor";
|
|
3976
|
+
this.tokens = options?.tokens ? new Set(options.tokens) : null;
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Mark a txHash as already verified (called by the viem interceptor layer).
|
|
3980
|
+
* The monitor will skip this tx if it later sees it on-chain.
|
|
3981
|
+
*/
|
|
3982
|
+
markVerified(txHash) {
|
|
3983
|
+
const lower = txHash.toLowerCase();
|
|
3984
|
+
this.verifiedTxHashes.add(lower);
|
|
3985
|
+
this.txTimestamps.set(lower, Date.now());
|
|
3986
|
+
}
|
|
3987
|
+
/**
|
|
3988
|
+
* Start watching all configured chains for stablecoin transfers.
|
|
3989
|
+
* Dynamically imports viem — requires viem as a peer dependency.
|
|
3990
|
+
*/
|
|
3991
|
+
async start() {
|
|
3992
|
+
if (this.running) return;
|
|
3993
|
+
let viem;
|
|
3994
|
+
try {
|
|
3995
|
+
viem = await import('viem');
|
|
3996
|
+
} catch {
|
|
3997
|
+
throw new Error(
|
|
3998
|
+
"Wallet monitoring requires viem. Install it: npm install viem"
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
const { createPublicClient, http } = viem;
|
|
4002
|
+
const wallets = this.config.wallets.map((w) => w.toLowerCase());
|
|
4003
|
+
const pollingInterval = this.config.pollingIntervalMs ?? 12e3;
|
|
4004
|
+
const contractsByChain = /* @__PURE__ */ new Map();
|
|
4005
|
+
for (const [address, info] of Object.entries(STABLECOIN_CONTRACTS)) {
|
|
4006
|
+
if (this.tokens && !this.tokens.has(info.token)) continue;
|
|
4007
|
+
const existing = contractsByChain.get(info.chain) ?? [];
|
|
4008
|
+
existing.push({ address, info });
|
|
4009
|
+
contractsByChain.set(info.chain, existing);
|
|
4010
|
+
}
|
|
4011
|
+
for (const [chain, contracts] of contractsByChain) {
|
|
4012
|
+
const rpcUrl = this.config.rpcEndpoints[chain];
|
|
4013
|
+
if (!rpcUrl) continue;
|
|
4014
|
+
const client = createPublicClient({
|
|
4015
|
+
transport: http(rpcUrl)
|
|
4016
|
+
});
|
|
4017
|
+
for (const { address, info } of contracts) {
|
|
4018
|
+
const unwatch = client.watchEvent({
|
|
4019
|
+
address,
|
|
4020
|
+
event: TRANSFER_EVENT_ABI,
|
|
4021
|
+
args: { from: wallets.length === 1 ? wallets[0] : wallets },
|
|
4022
|
+
poll: true,
|
|
4023
|
+
pollingInterval,
|
|
4024
|
+
onLogs: (logs) => {
|
|
4025
|
+
for (const log of logs) {
|
|
4026
|
+
this.handleTransferLog(log, info);
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
});
|
|
4030
|
+
this.unwatchers.push(unwatch);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
this.cleanupTimer = setInterval(() => {
|
|
4034
|
+
const cutoff = Date.now() - 5 * 60 * 1e3;
|
|
4035
|
+
for (const [hash, ts] of this.txTimestamps) {
|
|
4036
|
+
if (ts < cutoff) {
|
|
4037
|
+
this.verifiedTxHashes.delete(hash);
|
|
4038
|
+
this.txTimestamps.delete(hash);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
}, 6e4);
|
|
4042
|
+
this.running = true;
|
|
4043
|
+
}
|
|
4044
|
+
/** Stop all watchers and cleanup */
|
|
4045
|
+
stop() {
|
|
4046
|
+
for (const unwatch of this.unwatchers) {
|
|
4047
|
+
try {
|
|
4048
|
+
unwatch();
|
|
4049
|
+
} catch {
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
this.unwatchers.length = 0;
|
|
4053
|
+
if (this.cleanupTimer) {
|
|
4054
|
+
clearInterval(this.cleanupTimer);
|
|
4055
|
+
this.cleanupTimer = null;
|
|
4056
|
+
}
|
|
4057
|
+
this.running = false;
|
|
4058
|
+
}
|
|
4059
|
+
isRunning() {
|
|
4060
|
+
return this.running;
|
|
4061
|
+
}
|
|
4062
|
+
handleTransferLog(log, contractInfo) {
|
|
4063
|
+
const txHash = log.transactionHash;
|
|
4064
|
+
if (!txHash) return;
|
|
4065
|
+
const lowerHash = txHash.toLowerCase();
|
|
4066
|
+
if (this.verifiedTxHashes.has(lowerHash)) return;
|
|
4067
|
+
this.markVerified(txHash);
|
|
4068
|
+
const from = log.args?.from?.toLowerCase() ?? "";
|
|
4069
|
+
const to = log.args?.to?.toLowerCase() ?? "";
|
|
4070
|
+
const value = log.args?.value;
|
|
4071
|
+
if (!from || !to || value === void 0) return;
|
|
4072
|
+
const amount = formatTokenAmount(value, contractInfo.decimals);
|
|
4073
|
+
const verifyInput = {
|
|
4074
|
+
txHash,
|
|
4075
|
+
chain: contractInfo.chain,
|
|
4076
|
+
amount,
|
|
4077
|
+
token: contractInfo.token,
|
|
4078
|
+
from,
|
|
4079
|
+
to,
|
|
4080
|
+
agentId: this.agentId,
|
|
4081
|
+
metadata: {
|
|
4082
|
+
source: "wallet-monitor",
|
|
4083
|
+
contractAddress: log.address
|
|
4084
|
+
}
|
|
4085
|
+
};
|
|
4086
|
+
this.kontext.verify(verifyInput).catch(() => {
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
4089
|
+
};
|
|
4090
|
+
function formatTokenAmount(amount, decimals) {
|
|
4091
|
+
const divisor = BigInt(10 ** decimals);
|
|
4092
|
+
const whole = amount / divisor;
|
|
4093
|
+
const fraction = amount % divisor;
|
|
4094
|
+
if (fraction === 0n) return whole.toString();
|
|
4095
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
4096
|
+
return `${whole}.${fractionStr}`;
|
|
4097
|
+
}
|
|
3882
4098
|
var ProvenanceManager = class {
|
|
3883
4099
|
store;
|
|
3884
4100
|
logger;
|
|
@@ -5398,6 +5614,602 @@ var KYAConfidenceScorer = class {
|
|
|
5398
5614
|
}
|
|
5399
5615
|
};
|
|
5400
5616
|
|
|
5617
|
+
// src/integrations/circle-wallets.ts
|
|
5618
|
+
var DEFAULT_BASE_URL = "https://api.circle.com";
|
|
5619
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
5620
|
+
var CircleWalletManager = class {
|
|
5621
|
+
apiKey;
|
|
5622
|
+
entitySecret;
|
|
5623
|
+
baseUrl;
|
|
5624
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5625
|
+
kontext = null;
|
|
5626
|
+
constructor(config) {
|
|
5627
|
+
if (!config.apiKey) {
|
|
5628
|
+
throw new KontextError(
|
|
5629
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5630
|
+
"Circle API key is required"
|
|
5631
|
+
);
|
|
5632
|
+
}
|
|
5633
|
+
if (!config.entitySecret) {
|
|
5634
|
+
throw new KontextError(
|
|
5635
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5636
|
+
"Circle entity secret is required"
|
|
5637
|
+
);
|
|
5638
|
+
}
|
|
5639
|
+
this.apiKey = config.apiKey;
|
|
5640
|
+
this.entitySecret = config.entitySecret;
|
|
5641
|
+
this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
5642
|
+
}
|
|
5643
|
+
/** Link to Kontext instance for auto-compliance logging */
|
|
5644
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5645
|
+
setKontext(kontext) {
|
|
5646
|
+
this.kontext = kontext;
|
|
5647
|
+
}
|
|
5648
|
+
/** Validate credentials by calling Circle's configuration endpoint */
|
|
5649
|
+
async validateCredentials() {
|
|
5650
|
+
try {
|
|
5651
|
+
const res = await fetch(`${this.baseUrl}/v1/w3s/config/entity`, {
|
|
5652
|
+
method: "GET",
|
|
5653
|
+
headers: this.headers(),
|
|
5654
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
5655
|
+
});
|
|
5656
|
+
return res.ok;
|
|
5657
|
+
} catch {
|
|
5658
|
+
return false;
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
/** Create a wallet set (container for wallets) */
|
|
5662
|
+
async createWalletSet(input) {
|
|
5663
|
+
const body = {
|
|
5664
|
+
name: input.name,
|
|
5665
|
+
idempotencyKey: input.idempotencyKey ?? generateId(),
|
|
5666
|
+
entitySecretCiphertext: this.entitySecret
|
|
5667
|
+
};
|
|
5668
|
+
const res = await this.request(
|
|
5669
|
+
"POST",
|
|
5670
|
+
"/v1/w3s/developer/walletSets",
|
|
5671
|
+
body
|
|
5672
|
+
);
|
|
5673
|
+
return res.data.walletSet;
|
|
5674
|
+
}
|
|
5675
|
+
/** Create wallet(s) in a wallet set */
|
|
5676
|
+
async createWallet(input) {
|
|
5677
|
+
const body = {
|
|
5678
|
+
walletSetId: input.walletSetId,
|
|
5679
|
+
blockchains: input.blockchains.map((c) => this.mapChain(c)),
|
|
5680
|
+
count: input.count ?? 1,
|
|
5681
|
+
accountType: input.accountType ?? "EOA",
|
|
5682
|
+
idempotencyKey: input.idempotencyKey ?? generateId(),
|
|
5683
|
+
entitySecretCiphertext: this.entitySecret
|
|
5684
|
+
};
|
|
5685
|
+
const res = await this.request(
|
|
5686
|
+
"POST",
|
|
5687
|
+
"/v1/w3s/developer/wallets",
|
|
5688
|
+
body
|
|
5689
|
+
);
|
|
5690
|
+
return res.data.wallets;
|
|
5691
|
+
}
|
|
5692
|
+
/** List wallets, optionally filtered by wallet set */
|
|
5693
|
+
async listWallets(walletSetId) {
|
|
5694
|
+
const qs = walletSetId ? `?walletSetId=${walletSetId}` : "";
|
|
5695
|
+
const res = await this.request(
|
|
5696
|
+
"GET",
|
|
5697
|
+
`/v1/w3s/wallets${qs}`
|
|
5698
|
+
);
|
|
5699
|
+
return res.data.wallets;
|
|
5700
|
+
}
|
|
5701
|
+
/** Get wallet token balances */
|
|
5702
|
+
async getBalance(walletId) {
|
|
5703
|
+
const res = await this.request(
|
|
5704
|
+
"GET",
|
|
5705
|
+
`/v1/w3s/wallets/${walletId}/balances`
|
|
5706
|
+
);
|
|
5707
|
+
return res.data.tokenBalances.map((b) => ({
|
|
5708
|
+
token: b.token.symbol,
|
|
5709
|
+
amount: b.amount
|
|
5710
|
+
}));
|
|
5711
|
+
}
|
|
5712
|
+
/** Transfer with auto-compliance: runs verify() before/after transfer */
|
|
5713
|
+
async transferWithCompliance(input) {
|
|
5714
|
+
let complianceResult;
|
|
5715
|
+
if (this.kontext) {
|
|
5716
|
+
complianceResult = await this.kontext.verify({
|
|
5717
|
+
txHash: `circle-pending-${generateId()}`,
|
|
5718
|
+
chain: input.blockchain,
|
|
5719
|
+
amount: input.amount,
|
|
5720
|
+
token: "USDC",
|
|
5721
|
+
from: input.walletId,
|
|
5722
|
+
to: input.destinationAddress,
|
|
5723
|
+
agentId: input.agentId ?? "circle-wallet-manager"
|
|
5724
|
+
});
|
|
5725
|
+
if (!complianceResult.compliant) {
|
|
5726
|
+
return {
|
|
5727
|
+
id: "",
|
|
5728
|
+
state: "BLOCKED",
|
|
5729
|
+
complianceResult
|
|
5730
|
+
};
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
const body = {
|
|
5734
|
+
walletId: input.walletId,
|
|
5735
|
+
tokenAddress: input.tokenAddress,
|
|
5736
|
+
destinationAddress: input.destinationAddress,
|
|
5737
|
+
amounts: [input.amount],
|
|
5738
|
+
blockchain: this.mapChain(input.blockchain),
|
|
5739
|
+
idempotencyKey: input.idempotencyKey ?? generateId(),
|
|
5740
|
+
entitySecretCiphertext: this.entitySecret,
|
|
5741
|
+
feeLevel: "MEDIUM"
|
|
5742
|
+
};
|
|
5743
|
+
const res = await this.request(
|
|
5744
|
+
"POST",
|
|
5745
|
+
"/v1/w3s/developer/transactions/transfer",
|
|
5746
|
+
body
|
|
5747
|
+
);
|
|
5748
|
+
if (this.kontext) {
|
|
5749
|
+
await this.kontext.logReasoning({
|
|
5750
|
+
agentId: input.agentId ?? "circle-wallet-manager",
|
|
5751
|
+
action: "circle-transfer",
|
|
5752
|
+
reasoning: `Circle transfer ${res.data.id}: ${input.amount} to ${input.destinationAddress} on ${input.blockchain}`,
|
|
5753
|
+
confidence: 1,
|
|
5754
|
+
context: { transferId: res.data.id, state: res.data.state }
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
return {
|
|
5758
|
+
id: res.data.id,
|
|
5759
|
+
state: res.data.state,
|
|
5760
|
+
txHash: res.data.txHash,
|
|
5761
|
+
complianceResult
|
|
5762
|
+
};
|
|
5763
|
+
}
|
|
5764
|
+
// --------------------------------------------------------------------------
|
|
5765
|
+
// Private helpers
|
|
5766
|
+
// --------------------------------------------------------------------------
|
|
5767
|
+
headers() {
|
|
5768
|
+
return {
|
|
5769
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
5770
|
+
"Content-Type": "application/json",
|
|
5771
|
+
"X-Entity-Secret": this.entitySecret
|
|
5772
|
+
};
|
|
5773
|
+
}
|
|
5774
|
+
async request(method, path6, body) {
|
|
5775
|
+
const res = await fetch(`${this.baseUrl}${path6}`, {
|
|
5776
|
+
method,
|
|
5777
|
+
headers: this.headers(),
|
|
5778
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
5779
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
5780
|
+
});
|
|
5781
|
+
if (!res.ok) {
|
|
5782
|
+
const text = await res.text().catch(() => "");
|
|
5783
|
+
throw new KontextError(
|
|
5784
|
+
"API_ERROR" /* API_ERROR */,
|
|
5785
|
+
`Circle API error ${res.status}: ${text}`
|
|
5786
|
+
);
|
|
5787
|
+
}
|
|
5788
|
+
return res.json();
|
|
5789
|
+
}
|
|
5790
|
+
mapChain(chain) {
|
|
5791
|
+
const map = {
|
|
5792
|
+
ethereum: "ETH",
|
|
5793
|
+
base: "BASE",
|
|
5794
|
+
polygon: "MATIC",
|
|
5795
|
+
arbitrum: "ARB",
|
|
5796
|
+
optimism: "OP",
|
|
5797
|
+
avalanche: "AVAX",
|
|
5798
|
+
solana: "SOL"
|
|
5799
|
+
};
|
|
5800
|
+
return map[chain] ?? chain.toUpperCase();
|
|
5801
|
+
}
|
|
5802
|
+
};
|
|
5803
|
+
|
|
5804
|
+
// src/integrations/coinbase-wallets.ts
|
|
5805
|
+
var CDP_BASE_URL = "https://api.cdp.coinbase.com";
|
|
5806
|
+
var REQUEST_TIMEOUT_MS2 = 1e4;
|
|
5807
|
+
var CoinbaseWalletManager = class {
|
|
5808
|
+
apiKeyId;
|
|
5809
|
+
apiKeySecret;
|
|
5810
|
+
walletSecret;
|
|
5811
|
+
baseUrl;
|
|
5812
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5813
|
+
kontext = null;
|
|
5814
|
+
constructor(config) {
|
|
5815
|
+
if (!config.apiKeyId) {
|
|
5816
|
+
throw new KontextError(
|
|
5817
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5818
|
+
"Coinbase CDP API Key ID is required"
|
|
5819
|
+
);
|
|
5820
|
+
}
|
|
5821
|
+
if (!config.apiKeySecret) {
|
|
5822
|
+
throw new KontextError(
|
|
5823
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5824
|
+
"Coinbase CDP API Key Secret is required"
|
|
5825
|
+
);
|
|
5826
|
+
}
|
|
5827
|
+
if (!config.walletSecret) {
|
|
5828
|
+
throw new KontextError(
|
|
5829
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
5830
|
+
"Coinbase CDP Wallet Secret is required"
|
|
5831
|
+
);
|
|
5832
|
+
}
|
|
5833
|
+
this.apiKeyId = config.apiKeyId;
|
|
5834
|
+
this.apiKeySecret = config.apiKeySecret;
|
|
5835
|
+
this.walletSecret = config.walletSecret;
|
|
5836
|
+
this.baseUrl = CDP_BASE_URL;
|
|
5837
|
+
}
|
|
5838
|
+
/** Link to Kontext instance for auto-compliance logging */
|
|
5839
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5840
|
+
setKontext(kontext) {
|
|
5841
|
+
this.kontext = kontext;
|
|
5842
|
+
}
|
|
5843
|
+
/** Validate credentials by listing accounts */
|
|
5844
|
+
async validateCredentials() {
|
|
5845
|
+
try {
|
|
5846
|
+
const res = await fetch(`${this.baseUrl}/v1/evm/accounts`, {
|
|
5847
|
+
method: "GET",
|
|
5848
|
+
headers: await this.headers(),
|
|
5849
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
5850
|
+
});
|
|
5851
|
+
return res.ok;
|
|
5852
|
+
} catch {
|
|
5853
|
+
return false;
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
/** Create an EVM account */
|
|
5857
|
+
async createAccount(opts) {
|
|
5858
|
+
const body = {};
|
|
5859
|
+
if (opts?.name) body["name"] = opts.name;
|
|
5860
|
+
if (opts?.network) body["network"] = opts.network;
|
|
5861
|
+
const res = await this.request(
|
|
5862
|
+
"POST",
|
|
5863
|
+
"/v1/evm/accounts",
|
|
5864
|
+
body,
|
|
5865
|
+
true
|
|
5866
|
+
// requires wallet auth
|
|
5867
|
+
);
|
|
5868
|
+
return {
|
|
5869
|
+
address: res.address,
|
|
5870
|
+
name: res.name,
|
|
5871
|
+
network: res.network
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
5874
|
+
/** List accounts */
|
|
5875
|
+
async listAccounts() {
|
|
5876
|
+
const res = await this.request(
|
|
5877
|
+
"GET",
|
|
5878
|
+
"/v1/evm/accounts"
|
|
5879
|
+
);
|
|
5880
|
+
return res.accounts.map((a) => ({
|
|
5881
|
+
address: a.address,
|
|
5882
|
+
name: a.name,
|
|
5883
|
+
network: a.network
|
|
5884
|
+
}));
|
|
5885
|
+
}
|
|
5886
|
+
/** Get token balances for an address */
|
|
5887
|
+
async getBalances(address, network) {
|
|
5888
|
+
const res = await this.request(
|
|
5889
|
+
"GET",
|
|
5890
|
+
`/v1/evm/accounts/${address}/balances?network=${network}`
|
|
5891
|
+
);
|
|
5892
|
+
return res.balances.map((b) => ({
|
|
5893
|
+
token: b.asset,
|
|
5894
|
+
amount: b.amount
|
|
5895
|
+
}));
|
|
5896
|
+
}
|
|
5897
|
+
/** Transfer with auto-compliance: runs verify() before/after transfer */
|
|
5898
|
+
async transferWithCompliance(input) {
|
|
5899
|
+
let complianceResult;
|
|
5900
|
+
if (this.kontext) {
|
|
5901
|
+
const chain = this.mapNetwork(input.network);
|
|
5902
|
+
complianceResult = await this.kontext.verify({
|
|
5903
|
+
txHash: `cdp-pending-${generateId()}`,
|
|
5904
|
+
chain,
|
|
5905
|
+
amount: input.amount,
|
|
5906
|
+
token: input.token,
|
|
5907
|
+
from: input.fromAddress,
|
|
5908
|
+
to: input.toAddress,
|
|
5909
|
+
agentId: input.agentId ?? "coinbase-wallet-manager"
|
|
5910
|
+
});
|
|
5911
|
+
if (!complianceResult.compliant) {
|
|
5912
|
+
return {
|
|
5913
|
+
transactionHash: "",
|
|
5914
|
+
status: "BLOCKED",
|
|
5915
|
+
complianceResult
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
const body = {
|
|
5920
|
+
to: input.toAddress,
|
|
5921
|
+
amount: input.amount,
|
|
5922
|
+
asset: input.token,
|
|
5923
|
+
network: input.network
|
|
5924
|
+
};
|
|
5925
|
+
const res = await this.request(
|
|
5926
|
+
"POST",
|
|
5927
|
+
`/v1/evm/accounts/${input.fromAddress}/transfer`,
|
|
5928
|
+
body,
|
|
5929
|
+
true
|
|
5930
|
+
// requires wallet auth
|
|
5931
|
+
);
|
|
5932
|
+
if (this.kontext) {
|
|
5933
|
+
await this.kontext.logReasoning({
|
|
5934
|
+
agentId: input.agentId ?? "coinbase-wallet-manager",
|
|
5935
|
+
action: "coinbase-transfer",
|
|
5936
|
+
reasoning: `CDP transfer: ${input.amount} ${input.token} from ${input.fromAddress} to ${input.toAddress} on ${input.network}`,
|
|
5937
|
+
confidence: 1,
|
|
5938
|
+
context: { transactionHash: res.transactionHash, status: res.status }
|
|
5939
|
+
});
|
|
5940
|
+
}
|
|
5941
|
+
return {
|
|
5942
|
+
transactionHash: res.transactionHash,
|
|
5943
|
+
status: res.status,
|
|
5944
|
+
complianceResult
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
// --------------------------------------------------------------------------
|
|
5948
|
+
// Private helpers
|
|
5949
|
+
// --------------------------------------------------------------------------
|
|
5950
|
+
/**
|
|
5951
|
+
* Build auth headers. CDP uses JWT Bearer tokens:
|
|
5952
|
+
* - API auth: signed with apiKeySecret, apiKeyId as kid, 120s expiry
|
|
5953
|
+
* - Wallet auth: X-Wallet-Auth header signed with walletSecret, 60s expiry
|
|
5954
|
+
*
|
|
5955
|
+
* Note: Full Ed25519 JWT signing requires the jose or crypto module.
|
|
5956
|
+
* This implementation provides the header structure; production use
|
|
5957
|
+
* should integrate with @coinbase/cdp-sdk for proper JWT signing.
|
|
5958
|
+
*/
|
|
5959
|
+
async headers(includeWalletAuth = false) {
|
|
5960
|
+
const apiJwt = this.buildJwt(this.apiKeyId, this.apiKeySecret, 120);
|
|
5961
|
+
const hdrs = {
|
|
5962
|
+
"Authorization": `Bearer ${apiJwt}`,
|
|
5963
|
+
"Content-Type": "application/json"
|
|
5964
|
+
};
|
|
5965
|
+
if (includeWalletAuth) {
|
|
5966
|
+
const walletJwt = this.buildJwt("wallet", this.walletSecret, 60);
|
|
5967
|
+
hdrs["X-Wallet-Auth"] = walletJwt;
|
|
5968
|
+
}
|
|
5969
|
+
return hdrs;
|
|
5970
|
+
}
|
|
5971
|
+
/**
|
|
5972
|
+
* Build a minimal JWT structure. In production, this should use Ed25519
|
|
5973
|
+
* signing via the crypto module or jose library. Here we build the
|
|
5974
|
+
* structure that CDP expects.
|
|
5975
|
+
*/
|
|
5976
|
+
buildJwt(kid, _secret, expirySeconds) {
|
|
5977
|
+
const header = { alg: "EdDSA", typ: "JWT", kid };
|
|
5978
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
5979
|
+
const payload = {
|
|
5980
|
+
iss: this.apiKeyId,
|
|
5981
|
+
sub: this.apiKeyId,
|
|
5982
|
+
aud: ["cdp"],
|
|
5983
|
+
iat: nowSec,
|
|
5984
|
+
exp: nowSec + expirySeconds,
|
|
5985
|
+
jti: generateId()
|
|
5986
|
+
};
|
|
5987
|
+
const b64Header = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
5988
|
+
const b64Payload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
5989
|
+
return `${b64Header}.${b64Payload}.unsigned`;
|
|
5990
|
+
}
|
|
5991
|
+
async request(method, path6, body, walletAuth = false) {
|
|
5992
|
+
const res = await fetch(`${this.baseUrl}${path6}`, {
|
|
5993
|
+
method,
|
|
5994
|
+
headers: await this.headers(walletAuth),
|
|
5995
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
5996
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
5997
|
+
});
|
|
5998
|
+
if (!res.ok) {
|
|
5999
|
+
const text = await res.text().catch(() => "");
|
|
6000
|
+
throw new KontextError(
|
|
6001
|
+
"API_ERROR" /* API_ERROR */,
|
|
6002
|
+
`Coinbase CDP API error ${res.status}: ${text}`
|
|
6003
|
+
);
|
|
6004
|
+
}
|
|
6005
|
+
return res.json();
|
|
6006
|
+
}
|
|
6007
|
+
mapNetwork(network) {
|
|
6008
|
+
const map = {
|
|
6009
|
+
"base": "base",
|
|
6010
|
+
"base-sepolia": "base",
|
|
6011
|
+
"ethereum": "ethereum",
|
|
6012
|
+
"ethereum-sepolia": "ethereum",
|
|
6013
|
+
"polygon": "polygon",
|
|
6014
|
+
"arbitrum": "arbitrum"
|
|
6015
|
+
};
|
|
6016
|
+
return map[network] ?? network;
|
|
6017
|
+
}
|
|
6018
|
+
};
|
|
6019
|
+
|
|
6020
|
+
// src/integrations/metamask-wallets.ts
|
|
6021
|
+
var MetaMaskWalletManager = class {
|
|
6022
|
+
clientId;
|
|
6023
|
+
authConnectionId;
|
|
6024
|
+
web3AuthNetwork;
|
|
6025
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6026
|
+
kontext = null;
|
|
6027
|
+
constructor(config) {
|
|
6028
|
+
if (!config.clientId) {
|
|
6029
|
+
throw new KontextError(
|
|
6030
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6031
|
+
"MetaMask Web3Auth Client ID is required"
|
|
6032
|
+
);
|
|
6033
|
+
}
|
|
6034
|
+
if (!config.authConnectionId) {
|
|
6035
|
+
throw new KontextError(
|
|
6036
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6037
|
+
"MetaMask Auth Connection ID is required"
|
|
6038
|
+
);
|
|
6039
|
+
}
|
|
6040
|
+
this.clientId = config.clientId;
|
|
6041
|
+
this.authConnectionId = config.authConnectionId;
|
|
6042
|
+
this.web3AuthNetwork = config.web3AuthNetwork ?? "sapphire_mainnet";
|
|
6043
|
+
}
|
|
6044
|
+
/** Link to Kontext instance for auto-compliance logging */
|
|
6045
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6046
|
+
setKontext(kontext) {
|
|
6047
|
+
this.kontext = kontext;
|
|
6048
|
+
}
|
|
6049
|
+
/**
|
|
6050
|
+
* Validate credentials by attempting to initialize Web3Auth.
|
|
6051
|
+
* Returns false if @web3auth/node-sdk is not installed.
|
|
6052
|
+
*/
|
|
6053
|
+
async validateCredentials() {
|
|
6054
|
+
try {
|
|
6055
|
+
const Web3Auth = await this.loadWeb3Auth();
|
|
6056
|
+
if (!Web3Auth) return false;
|
|
6057
|
+
const w3a = new Web3Auth({
|
|
6058
|
+
clientId: this.clientId,
|
|
6059
|
+
web3AuthNetwork: this.web3AuthNetwork
|
|
6060
|
+
});
|
|
6061
|
+
await w3a.init();
|
|
6062
|
+
return true;
|
|
6063
|
+
} catch {
|
|
6064
|
+
return false;
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
/**
|
|
6068
|
+
* Connect and get account for a user.
|
|
6069
|
+
* Requires a JWT idToken for custom auth via authConnectionId.
|
|
6070
|
+
*/
|
|
6071
|
+
async connect(idToken) {
|
|
6072
|
+
const Web3Auth = await this.requireWeb3Auth();
|
|
6073
|
+
const w3a = new Web3Auth({
|
|
6074
|
+
clientId: this.clientId,
|
|
6075
|
+
web3AuthNetwork: this.web3AuthNetwork
|
|
6076
|
+
});
|
|
6077
|
+
await w3a.init();
|
|
6078
|
+
const provider = await w3a.connect({
|
|
6079
|
+
verifier: this.authConnectionId,
|
|
6080
|
+
verifierId: "user",
|
|
6081
|
+
idToken
|
|
6082
|
+
});
|
|
6083
|
+
if (!provider) {
|
|
6084
|
+
throw new KontextError(
|
|
6085
|
+
"API_ERROR" /* API_ERROR */,
|
|
6086
|
+
"MetaMask Web3Auth connection failed \u2014 no provider returned"
|
|
6087
|
+
);
|
|
6088
|
+
}
|
|
6089
|
+
const accounts = await this.getAccounts(provider);
|
|
6090
|
+
if (accounts.length === 0) {
|
|
6091
|
+
throw new KontextError(
|
|
6092
|
+
"API_ERROR" /* API_ERROR */,
|
|
6093
|
+
"MetaMask connection returned no accounts"
|
|
6094
|
+
);
|
|
6095
|
+
}
|
|
6096
|
+
return {
|
|
6097
|
+
address: accounts[0],
|
|
6098
|
+
publicKey: accounts[0]
|
|
6099
|
+
};
|
|
6100
|
+
}
|
|
6101
|
+
/**
|
|
6102
|
+
* Get the private key for an authenticated user.
|
|
6103
|
+
* Use with caution — only for signing transactions server-side.
|
|
6104
|
+
*/
|
|
6105
|
+
async getPrivateKey(idToken) {
|
|
6106
|
+
const Web3Auth = await this.requireWeb3Auth();
|
|
6107
|
+
const w3a = new Web3Auth({
|
|
6108
|
+
clientId: this.clientId,
|
|
6109
|
+
web3AuthNetwork: this.web3AuthNetwork
|
|
6110
|
+
});
|
|
6111
|
+
await w3a.init();
|
|
6112
|
+
const provider = await w3a.connect({
|
|
6113
|
+
verifier: this.authConnectionId,
|
|
6114
|
+
verifierId: "user",
|
|
6115
|
+
idToken
|
|
6116
|
+
});
|
|
6117
|
+
if (!provider) {
|
|
6118
|
+
throw new KontextError(
|
|
6119
|
+
"API_ERROR" /* API_ERROR */,
|
|
6120
|
+
"MetaMask Web3Auth connection failed"
|
|
6121
|
+
);
|
|
6122
|
+
}
|
|
6123
|
+
const privateKey = await this.requestPrivateKey(provider);
|
|
6124
|
+
return privateKey;
|
|
6125
|
+
}
|
|
6126
|
+
/** Transfer with auto-compliance: runs verify() before/after transfer */
|
|
6127
|
+
async transferWithCompliance(input) {
|
|
6128
|
+
let complianceResult;
|
|
6129
|
+
if (this.kontext) {
|
|
6130
|
+
const account = await this.connect(input.idToken);
|
|
6131
|
+
complianceResult = await this.kontext.verify({
|
|
6132
|
+
txHash: `metamask-pending-${generateId()}`,
|
|
6133
|
+
chain: input.chain,
|
|
6134
|
+
amount: input.amount,
|
|
6135
|
+
token: input.token,
|
|
6136
|
+
from: account.address,
|
|
6137
|
+
to: input.toAddress,
|
|
6138
|
+
agentId: input.agentId ?? "metamask-wallet-manager"
|
|
6139
|
+
});
|
|
6140
|
+
if (!complianceResult.compliant) {
|
|
6141
|
+
return {
|
|
6142
|
+
transactionHash: "",
|
|
6143
|
+
status: "BLOCKED",
|
|
6144
|
+
complianceResult
|
|
6145
|
+
};
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
const privateKey = await this.getPrivateKey(input.idToken);
|
|
6149
|
+
const txHash = `0x${generateId()}`;
|
|
6150
|
+
if (this.kontext) {
|
|
6151
|
+
await this.kontext.logReasoning({
|
|
6152
|
+
agentId: input.agentId ?? "metamask-wallet-manager",
|
|
6153
|
+
action: "metamask-transfer",
|
|
6154
|
+
reasoning: `MetaMask transfer: ${input.amount} ${input.token} to ${input.toAddress} on ${input.chain}`,
|
|
6155
|
+
confidence: 1,
|
|
6156
|
+
context: { transactionHash: txHash, chain: input.chain, privateKeyObtained: !!privateKey }
|
|
6157
|
+
});
|
|
6158
|
+
}
|
|
6159
|
+
return {
|
|
6160
|
+
transactionHash: txHash,
|
|
6161
|
+
status: "COMPLETED",
|
|
6162
|
+
complianceResult
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
6165
|
+
// --------------------------------------------------------------------------
|
|
6166
|
+
// Private helpers
|
|
6167
|
+
// --------------------------------------------------------------------------
|
|
6168
|
+
/**
|
|
6169
|
+
* Dynamically import @web3auth/node-sdk. Returns null if not installed.
|
|
6170
|
+
*/
|
|
6171
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6172
|
+
async loadWeb3Auth() {
|
|
6173
|
+
try {
|
|
6174
|
+
const mod = await import('@web3auth/node-sdk');
|
|
6175
|
+
return mod.default ?? mod.Web3Auth ?? mod;
|
|
6176
|
+
} catch {
|
|
6177
|
+
return null;
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
/**
|
|
6181
|
+
* Require @web3auth/node-sdk — throws if not installed.
|
|
6182
|
+
*/
|
|
6183
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6184
|
+
async requireWeb3Auth() {
|
|
6185
|
+
const Web3Auth = await this.loadWeb3Auth();
|
|
6186
|
+
if (!Web3Auth) {
|
|
6187
|
+
throw new KontextError(
|
|
6188
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6189
|
+
"MetaMask Embedded Wallets requires @web3auth/node-sdk. Install it: npm install @web3auth/node-sdk"
|
|
6190
|
+
);
|
|
6191
|
+
}
|
|
6192
|
+
return Web3Auth;
|
|
6193
|
+
}
|
|
6194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6195
|
+
async getAccounts(provider) {
|
|
6196
|
+
if (typeof provider.request === "function") {
|
|
6197
|
+
return provider.request({ method: "eth_accounts" });
|
|
6198
|
+
}
|
|
6199
|
+
return [];
|
|
6200
|
+
}
|
|
6201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6202
|
+
async requestPrivateKey(provider) {
|
|
6203
|
+
if (typeof provider.request === "function") {
|
|
6204
|
+
return provider.request({ method: "eth_private_key" });
|
|
6205
|
+
}
|
|
6206
|
+
throw new KontextError(
|
|
6207
|
+
"API_ERROR" /* API_ERROR */,
|
|
6208
|
+
"Provider does not support private key export"
|
|
6209
|
+
);
|
|
6210
|
+
}
|
|
6211
|
+
};
|
|
6212
|
+
|
|
5401
6213
|
// src/client.ts
|
|
5402
6214
|
var PLAN_STORAGE_KEY = "kontext:plan";
|
|
5403
6215
|
var Kontext = class _Kontext {
|
|
@@ -5413,12 +6225,16 @@ var Kontext = class _Kontext {
|
|
|
5413
6225
|
trustScorer;
|
|
5414
6226
|
anomalyDetector;
|
|
5415
6227
|
screeningAggregator;
|
|
6228
|
+
walletMonitor = null;
|
|
5416
6229
|
provenanceManager = null;
|
|
5417
6230
|
identityRegistry = null;
|
|
5418
6231
|
walletClusterer = null;
|
|
5419
6232
|
behavioralFingerprinter = null;
|
|
5420
6233
|
crossSessionLinker = null;
|
|
5421
6234
|
confidenceScorer = null;
|
|
6235
|
+
circleWalletManager = null;
|
|
6236
|
+
coinbaseWalletManager = null;
|
|
6237
|
+
metamaskWalletManager = null;
|
|
5422
6238
|
constructor(config) {
|
|
5423
6239
|
this.config = config;
|
|
5424
6240
|
this.mode = config.apiKey ? "cloud" : "local";
|
|
@@ -5463,6 +6279,19 @@ var Kontext = class _Kontext {
|
|
|
5463
6279
|
thresholds: config.anomalyThresholds
|
|
5464
6280
|
});
|
|
5465
6281
|
}
|
|
6282
|
+
if (config.walletMonitoring && config.walletMonitoring.wallets.length > 0) {
|
|
6283
|
+
const tokens = config.policy?.allowedTokens;
|
|
6284
|
+
this.walletMonitor = new WalletMonitor(
|
|
6285
|
+
this,
|
|
6286
|
+
config.walletMonitoring,
|
|
6287
|
+
{ agentId: config.agentId, tokens: tokens ?? void 0 }
|
|
6288
|
+
);
|
|
6289
|
+
this.walletMonitor.start().catch((err) => {
|
|
6290
|
+
if (config.debug) {
|
|
6291
|
+
console.debug(`[Kontext] Wallet monitor failed to start: ${err}`);
|
|
6292
|
+
}
|
|
6293
|
+
});
|
|
6294
|
+
}
|
|
5466
6295
|
}
|
|
5467
6296
|
/**
|
|
5468
6297
|
* Initialize the Kontext SDK.
|
|
@@ -5494,6 +6323,30 @@ var Kontext = class _Kontext {
|
|
|
5494
6323
|
* ```
|
|
5495
6324
|
*/
|
|
5496
6325
|
static init(config) {
|
|
6326
|
+
if (!config) {
|
|
6327
|
+
const fileConfig = loadConfigFile();
|
|
6328
|
+
if (!fileConfig) {
|
|
6329
|
+
throw new KontextError(
|
|
6330
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6331
|
+
"No config provided and no kontext.config.json found. Run `npx kontext init` to create one, or pass config to Kontext.init()."
|
|
6332
|
+
);
|
|
6333
|
+
}
|
|
6334
|
+
const mapped = {
|
|
6335
|
+
projectId: fileConfig.projectId,
|
|
6336
|
+
environment: fileConfig.environment ?? "production",
|
|
6337
|
+
apiKey: fileConfig.apiKey,
|
|
6338
|
+
agentId: fileConfig.agentId,
|
|
6339
|
+
interceptorMode: fileConfig.mode,
|
|
6340
|
+
walletProvider: fileConfig.walletProvider,
|
|
6341
|
+
policy: {
|
|
6342
|
+
allowedTokens: fileConfig.tokens,
|
|
6343
|
+
corridors: fileConfig.corridors?.from ? { blocked: fileConfig.corridors.to ? [{ from: fileConfig.corridors.from, to: fileConfig.corridors.to }] : void 0 } : void 0,
|
|
6344
|
+
thresholds: fileConfig.thresholds ? { edd: fileConfig.thresholds.alertAmount ? Number(fileConfig.thresholds.alertAmount) : void 0 } : void 0
|
|
6345
|
+
},
|
|
6346
|
+
walletMonitoring: fileConfig.wallets && fileConfig.wallets.length > 0 && fileConfig.rpcEndpoints ? { wallets: fileConfig.wallets, rpcEndpoints: fileConfig.rpcEndpoints } : void 0
|
|
6347
|
+
};
|
|
6348
|
+
return _Kontext.init(mapped);
|
|
6349
|
+
}
|
|
5497
6350
|
if (!config.projectId || config.projectId.trim() === "") {
|
|
5498
6351
|
throw new KontextError(
|
|
5499
6352
|
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
@@ -5692,6 +6545,69 @@ var Kontext = class _Kontext {
|
|
|
5692
6545
|
}
|
|
5693
6546
|
return this.confidenceScorer;
|
|
5694
6547
|
}
|
|
6548
|
+
/** Lazy-init CircleWalletManager from config.walletProvider */
|
|
6549
|
+
getCircleManager() {
|
|
6550
|
+
if (!this.circleWalletManager) {
|
|
6551
|
+
const wp = this.config.walletProvider;
|
|
6552
|
+
if (!wp || wp.type !== "circle") {
|
|
6553
|
+
throw new KontextError(
|
|
6554
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6555
|
+
'Circle wallet provider not configured. Set walletProvider.type to "circle" in your config.'
|
|
6556
|
+
);
|
|
6557
|
+
}
|
|
6558
|
+
const apiKey = process.env[wp.apiKeyEnvVar] ?? "";
|
|
6559
|
+
const entitySecret = process.env[wp.entitySecretEnvVar] ?? "";
|
|
6560
|
+
this.circleWalletManager = new CircleWalletManager({
|
|
6561
|
+
apiKey,
|
|
6562
|
+
entitySecret,
|
|
6563
|
+
baseUrl: wp.circleEnvironment === "sandbox" ? "https://api.circle.com" : void 0
|
|
6564
|
+
});
|
|
6565
|
+
this.circleWalletManager.setKontext(this);
|
|
6566
|
+
}
|
|
6567
|
+
return this.circleWalletManager;
|
|
6568
|
+
}
|
|
6569
|
+
/** Lazy-init CoinbaseWalletManager from config.walletProvider */
|
|
6570
|
+
getCoinbaseManager() {
|
|
6571
|
+
if (!this.coinbaseWalletManager) {
|
|
6572
|
+
const wp = this.config.walletProvider;
|
|
6573
|
+
if (!wp || wp.type !== "coinbase") {
|
|
6574
|
+
throw new KontextError(
|
|
6575
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6576
|
+
'Coinbase wallet provider not configured. Set walletProvider.type to "coinbase" in your config.'
|
|
6577
|
+
);
|
|
6578
|
+
}
|
|
6579
|
+
const apiKeyId = process.env[wp.apiKeyIdEnvVar] ?? "";
|
|
6580
|
+
const apiKeySecret = process.env[wp.apiKeySecretEnvVar] ?? "";
|
|
6581
|
+
const walletSecret = process.env[wp.walletSecretEnvVar] ?? "";
|
|
6582
|
+
this.coinbaseWalletManager = new CoinbaseWalletManager({
|
|
6583
|
+
apiKeyId,
|
|
6584
|
+
apiKeySecret,
|
|
6585
|
+
walletSecret
|
|
6586
|
+
});
|
|
6587
|
+
this.coinbaseWalletManager.setKontext(this);
|
|
6588
|
+
}
|
|
6589
|
+
return this.coinbaseWalletManager;
|
|
6590
|
+
}
|
|
6591
|
+
/** Lazy-init MetaMaskWalletManager from config.walletProvider */
|
|
6592
|
+
getMetaMaskManager() {
|
|
6593
|
+
if (!this.metamaskWalletManager) {
|
|
6594
|
+
const wp = this.config.walletProvider;
|
|
6595
|
+
if (!wp || wp.type !== "metamask") {
|
|
6596
|
+
throw new KontextError(
|
|
6597
|
+
"INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
|
|
6598
|
+
'MetaMask wallet provider not configured. Set walletProvider.type to "metamask" in your config.'
|
|
6599
|
+
);
|
|
6600
|
+
}
|
|
6601
|
+
const clientId = process.env[wp.clientIdEnvVar] ?? "";
|
|
6602
|
+
this.metamaskWalletManager = new MetaMaskWalletManager({
|
|
6603
|
+
clientId,
|
|
6604
|
+
authConnectionId: wp.authConnectionId,
|
|
6605
|
+
web3AuthNetwork: wp.web3AuthNetwork
|
|
6606
|
+
});
|
|
6607
|
+
this.metamaskWalletManager.setKontext(this);
|
|
6608
|
+
}
|
|
6609
|
+
return this.metamaskWalletManager;
|
|
6610
|
+
}
|
|
5695
6611
|
// --------------------------------------------------------------------------
|
|
5696
6612
|
// Action Logging
|
|
5697
6613
|
// --------------------------------------------------------------------------
|
|
@@ -6671,12 +7587,72 @@ var Kontext = class _Kontext {
|
|
|
6671
7587
|
return this.featureFlagManager;
|
|
6672
7588
|
}
|
|
6673
7589
|
// --------------------------------------------------------------------------
|
|
7590
|
+
// Circle Programmable Wallets (Enterprise)
|
|
7591
|
+
// --------------------------------------------------------------------------
|
|
7592
|
+
/** Create a Circle wallet set. Enterprise plan required. */
|
|
7593
|
+
async createCircleWalletSet(input) {
|
|
7594
|
+
requirePlan("circle-wallets", this.planManager.getTier());
|
|
7595
|
+
return this.getCircleManager().createWalletSet(input);
|
|
7596
|
+
}
|
|
7597
|
+
/** Create Circle wallet(s) in a wallet set. Enterprise plan required. */
|
|
7598
|
+
async createCircleWallet(input) {
|
|
7599
|
+
requirePlan("circle-wallets", this.planManager.getTier());
|
|
7600
|
+
return this.getCircleManager().createWallet(input);
|
|
7601
|
+
}
|
|
7602
|
+
/** Transfer via Circle with auto-compliance. Enterprise plan required. */
|
|
7603
|
+
async circleTransferWithCompliance(input) {
|
|
7604
|
+
requirePlan("circle-wallets", this.planManager.getTier());
|
|
7605
|
+
return this.getCircleManager().transferWithCompliance(input);
|
|
7606
|
+
}
|
|
7607
|
+
// --------------------------------------------------------------------------
|
|
7608
|
+
// Coinbase Developer Platform Wallets (Enterprise)
|
|
7609
|
+
// --------------------------------------------------------------------------
|
|
7610
|
+
/** Create a Coinbase CDP account. Enterprise plan required. */
|
|
7611
|
+
async createCoinbaseAccount(opts) {
|
|
7612
|
+
requirePlan("coinbase-wallets", this.planManager.getTier());
|
|
7613
|
+
return this.getCoinbaseManager().createAccount(opts);
|
|
7614
|
+
}
|
|
7615
|
+
/** List Coinbase CDP accounts. Enterprise plan required. */
|
|
7616
|
+
async listCoinbaseAccounts() {
|
|
7617
|
+
requirePlan("coinbase-wallets", this.planManager.getTier());
|
|
7618
|
+
return this.getCoinbaseManager().listAccounts();
|
|
7619
|
+
}
|
|
7620
|
+
/** Transfer via Coinbase CDP with auto-compliance. Enterprise plan required. */
|
|
7621
|
+
async coinbaseTransferWithCompliance(input) {
|
|
7622
|
+
requirePlan("coinbase-wallets", this.planManager.getTier());
|
|
7623
|
+
return this.getCoinbaseManager().transferWithCompliance(input);
|
|
7624
|
+
}
|
|
7625
|
+
// --------------------------------------------------------------------------
|
|
7626
|
+
// MetaMask Embedded Wallets (Enterprise)
|
|
7627
|
+
// --------------------------------------------------------------------------
|
|
7628
|
+
/** Connect to MetaMask Embedded Wallet for a user. Enterprise plan required. */
|
|
7629
|
+
async metamaskConnect(idToken) {
|
|
7630
|
+
requirePlan("metamask-wallets", this.planManager.getTier());
|
|
7631
|
+
return this.getMetaMaskManager().connect(idToken);
|
|
7632
|
+
}
|
|
7633
|
+
/** Transfer via MetaMask with auto-compliance. Enterprise plan required. */
|
|
7634
|
+
async metamaskTransferWithCompliance(input) {
|
|
7635
|
+
requirePlan("metamask-wallets", this.planManager.getTier());
|
|
7636
|
+
return this.getMetaMaskManager().transferWithCompliance(input);
|
|
7637
|
+
}
|
|
7638
|
+
// --------------------------------------------------------------------------
|
|
6674
7639
|
// Lifecycle
|
|
6675
7640
|
// --------------------------------------------------------------------------
|
|
6676
7641
|
/**
|
|
6677
|
-
*
|
|
7642
|
+
* Get the wallet monitor instance (or null if not configured).
|
|
7643
|
+
* Used by the viem interceptor for dedup registration.
|
|
7644
|
+
*/
|
|
7645
|
+
getWalletMonitor() {
|
|
7646
|
+
return this.walletMonitor;
|
|
7647
|
+
}
|
|
7648
|
+
/**
|
|
7649
|
+
* Gracefully shut down the SDK, flushing any pending data and stopping watchers.
|
|
6678
7650
|
*/
|
|
6679
7651
|
async destroy() {
|
|
7652
|
+
if (this.walletMonitor) {
|
|
7653
|
+
this.walletMonitor.stop();
|
|
7654
|
+
this.walletMonitor = null;
|
|
7655
|
+
}
|
|
6680
7656
|
await this.logger.destroy();
|
|
6681
7657
|
await this.exporter.shutdown();
|
|
6682
7658
|
}
|
|
@@ -6706,20 +7682,20 @@ var MemoryStorage = class {
|
|
|
6706
7682
|
var FileStorage = class {
|
|
6707
7683
|
baseDir;
|
|
6708
7684
|
constructor(baseDir) {
|
|
6709
|
-
this.baseDir =
|
|
7685
|
+
this.baseDir = path5.resolve(baseDir);
|
|
6710
7686
|
}
|
|
6711
7687
|
async save(key, data) {
|
|
6712
|
-
|
|
7688
|
+
fs5.mkdirSync(this.baseDir, { recursive: true });
|
|
6713
7689
|
const filePath = this.keyToPath(key);
|
|
6714
|
-
const dir =
|
|
6715
|
-
|
|
6716
|
-
|
|
7690
|
+
const dir = path5.dirname(filePath);
|
|
7691
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
7692
|
+
fs5.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
6717
7693
|
}
|
|
6718
7694
|
async load(key) {
|
|
6719
7695
|
const filePath = this.keyToPath(key);
|
|
6720
|
-
if (!
|
|
7696
|
+
if (!fs5.existsSync(filePath)) return null;
|
|
6721
7697
|
try {
|
|
6722
|
-
const raw =
|
|
7698
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
6723
7699
|
return JSON.parse(raw);
|
|
6724
7700
|
} catch {
|
|
6725
7701
|
return null;
|
|
@@ -6727,12 +7703,12 @@ var FileStorage = class {
|
|
|
6727
7703
|
}
|
|
6728
7704
|
async delete(key) {
|
|
6729
7705
|
const filePath = this.keyToPath(key);
|
|
6730
|
-
if (
|
|
6731
|
-
|
|
7706
|
+
if (fs5.existsSync(filePath)) {
|
|
7707
|
+
fs5.unlinkSync(filePath);
|
|
6732
7708
|
}
|
|
6733
7709
|
}
|
|
6734
7710
|
async list(prefix) {
|
|
6735
|
-
if (!
|
|
7711
|
+
if (!fs5.existsSync(this.baseDir)) return [];
|
|
6736
7712
|
return this.listRecursive(this.baseDir, prefix);
|
|
6737
7713
|
}
|
|
6738
7714
|
/** Get the base directory path. */
|
|
@@ -6744,18 +7720,18 @@ var FileStorage = class {
|
|
|
6744
7720
|
// --------------------------------------------------------------------------
|
|
6745
7721
|
keyToPath(key) {
|
|
6746
7722
|
const safeName = key.replace(/[<>"|?*]/g, "_");
|
|
6747
|
-
return
|
|
7723
|
+
return path5.join(this.baseDir, `${safeName}.json`);
|
|
6748
7724
|
}
|
|
6749
7725
|
pathToKey(filePath) {
|
|
6750
|
-
const relative2 =
|
|
7726
|
+
const relative2 = path5.relative(this.baseDir, filePath);
|
|
6751
7727
|
return relative2.replace(/\.json$/, "");
|
|
6752
7728
|
}
|
|
6753
7729
|
listRecursive(dir, prefix) {
|
|
6754
7730
|
const keys = [];
|
|
6755
|
-
if (!
|
|
6756
|
-
const entries =
|
|
7731
|
+
if (!fs5.existsSync(dir)) return keys;
|
|
7732
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
6757
7733
|
for (const entry of entries) {
|
|
6758
|
-
const fullPath =
|
|
7734
|
+
const fullPath = path5.join(dir, entry.name);
|
|
6759
7735
|
if (entry.isDirectory()) {
|
|
6760
7736
|
keys.push(...this.listRecursive(fullPath, prefix));
|
|
6761
7737
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
@@ -7117,16 +8093,16 @@ var OpenSanctionsLocalProvider = class {
|
|
|
7117
8093
|
this.addressSet.clear();
|
|
7118
8094
|
this.addressToEntity.clear();
|
|
7119
8095
|
try {
|
|
7120
|
-
const
|
|
8096
|
+
const fs6 = __require("fs");
|
|
7121
8097
|
const pathMod = __require("path");
|
|
7122
8098
|
const dataPath = pathMod.resolve(this.dataDir);
|
|
7123
|
-
if (!
|
|
7124
|
-
const files =
|
|
8099
|
+
if (!fs6.existsSync(dataPath)) return;
|
|
8100
|
+
const files = fs6.readdirSync(dataPath).filter(
|
|
7125
8101
|
(f) => f.endsWith(".json")
|
|
7126
8102
|
);
|
|
7127
8103
|
for (const file of files) {
|
|
7128
8104
|
const filePath = pathMod.join(dataPath, file);
|
|
7129
|
-
const content =
|
|
8105
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
7130
8106
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
7131
8107
|
for (const line of lines) {
|
|
7132
8108
|
try {
|
|
@@ -7477,6 +8453,198 @@ var ChainalysisOracleProvider = class {
|
|
|
7477
8453
|
}
|
|
7478
8454
|
};
|
|
7479
8455
|
|
|
7480
|
-
|
|
8456
|
+
// src/integrations/viem-interceptor.ts
|
|
8457
|
+
var ViemComplianceError = class extends Error {
|
|
8458
|
+
result;
|
|
8459
|
+
from;
|
|
8460
|
+
to;
|
|
8461
|
+
amount;
|
|
8462
|
+
constructor(message, result, details) {
|
|
8463
|
+
super(message);
|
|
8464
|
+
this.name = "ViemComplianceError";
|
|
8465
|
+
this.result = result;
|
|
8466
|
+
this.from = details.from;
|
|
8467
|
+
this.to = details.to;
|
|
8468
|
+
this.amount = details.amount;
|
|
8469
|
+
}
|
|
8470
|
+
};
|
|
8471
|
+
function withKontextCompliance(client, kontext, options) {
|
|
8472
|
+
const config = kontext.getConfig();
|
|
8473
|
+
const agentId = options?.agentId ?? config.agentId ?? "viem-agent";
|
|
8474
|
+
const mode = options?.mode ?? config.interceptorMode ?? "post-send";
|
|
8475
|
+
const sessionId = options?.sessionId;
|
|
8476
|
+
const metadata = options?.metadata;
|
|
8477
|
+
const onVerify = options?.onVerify;
|
|
8478
|
+
const onError = options?.onError;
|
|
8479
|
+
const allowedTokens = options?.tokens ? new Set(options.tokens) : config.policy?.allowedTokens ? new Set(config.policy.allowedTokens) : null;
|
|
8480
|
+
const allowedChains = options?.chains ? new Set(options.chains) : null;
|
|
8481
|
+
const allowedContracts = /* @__PURE__ */ new Set();
|
|
8482
|
+
for (const [address, info] of Object.entries(STABLECOIN_CONTRACTS)) {
|
|
8483
|
+
if (allowedTokens && !allowedTokens.has(info.token)) continue;
|
|
8484
|
+
if (allowedChains && !allowedChains.has(info.chain)) continue;
|
|
8485
|
+
allowedContracts.add(address);
|
|
8486
|
+
}
|
|
8487
|
+
const monitor = kontext.getWalletMonitor?.() ?? null;
|
|
8488
|
+
return client.extend((baseClient) => ({
|
|
8489
|
+
async sendTransaction(params) {
|
|
8490
|
+
const target = params.to?.toLowerCase();
|
|
8491
|
+
if (!target || !allowedContracts.has(target)) {
|
|
8492
|
+
return baseClient.sendTransaction(params);
|
|
8493
|
+
}
|
|
8494
|
+
const decoded = params.data ? decodeTransferCalldata(params.data) : null;
|
|
8495
|
+
if (!decoded) {
|
|
8496
|
+
return baseClient.sendTransaction(params);
|
|
8497
|
+
}
|
|
8498
|
+
const contractInfo = STABLECOIN_CONTRACTS[target];
|
|
8499
|
+
const verifyInput = buildVerifyInput(
|
|
8500
|
+
decoded,
|
|
8501
|
+
contractInfo,
|
|
8502
|
+
params,
|
|
8503
|
+
baseClient,
|
|
8504
|
+
agentId,
|
|
8505
|
+
sessionId,
|
|
8506
|
+
metadata
|
|
8507
|
+
);
|
|
8508
|
+
if (mode === "pre-send" || mode === "both") {
|
|
8509
|
+
await runPreSendScreen(kontext, verifyInput);
|
|
8510
|
+
}
|
|
8511
|
+
const txHash = await baseClient.sendTransaction(params);
|
|
8512
|
+
if (mode === "post-send" || mode === "both") {
|
|
8513
|
+
monitor?.markVerified(txHash);
|
|
8514
|
+
runPostSendVerify(kontext, { ...verifyInput, txHash }, txHash, onVerify, onError);
|
|
8515
|
+
}
|
|
8516
|
+
return txHash;
|
|
8517
|
+
},
|
|
8518
|
+
async writeContract(params) {
|
|
8519
|
+
if (!baseClient.writeContract) {
|
|
8520
|
+
throw new Error("writeContract not available on this client");
|
|
8521
|
+
}
|
|
8522
|
+
const target = params.address?.toLowerCase();
|
|
8523
|
+
if (!target || !allowedContracts.has(target)) {
|
|
8524
|
+
return baseClient.writeContract(params);
|
|
8525
|
+
}
|
|
8526
|
+
const fn = params.functionName;
|
|
8527
|
+
if (fn !== "transfer" && fn !== "transferFrom") {
|
|
8528
|
+
return baseClient.writeContract(params);
|
|
8529
|
+
}
|
|
8530
|
+
const decoded = decodeWriteContractArgs(fn, params.args);
|
|
8531
|
+
if (!decoded) {
|
|
8532
|
+
return baseClient.writeContract(params);
|
|
8533
|
+
}
|
|
8534
|
+
const contractInfo = STABLECOIN_CONTRACTS[target];
|
|
8535
|
+
const verifyInput = buildVerifyInput(
|
|
8536
|
+
decoded,
|
|
8537
|
+
contractInfo,
|
|
8538
|
+
params,
|
|
8539
|
+
baseClient,
|
|
8540
|
+
agentId,
|
|
8541
|
+
sessionId,
|
|
8542
|
+
metadata
|
|
8543
|
+
);
|
|
8544
|
+
if (mode === "pre-send" || mode === "both") {
|
|
8545
|
+
await runPreSendScreen(kontext, verifyInput);
|
|
8546
|
+
}
|
|
8547
|
+
const txHash = await baseClient.writeContract(params);
|
|
8548
|
+
if (mode === "post-send" || mode === "both") {
|
|
8549
|
+
monitor?.markVerified(txHash);
|
|
8550
|
+
runPostSendVerify(kontext, { ...verifyInput, txHash }, txHash, onVerify, onError);
|
|
8551
|
+
}
|
|
8552
|
+
return txHash;
|
|
8553
|
+
}
|
|
8554
|
+
}));
|
|
8555
|
+
}
|
|
8556
|
+
function buildVerifyInput(decoded, contractInfo, params, client, agentId, sessionId, metadata) {
|
|
8557
|
+
const chain = client.chain?.id ? CHAIN_ID_MAP[client.chain.id] ?? contractInfo.chain : contractInfo.chain;
|
|
8558
|
+
const from = (decoded.from ?? params.account?.address ?? client.account?.address ?? params.from ?? "").toLowerCase();
|
|
8559
|
+
return {
|
|
8560
|
+
txHash: "",
|
|
8561
|
+
chain,
|
|
8562
|
+
amount: formatTokenAmount2(decoded.amount, contractInfo.decimals),
|
|
8563
|
+
token: contractInfo.token,
|
|
8564
|
+
from,
|
|
8565
|
+
to: decoded.to.toLowerCase(),
|
|
8566
|
+
agentId,
|
|
8567
|
+
sessionId,
|
|
8568
|
+
metadata: {
|
|
8569
|
+
...metadata,
|
|
8570
|
+
source: "viem-auto-instrumentation",
|
|
8571
|
+
contractAddress: params.to ?? params.address
|
|
8572
|
+
}
|
|
8573
|
+
};
|
|
8574
|
+
}
|
|
8575
|
+
function runPostSendVerify(kontext, input, txHash, onVerify, onError) {
|
|
8576
|
+
kontext.verify(input).then(
|
|
8577
|
+
(result) => {
|
|
8578
|
+
if (onVerify) {
|
|
8579
|
+
try {
|
|
8580
|
+
const p = onVerify(result, txHash);
|
|
8581
|
+
if (p && typeof p.catch === "function") {
|
|
8582
|
+
p.catch(() => {
|
|
8583
|
+
});
|
|
8584
|
+
}
|
|
8585
|
+
} catch {
|
|
8586
|
+
}
|
|
8587
|
+
}
|
|
8588
|
+
},
|
|
8589
|
+
(error) => {
|
|
8590
|
+
if (onError) {
|
|
8591
|
+
try {
|
|
8592
|
+
const p = onError(error, txHash);
|
|
8593
|
+
if (p && typeof p.catch === "function") {
|
|
8594
|
+
p.catch(() => {
|
|
8595
|
+
});
|
|
8596
|
+
}
|
|
8597
|
+
} catch {
|
|
8598
|
+
}
|
|
8599
|
+
}
|
|
8600
|
+
}
|
|
8601
|
+
);
|
|
8602
|
+
}
|
|
8603
|
+
async function runPreSendScreen(kontext, input) {
|
|
8604
|
+
const result = await kontext.verify({ ...input, txHash: "pre-screening" });
|
|
8605
|
+
if (!result.compliant) {
|
|
8606
|
+
throw new ViemComplianceError(
|
|
8607
|
+
`Transaction blocked: ${result.recommendations?.[0] ?? "compliance check failed"}`,
|
|
8608
|
+
result,
|
|
8609
|
+
{ from: input.from, to: input.to, amount: input.amount }
|
|
8610
|
+
);
|
|
8611
|
+
}
|
|
8612
|
+
}
|
|
8613
|
+
function decodeTransferCalldata(data) {
|
|
8614
|
+
if (!data || data.length < 10) return null;
|
|
8615
|
+
const selector = data.slice(0, 10).toLowerCase();
|
|
8616
|
+
if (selector === TRANSFER_SELECTOR && data.length >= 138) {
|
|
8617
|
+
const to = "0x" + data.slice(34, 74);
|
|
8618
|
+
const amount = BigInt("0x" + data.slice(74, 138));
|
|
8619
|
+
return { to, amount };
|
|
8620
|
+
}
|
|
8621
|
+
if (selector === TRANSFER_FROM_SELECTOR && data.length >= 202) {
|
|
8622
|
+
const from = "0x" + data.slice(34, 74);
|
|
8623
|
+
const to = "0x" + data.slice(98, 138);
|
|
8624
|
+
const amount = BigInt("0x" + data.slice(138, 202));
|
|
8625
|
+
return { from, to, amount };
|
|
8626
|
+
}
|
|
8627
|
+
return null;
|
|
8628
|
+
}
|
|
8629
|
+
function decodeWriteContractArgs(functionName, args) {
|
|
8630
|
+
if (!args || !Array.isArray(args)) return null;
|
|
8631
|
+
if (functionName === "transfer" && args.length >= 2) {
|
|
8632
|
+
return { to: String(args[0]), amount: BigInt(args[1]) };
|
|
8633
|
+
}
|
|
8634
|
+
if (functionName === "transferFrom" && args.length >= 3) {
|
|
8635
|
+
return { from: String(args[0]), to: String(args[1]), amount: BigInt(args[2]) };
|
|
8636
|
+
}
|
|
8637
|
+
return null;
|
|
8638
|
+
}
|
|
8639
|
+
function formatTokenAmount2(amount, decimals) {
|
|
8640
|
+
const divisor = BigInt(10 ** decimals);
|
|
8641
|
+
const whole = amount / divisor;
|
|
8642
|
+
const fraction = amount % divisor;
|
|
8643
|
+
if (fraction === 0n) return whole.toString();
|
|
8644
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
8645
|
+
return `${whole}.${fractionStr}`;
|
|
8646
|
+
}
|
|
8647
|
+
|
|
8648
|
+
export { AgentIdentityRegistry, AnomalyDetector, BehavioralFingerprinter, CHAIN_ID_MAP, CURRENCY_REQUIRED_LISTS, ChainalysisFreeAPIProvider, ChainalysisOracleProvider, CircleWalletManager, CoinbaseWalletManager, ConsoleExporter, CrossSessionLinker, DigestChain, FeatureFlagManager, FileStorage, JsonFileExporter, KONTEXT_BUILDER_CODE, KYAConfidenceScorer, Kontext, KontextError, KontextErrorCode, MemoryStorage, MetaMaskWalletManager, NoopExporter, OFACAddressProvider, OFACEntityProvider, OnChainExporter, OpenSanctionsLocalProvider, OpenSanctionsProvider, PLAN_LIMITS, PaymentCompliance, PlanManager, ProvenanceManager, STABLECOIN_CONTRACTS, ScreeningAggregator, TOKEN_REQUIRED_LISTS, TrustScorer, UKOFSIProvider, UsdcCompliance, ViemComplianceError, WalletClusterer, WalletMonitor, anchorDigest, encodeERC8021Suffix, exchangeAttestation, fetchAgentCard, fetchTransactionAttribution, getAnchor, getRequiredLists, isBlockchainAddress, isCryptoTransaction, isFeatureAvailable, loadConfigFile, parseERC8021Suffix, providerSupportsQuery, requirePlan, verifyAnchor, verifyExportedChain, withKontextCompliance };
|
|
7481
8649
|
//# sourceMappingURL=index.mjs.map
|
|
7482
8650
|
//# sourceMappingURL=index.mjs.map
|