@exagent/agent 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3398,9 +3398,9 @@ var path7 = __toESM(require("path"));
3398
3398
 
3399
3399
  // src/runtime.ts
3400
3400
  var import_sdk = require("@exagent/sdk");
3401
- var import_viem7 = require("viem");
3402
- var import_chains5 = require("viem/chains");
3403
- var import_accounts5 = require("viem/accounts");
3401
+ var import_viem11 = require("viem");
3402
+ var import_chains8 = require("viem/chains");
3403
+ var import_accounts8 = require("viem/accounts");
3404
3404
 
3405
3405
  // src/position-tracker.ts
3406
3406
  init_market();
@@ -4000,6 +4000,49 @@ var PerpConfigSchema = import_zod.z.object({
4000
4000
  /** Allowed perp instruments (e.g. ["ETH", "BTC", "SOL"]). If empty, all instruments allowed. */
4001
4001
  allowedInstruments: import_zod.z.array(import_zod.z.string()).optional()
4002
4002
  }).optional();
4003
+ var PredictionConfigSchema = import_zod.z.object({
4004
+ /** Enable prediction market trading */
4005
+ enabled: import_zod.z.boolean().default(false),
4006
+ /** Polymarket CLOB API URL */
4007
+ clobApiUrl: import_zod.z.string().url().default("https://clob.polymarket.com"),
4008
+ /** Gamma API URL for market discovery */
4009
+ gammaApiUrl: import_zod.z.string().url().default("https://gamma-api.polymarket.com"),
4010
+ /** Polygon RPC URL */
4011
+ polygonRpcUrl: import_zod.z.string().url().optional(),
4012
+ /** CLOB API key (L2 auth) — derived from wallet signature if not provided */
4013
+ clobApiKey: import_zod.z.string().optional(),
4014
+ /** CLOB API secret (L2 auth) */
4015
+ clobApiSecret: import_zod.z.string().optional(),
4016
+ /** CLOB API passphrase (L2 auth) */
4017
+ clobApiPassphrase: import_zod.z.string().optional(),
4018
+ /** Private key for the prediction relayer (calls recordPerpTrade on Base). Falls back to agent wallet. */
4019
+ predictionRelayerKey: import_zod.z.string().optional(),
4020
+ /** Maximum notional per trade in USD (default: 500) */
4021
+ maxNotionalUSD: import_zod.z.number().min(1).default(500),
4022
+ /** Maximum total exposure across all markets in USD (default: 5000) */
4023
+ maxTotalExposureUSD: import_zod.z.number().min(1).default(5e3),
4024
+ /** Target USDC allocation on Polygon in USD (default: 100) */
4025
+ polygonAllocationUSD: import_zod.z.number().min(1).default(100),
4026
+ /** Allowed market categories (empty = all categories) */
4027
+ allowedCategories: import_zod.z.array(import_zod.z.string()).optional(),
4028
+ /** Fee bridge threshold in USD — accumulated fees are bridged to Base when exceeded (default: 10) */
4029
+ feeBridgeThresholdUSD: import_zod.z.number().min(1).default(10),
4030
+ /** Prediction vault configuration (ERC-4626 + ERC-1271 on Polygon) */
4031
+ vault: import_zod.z.object({
4032
+ /** Enable vault mode for prediction trading */
4033
+ enabled: import_zod.z.boolean().default(false),
4034
+ /** PredictionVaultFactory address on Polygon */
4035
+ factoryAddress: import_zod.z.string(),
4036
+ /** PredictionFeeCollector address on Polygon */
4037
+ feeCollectorAddress: import_zod.z.string().optional(),
4038
+ /** Existing vault address (if already created) */
4039
+ vaultAddress: import_zod.z.string().optional(),
4040
+ /** Agent's fee recipient address */
4041
+ feeRecipient: import_zod.z.string().optional(),
4042
+ /** Whether agent is verified (higher deposit cap) */
4043
+ isVerified: import_zod.z.boolean().default(false)
4044
+ }).optional()
4045
+ }).optional();
4003
4046
  var AgentConfigSchema = import_zod.z.object({
4004
4047
  // Identity (from on-chain registration)
4005
4048
  agentId: import_zod.z.union([import_zod.z.number().positive(), import_zod.z.string()]),
@@ -4019,6 +4062,8 @@ var AgentConfigSchema = import_zod.z.object({
4019
4062
  relay: RelayConfigSchema,
4020
4063
  // Perp trading configuration (Hyperliquid)
4021
4064
  perp: PerpConfigSchema,
4065
+ // Prediction market configuration (Polymarket)
4066
+ prediction: PredictionConfigSchema,
4022
4067
  // Allowed tokens (addresses)
4023
4068
  allowedTokens: import_zod.z.array(import_zod.z.string()).optional()
4024
4069
  });
@@ -4118,13 +4163,6 @@ var VAULT_FACTORY_ABI = [
4118
4163
  outputs: [{ type: "address" }],
4119
4164
  stateMutability: "view"
4120
4165
  },
4121
- {
4122
- type: "function",
4123
- name: "canCreateVault",
4124
- inputs: [{ name: "creator", type: "address" }],
4125
- outputs: [{ name: "canCreate", type: "bool" }, { name: "reason", type: "string" }],
4126
- stateMutability: "view"
4127
- },
4128
4166
  {
4129
4167
  type: "function",
4130
4168
  name: "createVault",
@@ -4141,10 +4179,17 @@ var VAULT_FACTORY_ABI = [
4141
4179
  },
4142
4180
  {
4143
4181
  type: "function",
4144
- name: "minimumVeEXARequired",
4182
+ name: "MIN_SEED_AMOUNT",
4145
4183
  inputs: [],
4146
4184
  outputs: [{ type: "uint256" }],
4147
4185
  stateMutability: "view"
4186
+ },
4187
+ {
4188
+ type: "function",
4189
+ name: "allowedAssets",
4190
+ inputs: [{ name: "asset", type: "address" }],
4191
+ outputs: [{ type: "bool" }],
4192
+ stateMutability: "view"
4148
4193
  }
4149
4194
  ];
4150
4195
  var VAULT_ABI = [
@@ -4242,23 +4287,34 @@ var VaultManager = class {
4242
4287
  } catch {
4243
4288
  }
4244
4289
  }
4245
- const [canCreateResult, requirements] = await Promise.all([
4246
- this.publicClient.readContract({
4247
- address: this.addresses.vaultFactory,
4248
- abi: VAULT_FACTORY_ABI,
4249
- functionName: "canCreateVault",
4250
- args: [this.account.address]
4251
- }),
4252
- this.getRequirements()
4253
- ]);
4290
+ let canCreate = true;
4291
+ let cannotCreateReason = null;
4292
+ if (hasVault) {
4293
+ canCreate = false;
4294
+ cannotCreateReason = "Vault already exists for this agent";
4295
+ } else {
4296
+ try {
4297
+ const isAssetAllowed = await this.publicClient.readContract({
4298
+ address: this.addresses.vaultFactory,
4299
+ abi: VAULT_FACTORY_ABI,
4300
+ functionName: "allowedAssets",
4301
+ args: [this.addresses.usdc]
4302
+ });
4303
+ if (!isAssetAllowed) {
4304
+ canCreate = false;
4305
+ cannotCreateReason = "USDC is not an allowed asset on the vault factory";
4306
+ }
4307
+ } catch {
4308
+ }
4309
+ }
4254
4310
  return {
4255
4311
  hasVault,
4256
4312
  vaultAddress,
4257
4313
  totalAssets,
4258
- canCreateVault: canCreateResult[0],
4259
- cannotCreateReason: canCreateResult[0] ? null : canCreateResult[1],
4260
- requirementsMet: canCreateResult[0] || requirements.isBypassed,
4261
- requirements
4314
+ canCreateVault: canCreate,
4315
+ cannotCreateReason,
4316
+ requirementsMet: canCreate,
4317
+ requirements: { isBypassed: true }
4262
4318
  };
4263
4319
  }
4264
4320
  /**
@@ -4306,11 +4362,67 @@ var VaultManager = class {
4306
4362
  if (existingVault) {
4307
4363
  return { success: false, error: "Vault already exists", vaultAddress: existingVault };
4308
4364
  }
4309
- const status = await this.getVaultStatus();
4310
- if (!status.canCreateVault) {
4311
- return { success: false, error: status.cannotCreateReason || "Requirements not met" };
4312
- }
4313
4365
  const seed = seedAmount || BigInt(1e8);
4366
+ let usdcBalance;
4367
+ try {
4368
+ usdcBalance = await this.publicClient.readContract({
4369
+ address: this.addresses.usdc,
4370
+ abi: import_viem3.erc20Abi,
4371
+ functionName: "balanceOf",
4372
+ args: [this.account.address]
4373
+ });
4374
+ } catch {
4375
+ return { success: false, error: "Failed to read USDC balance" };
4376
+ }
4377
+ if (usdcBalance < seed) {
4378
+ const needed = Number(seed) / 1e6;
4379
+ const have = Number(usdcBalance) / 1e6;
4380
+ return {
4381
+ success: false,
4382
+ error: `Insufficient USDC: need ${needed} USDC seed but wallet has ${have} USDC. Fund ${this.account.address} with at least ${needed} USDC and retry.`
4383
+ };
4384
+ }
4385
+ try {
4386
+ const isAllowed = await this.publicClient.readContract({
4387
+ address: this.addresses.vaultFactory,
4388
+ abi: VAULT_FACTORY_ABI,
4389
+ functionName: "allowedAssets",
4390
+ args: [this.addresses.usdc]
4391
+ });
4392
+ if (!isAllowed) {
4393
+ return { success: false, error: "USDC is not whitelisted on the vault factory. Contact protocol admin." };
4394
+ }
4395
+ } catch {
4396
+ }
4397
+ try {
4398
+ const currentAllowance = await this.publicClient.readContract({
4399
+ address: this.addresses.usdc,
4400
+ abi: import_viem3.erc20Abi,
4401
+ functionName: "allowance",
4402
+ args: [this.account.address, this.addresses.vaultFactory]
4403
+ });
4404
+ if (currentAllowance < seed) {
4405
+ console.log(`Approving ${Number(seed) / 1e6} USDC to VaultFactory...`);
4406
+ const approveHash = await this.walletClient.writeContract({
4407
+ address: this.addresses.usdc,
4408
+ abi: import_viem3.erc20Abi,
4409
+ functionName: "approve",
4410
+ args: [this.addresses.vaultFactory, seed],
4411
+ chain: import_chains2.base,
4412
+ account: this.account
4413
+ });
4414
+ const approveReceipt = await this.publicClient.waitForTransactionReceipt({ hash: approveHash });
4415
+ if (approveReceipt.status !== "success") {
4416
+ return { success: false, error: "USDC approval transaction failed" };
4417
+ }
4418
+ console.log("USDC approved successfully");
4419
+ }
4420
+ } catch (error) {
4421
+ return {
4422
+ success: false,
4423
+ error: `USDC approval failed: ${error instanceof Error ? error.message : "Unknown error"}`
4424
+ };
4425
+ }
4314
4426
  const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
4315
4427
  const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
4316
4428
  const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
@@ -4332,8 +4444,9 @@ var VaultManager = class {
4332
4444
  });
4333
4445
  const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
4334
4446
  if (receipt.status !== "success") {
4335
- return { success: false, error: "Transaction failed", txHash: hash };
4447
+ return { success: false, error: "createVault transaction reverted on-chain", txHash: hash };
4336
4448
  }
4449
+ this.lastVaultCheck = 0;
4337
4450
  const vaultAddress = await this.getVaultAddress();
4338
4451
  this.cachedVaultAddress = vaultAddress;
4339
4452
  return {
@@ -4342,10 +4455,23 @@ var VaultManager = class {
4342
4455
  txHash: hash
4343
4456
  };
4344
4457
  } catch (error) {
4345
- return {
4346
- success: false,
4347
- error: error instanceof Error ? error.message : "Unknown error"
4348
- };
4458
+ const msg = error instanceof Error ? error.message : "Unknown error";
4459
+ if (msg.includes("NotAgentOwner")) {
4460
+ return { success: false, error: "This wallet is not the owner of the agent. Only the agent owner can create a fund." };
4461
+ }
4462
+ if (msg.includes("InsufficientStake")) {
4463
+ return { success: false, error: "Staking requirement not met. This should not happen with StakingStub \u2014 contact protocol admin." };
4464
+ }
4465
+ if (msg.includes("FrontierCannotCreateVault")) {
4466
+ return { success: false, error: "Frontier-risk agents cannot create funds. Change the agent risk universe first." };
4467
+ }
4468
+ if (msg.includes("VaultAlreadyExists")) {
4469
+ return { success: false, error: "A fund already exists for this agent and asset." };
4470
+ }
4471
+ if (msg.includes("InsufficientSeed")) {
4472
+ return { success: false, error: `Seed amount too low. Minimum is 100 USDC.` };
4473
+ }
4474
+ return { success: false, error: msg };
4349
4475
  }
4350
4476
  }
4351
4477
  /**
@@ -4390,7 +4516,7 @@ var VaultManager = class {
4390
4516
 
4391
4517
  // src/relay.ts
4392
4518
  var import_ws2 = __toESM(require("ws"));
4393
- var import_accounts4 = require("viem/accounts");
4519
+ var import_accounts7 = require("viem/accounts");
4394
4520
 
4395
4521
  // src/index.ts
4396
4522
  init_store();
@@ -5643,172 +5769,1979 @@ var CORE_DEPOSIT_WALLET_ABI = (0, import_viem6.parseAbi)([
5643
5769
  "function deposit(uint256 amount, uint32 destinationDex) external"
5644
5770
  ]);
5645
5771
 
5646
- // src/secure-env.ts
5647
- var crypto = __toESM(require("crypto"));
5648
- var fs = __toESM(require("fs"));
5649
- var path = __toESM(require("path"));
5650
- var ALGORITHM = "aes-256-gcm";
5651
- var PBKDF2_ITERATIONS = 1e5;
5652
- var SALT_LENGTH = 32;
5653
- var IV_LENGTH = 16;
5654
- var KEY_LENGTH = 32;
5655
- var SENSITIVE_PATTERNS = [
5656
- /PRIVATE_KEY$/i,
5657
- /_API_KEY$/i,
5658
- /API_KEY$/i,
5659
- /_SECRET$/i,
5660
- /^OPENAI_API_KEY$/i,
5661
- /^ANTHROPIC_API_KEY$/i,
5662
- /^GOOGLE_AI_API_KEY$/i,
5663
- /^DEEPSEEK_API_KEY$/i,
5664
- /^MISTRAL_API_KEY$/i,
5665
- /^GROQ_API_KEY$/i,
5666
- /^TOGETHER_API_KEY$/i
5667
- ];
5668
- function isSensitiveKey(key) {
5669
- return SENSITIVE_PATTERNS.some((pattern) => pattern.test(key));
5670
- }
5671
- function deriveKey(passphrase, salt) {
5672
- return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
5673
- }
5674
- function encryptValue(value, key) {
5675
- const iv = crypto.randomBytes(IV_LENGTH);
5676
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
5677
- let encrypted = cipher.update(value, "utf8", "hex");
5678
- encrypted += cipher.final("hex");
5679
- const tag = cipher.getAuthTag();
5680
- return {
5681
- iv: iv.toString("hex"),
5682
- encrypted,
5683
- tag: tag.toString("hex")
5684
- };
5685
- }
5686
- function decryptValue(encrypted, key, iv, tag) {
5687
- const decipher = crypto.createDecipheriv(
5688
- ALGORITHM,
5689
- key,
5690
- Buffer.from(iv, "hex")
5691
- );
5692
- decipher.setAuthTag(Buffer.from(tag, "hex"));
5693
- let decrypted = decipher.update(encrypted, "hex", "utf8");
5694
- decrypted += decipher.final("utf8");
5695
- return decrypted;
5696
- }
5697
- function parseEnvFile(content) {
5698
- const entries = [];
5699
- for (const line of content.split("\n")) {
5700
- const trimmed = line.trim();
5701
- if (!trimmed || trimmed.startsWith("#")) continue;
5702
- const eqIndex = trimmed.indexOf("=");
5703
- if (eqIndex === -1) continue;
5704
- const key = trimmed.slice(0, eqIndex).trim();
5705
- let value = trimmed.slice(eqIndex + 1).trim();
5706
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
5707
- value = value.slice(1, -1);
5708
- }
5709
- if (key && value) {
5710
- entries.push({ key, value });
5711
- }
5772
+ // src/prediction/types.ts
5773
+ var DEFAULT_PREDICTION_CONFIG = {
5774
+ clobApiUrl: "https://clob.polymarket.com",
5775
+ gammaApiUrl: "https://gamma-api.polymarket.com",
5776
+ polygonRpcUrl: "https://polygon-rpc.com",
5777
+ maxNotionalUSD: 1e3,
5778
+ maxTotalExposureUSD: 5e3,
5779
+ polygonAllocationUSD: 100,
5780
+ feeBridgeThresholdUSD: 10
5781
+ };
5782
+ var POLYGON_CHAIN_ID = 137;
5783
+ var POLYMARKET_SIGNATURE_TYPE_EOA = 0;
5784
+ var POLYMARKET_SIGNATURE_TYPE_GNOSIS_SAFE = 1;
5785
+ var POLYGON_USDC_ADDRESS = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
5786
+ var POLYGON_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
5787
+ var POLYGON_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
5788
+ var CCTP_DOMAIN_BASE = 6;
5789
+ var CCTP_DOMAIN_POLYGON = 7;
5790
+ var PREDICTION_INSTRUMENT_PREFIX = "POLY:";
5791
+
5792
+ // src/prediction/client.ts
5793
+ var import_clob_client = require("@polymarket/clob-client");
5794
+ var import_ethers = require("ethers");
5795
+ var PolymarketClient = class {
5796
+ config;
5797
+ clobClient = null;
5798
+ apiCreds = null;
5799
+ signer;
5800
+ walletAddress;
5801
+ /** Vault mode: when true, orders use maker=vaultAddress with POLY_GNOSIS_SAFE signature type */
5802
+ vaultMode;
5803
+ /** Vault contract address (only set in vault mode) */
5804
+ vaultAddress;
5805
+ /** Cache for Gamma API market data (conditionId -> market) */
5806
+ marketCache = /* @__PURE__ */ new Map();
5807
+ CACHE_TTL_MS = 6e4;
5808
+ // 60 seconds
5809
+ constructor(privateKey, config, vaultOpts) {
5810
+ this.config = { ...DEFAULT_PREDICTION_CONFIG, ...config };
5811
+ this.signer = new import_ethers.Wallet(privateKey);
5812
+ this.walletAddress = this.signer.address;
5813
+ this.vaultMode = vaultOpts?.vaultMode ?? false;
5814
+ this.vaultAddress = vaultOpts?.vaultAddress;
5712
5815
  }
5713
- return entries;
5714
- }
5715
- function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
5716
- if (!fs.existsSync(envPath)) {
5717
- throw new Error(`File not found: ${envPath}`);
5816
+ // ============================================================
5817
+ // INITIALIZATION
5818
+ // ============================================================
5819
+ /**
5820
+ * Initialize the CLOB client with L2 API credentials.
5821
+ * Must be called once before placing orders.
5822
+ */
5823
+ async initialize() {
5824
+ const initClient = new import_clob_client.ClobClient(
5825
+ this.config.clobApiUrl,
5826
+ POLYGON_CHAIN_ID,
5827
+ this.signer
5828
+ );
5829
+ this.apiCreds = await initClient.createOrDeriveApiKey();
5830
+ const signatureType = this.vaultMode ? POLYMARKET_SIGNATURE_TYPE_GNOSIS_SAFE : POLYMARKET_SIGNATURE_TYPE_EOA;
5831
+ this.clobClient = new import_clob_client.ClobClient(
5832
+ this.config.clobApiUrl,
5833
+ POLYGON_CHAIN_ID,
5834
+ this.signer,
5835
+ this.apiCreds,
5836
+ signatureType
5837
+ );
5838
+ if (this.vaultMode) {
5839
+ console.log(`Polymarket CLOB initialized in VAULT MODE \u2014 maker: ${this.vaultAddress}, signer: ${this.walletAddress}`);
5840
+ } else {
5841
+ console.log(`Polymarket CLOB initialized for ${this.walletAddress}`);
5842
+ }
5718
5843
  }
5719
- const content = fs.readFileSync(envPath, "utf-8");
5720
- const entries = parseEnvFile(content);
5721
- if (entries.length === 0) {
5722
- throw new Error("No environment variables found in file");
5844
+ /**
5845
+ * Check if the client is initialized with CLOB credentials.
5846
+ */
5847
+ get isInitialized() {
5848
+ return this.clobClient !== null && this.apiCreds !== null;
5723
5849
  }
5724
- const salt = crypto.randomBytes(SALT_LENGTH);
5725
- const key = deriveKey(passphrase, salt);
5726
- const encryptedEntries = entries.map(({ key: envKey, value }) => {
5727
- if (isSensitiveKey(envKey)) {
5728
- const { iv, encrypted, tag } = encryptValue(value, key);
5729
- return {
5730
- key: envKey,
5731
- value: encrypted,
5732
- encrypted: true,
5733
- iv,
5734
- tag
5735
- };
5736
- }
5850
+ // ============================================================
5851
+ // CLOB API ORDER BOOK & PRICES
5852
+ // ============================================================
5853
+ /**
5854
+ * Get the order book for a specific outcome token.
5855
+ */
5856
+ async getOrderBook(tokenId) {
5857
+ this.ensureInitialized();
5858
+ const book = await this.clobClient.getOrderBook(tokenId);
5737
5859
  return {
5738
- key: envKey,
5739
- value,
5740
- encrypted: false
5860
+ bids: (book.bids || []).map((b) => ({ price: parseFloat(b.price), size: parseFloat(b.size) })),
5861
+ asks: (book.asks || []).map((a) => ({ price: parseFloat(a.price), size: parseFloat(a.size) }))
5741
5862
  };
5742
- });
5743
- const encryptedEnv = {
5744
- version: 1,
5745
- salt: salt.toString("hex"),
5746
- entries: encryptedEntries
5747
- };
5748
- const encPath = envPath + ".enc";
5749
- fs.writeFileSync(encPath, JSON.stringify(encryptedEnv, null, 2), { mode: 384 });
5750
- if (deleteOriginal) {
5751
- fs.unlinkSync(envPath);
5752
5863
  }
5753
- const sensitiveCount = encryptedEntries.filter((e) => e.encrypted).length;
5754
- const plainCount = encryptedEntries.filter((e) => !e.encrypted).length;
5755
- console.log(
5756
- `Encrypted ${sensitiveCount} sensitive values (${plainCount} non-sensitive kept as plaintext)`
5757
- );
5758
- return encPath;
5759
- }
5760
- function decryptEnvFile(encPath, passphrase) {
5761
- if (!fs.existsSync(encPath)) {
5762
- throw new Error(`Encrypted env file not found: ${encPath}`);
5864
+ /**
5865
+ * Get the midpoint price for an outcome token.
5866
+ */
5867
+ async getMidpointPrice(tokenId) {
5868
+ const book = await this.getOrderBook(tokenId);
5869
+ if (book.bids.length === 0 || book.asks.length === 0) return 0;
5870
+ return (book.bids[0].price + book.asks[0].price) / 2;
5763
5871
  }
5764
- const content = JSON.parse(fs.readFileSync(encPath, "utf-8"));
5765
- if (content.version !== 1) {
5766
- throw new Error(`Unsupported encrypted env version: ${content.version}`);
5872
+ /**
5873
+ * Get the last trade price for an outcome token.
5874
+ */
5875
+ async getLastTradePrice(tokenId) {
5876
+ this.ensureInitialized();
5877
+ const resp = await this.clobClient.getLastTradePrice(tokenId);
5878
+ return parseFloat(resp?.price || "0");
5767
5879
  }
5768
- const salt = Buffer.from(content.salt, "hex");
5769
- const key = deriveKey(passphrase, salt);
5770
- const result = {};
5771
- for (const entry of content.entries) {
5772
- if (entry.encrypted) {
5773
- if (!entry.iv || !entry.tag) {
5774
- throw new Error(`Missing encryption metadata for ${entry.key}`);
5775
- }
5776
- try {
5777
- result[entry.key] = decryptValue(entry.value, key, entry.iv, entry.tag);
5778
- } catch {
5779
- throw new Error(
5780
- `Failed to decrypt ${entry.key}. Wrong passphrase or corrupted data.`
5781
- );
5782
- }
5783
- } else {
5784
- result[entry.key] = entry.value;
5785
- }
5880
+ // ============================================================
5881
+ // CLOB API ORDERS
5882
+ // ============================================================
5883
+ /**
5884
+ * Place a limit order on the CLOB.
5885
+ */
5886
+ async placeLimitOrder(params) {
5887
+ this.ensureInitialized();
5888
+ const order = await this.clobClient.createAndPostOrder({
5889
+ tokenID: params.tokenId,
5890
+ price: params.price,
5891
+ side: params.side,
5892
+ size: params.size
5893
+ });
5894
+ return {
5895
+ orderId: order?.orderID || "",
5896
+ success: !!order?.orderID
5897
+ };
5786
5898
  }
5787
- return result;
5788
- }
5789
- function loadSecureEnv(basePath, passphrase) {
5790
- const encPath = path.join(basePath, ".env.enc");
5791
- const envPath = path.join(basePath, ".env");
5792
- if (fs.existsSync(encPath)) {
5793
- if (!passphrase) {
5794
- passphrase = process.env.EXAGENT_PASSPHRASE;
5795
- }
5796
- if (!passphrase) {
5797
- console.warn("");
5798
- console.warn("WARNING: Found .env.enc but no passphrase provided.");
5799
- console.warn(" Set EXAGENT_PASSPHRASE environment variable or");
5800
- console.warn(" pass --passphrase when running the agent.");
5801
- console.warn(" Falling back to plaintext .env file.");
5802
- console.warn("");
5803
- } else {
5804
- const vars = decryptEnvFile(encPath, passphrase);
5805
- for (const [key, value] of Object.entries(vars)) {
5806
- process.env[key] = value;
5807
- }
5899
+ /**
5900
+ * Place a market order (aggressive limit at best available price).
5901
+ */
5902
+ async placeMarketOrder(params) {
5903
+ this.ensureInitialized();
5904
+ const order = await this.clobClient.createMarketOrder({
5905
+ tokenID: params.tokenId,
5906
+ amount: params.amount,
5907
+ side: params.side
5908
+ });
5909
+ const result = await this.clobClient.postOrder(order);
5910
+ return {
5911
+ orderId: result?.orderID || "",
5912
+ success: !!result?.orderID
5913
+ };
5914
+ }
5915
+ /**
5916
+ * Cancel an open order by ID.
5917
+ */
5918
+ async cancelOrder(orderId) {
5919
+ this.ensureInitialized();
5920
+ try {
5921
+ await this.clobClient.cancelOrder({ orderID: orderId });
5808
5922
  return true;
5923
+ } catch {
5924
+ return false;
5809
5925
  }
5810
5926
  }
5811
- if (fs.existsSync(envPath)) {
5927
+ /**
5928
+ * Cancel all open orders.
5929
+ */
5930
+ async cancelAllOrders() {
5931
+ this.ensureInitialized();
5932
+ try {
5933
+ await this.clobClient.cancelAll();
5934
+ return true;
5935
+ } catch {
5936
+ return false;
5937
+ }
5938
+ }
5939
+ /**
5940
+ * Get open orders for the agent's wallet.
5941
+ */
5942
+ async getOpenOrders() {
5943
+ this.ensureInitialized();
5944
+ return this.clobClient.getOpenOrders();
5945
+ }
5946
+ /**
5947
+ * Get trade history (fills) for the agent's wallet.
5948
+ */
5949
+ async getTradeHistory() {
5950
+ this.ensureInitialized();
5951
+ const trades = await this.clobClient.getTrades();
5952
+ return (trades || []).map((t) => this.parseRawFill(t));
5953
+ }
5954
+ // ============================================================
5955
+ // GAMMA API — MARKET DISCOVERY (public, no auth)
5956
+ // ============================================================
5957
+ /**
5958
+ * Get active prediction markets from Gamma API.
5959
+ */
5960
+ async getMarkets(params) {
5961
+ const query = new URLSearchParams();
5962
+ if (params?.limit) query.set("limit", String(params.limit));
5963
+ if (params?.offset) query.set("offset", String(params.offset));
5964
+ if (params?.active !== void 0) query.set("active", String(params.active));
5965
+ if (params?.category) query.set("tag", params.category);
5966
+ const url = `${this.config.gammaApiUrl}/markets?${query.toString()}`;
5967
+ const resp = await fetch(url);
5968
+ if (!resp.ok) throw new Error(`Gamma API error: ${resp.status} ${await resp.text()}`);
5969
+ const raw = await resp.json();
5970
+ return (raw || []).map((m) => this.parseGammaMarket(m));
5971
+ }
5972
+ /**
5973
+ * Get a single market by condition ID.
5974
+ */
5975
+ async getMarketByConditionId(conditionId) {
5976
+ const cached = this.marketCache.get(conditionId);
5977
+ if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
5978
+ return cached.market;
5979
+ }
5980
+ const url = `${this.config.gammaApiUrl}/markets?condition_id=${conditionId}`;
5981
+ const resp = await fetch(url);
5982
+ if (!resp.ok) return null;
5983
+ const raw = await resp.json();
5984
+ if (!raw || raw.length === 0) return null;
5985
+ const market = this.parseGammaMarket(raw[0]);
5986
+ this.marketCache.set(conditionId, { market, cachedAt: Date.now() });
5987
+ return market;
5988
+ }
5989
+ /**
5990
+ * Search markets by query string.
5991
+ */
5992
+ async searchMarkets(query, limit = 20) {
5993
+ const url = `${this.config.gammaApiUrl}/markets?_q=${encodeURIComponent(query)}&limit=${limit}&active=true`;
5994
+ const resp = await fetch(url);
5995
+ if (!resp.ok) return [];
5996
+ const raw = await resp.json();
5997
+ return (raw || []).map((m) => this.parseGammaMarket(m));
5998
+ }
5999
+ /**
6000
+ * Get trending markets sorted by volume.
6001
+ */
6002
+ async getTrendingMarkets(limit = 10) {
6003
+ const url = `${this.config.gammaApiUrl}/markets?active=true&limit=${limit}&order=volume24hr&ascending=false`;
6004
+ const resp = await fetch(url);
6005
+ if (!resp.ok) return [];
6006
+ const raw = await resp.json();
6007
+ return (raw || []).map((m) => this.parseGammaMarket(m));
6008
+ }
6009
+ // ============================================================
6010
+ // WALLET ADDRESS
6011
+ // ============================================================
6012
+ getWalletAddress() {
6013
+ return this.walletAddress;
6014
+ }
6015
+ /**
6016
+ * Get the effective maker address for CLOB orders.
6017
+ * In vault mode, this is the vault contract address.
6018
+ * In normal mode, this is the agent's EOA address.
6019
+ */
6020
+ getMakerAddress() {
6021
+ return this.vaultMode && this.vaultAddress ? this.vaultAddress : this.walletAddress;
6022
+ }
6023
+ /**
6024
+ * Whether the client is operating in vault mode.
6025
+ */
6026
+ get isVaultMode() {
6027
+ return this.vaultMode;
6028
+ }
6029
+ /**
6030
+ * Get the vault address (null if not in vault mode).
6031
+ */
6032
+ getVaultAddress() {
6033
+ return this.vaultAddress;
6034
+ }
6035
+ // ============================================================
6036
+ // PRIVATE HELPERS
6037
+ // ============================================================
6038
+ ensureInitialized() {
6039
+ if (!this.clobClient) {
6040
+ throw new Error("PolymarketClient not initialized. Call initialize() first.");
6041
+ }
6042
+ }
6043
+ parseGammaMarket(raw) {
6044
+ const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
6045
+ const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
6046
+ const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
6047
+ return {
6048
+ conditionId: raw.conditionId || raw.condition_id || "",
6049
+ question: raw.question || "",
6050
+ description: raw.description || "",
6051
+ category: raw.groupItemTitle || raw.category || "Other",
6052
+ outcomes,
6053
+ outcomeTokenIds,
6054
+ outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
6055
+ volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
6056
+ liquidity: parseFloat(raw.liquidity || "0"),
6057
+ endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
6058
+ active: raw.active !== false && raw.closed !== true,
6059
+ resolved: raw.resolved === true,
6060
+ winningOutcome: raw.winningOutcome,
6061
+ resolutionSource: raw.resolutionSource || void 0,
6062
+ uniqueTraders: raw.uniqueTraders || void 0
6063
+ };
6064
+ }
6065
+ parseRawFill(raw) {
6066
+ const tokenId = raw.asset_id || void 0;
6067
+ const marketConditionId = raw.market || raw.asset_id || "";
6068
+ return {
6069
+ orderId: raw.orderId || raw.order_id || "",
6070
+ tradeId: raw.id || raw.tradeId || "",
6071
+ marketConditionId,
6072
+ outcomeIndex: raw.outcome_index ?? (raw.side === "BUY" ? 0 : 1),
6073
+ side: raw.side === "BUY" || raw.side === "buy" ? "BUY" : "SELL",
6074
+ price: String(raw.price || "0"),
6075
+ size: String(raw.size || "0"),
6076
+ fee: String(raw.fee || "0"),
6077
+ timestamp: raw.timestamp || raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
6078
+ isMaker: raw.maker_order || raw.is_maker || false,
6079
+ tokenId
6080
+ };
6081
+ }
6082
+ };
6083
+
6084
+ // src/prediction/signer.ts
6085
+ var import_viem7 = require("viem");
6086
+ function tradeIdToBytes32(tradeId) {
6087
+ if (tradeId.startsWith("0x") && tradeId.length === 66) {
6088
+ return tradeId;
6089
+ }
6090
+ return (0, import_viem7.keccak256)((0, import_viem7.encodePacked)(["string"], [tradeId]));
6091
+ }
6092
+ function encodePredictionInstrument(conditionId, outcomeIndex) {
6093
+ return `${PREDICTION_INSTRUMENT_PREFIX}${conditionId}:${outcomeIndex}`;
6094
+ }
6095
+ function calculatePredictionFee(notionalUSD) {
6096
+ return notionalUSD * 20n / 10000n;
6097
+ }
6098
+
6099
+ // src/prediction/order-manager.ts
6100
+ var PredictionOrderManager = class {
6101
+ client;
6102
+ config;
6103
+ /** Recent fills tracked for recording */
6104
+ recentFills = [];
6105
+ MAX_RECENT_FILLS = 100;
6106
+ constructor(client, config) {
6107
+ this.client = client;
6108
+ this.config = config;
6109
+ }
6110
+ // ============================================================
6111
+ // ORDER PLACEMENT
6112
+ // ============================================================
6113
+ /**
6114
+ * Execute a prediction trade signal.
6115
+ * Routes to limit or market order based on signal.orderType.
6116
+ */
6117
+ async executeSignal(signal) {
6118
+ try {
6119
+ const violation = this.checkRiskLimits(signal);
6120
+ if (violation) {
6121
+ return { success: false, status: "error", error: violation };
6122
+ }
6123
+ const market = await this.client.getMarketByConditionId(signal.marketConditionId);
6124
+ if (!market) {
6125
+ return { success: false, status: "error", error: `Market not found: ${signal.marketConditionId}` };
6126
+ }
6127
+ if (!market.active) {
6128
+ return { success: false, status: "error", error: `Market is closed: ${signal.marketQuestion}` };
6129
+ }
6130
+ const tokenId = market.outcomeTokenIds[signal.outcomeIndex];
6131
+ if (!tokenId) {
6132
+ return { success: false, status: "error", error: `Invalid outcome index ${signal.outcomeIndex} for market` };
6133
+ }
6134
+ const isBuy = signal.action === "buy_yes" || signal.action === "buy_no";
6135
+ const side = isBuy ? "BUY" : "SELL";
6136
+ let result;
6137
+ if (signal.orderType === "market") {
6138
+ result = await this.client.placeMarketOrder({
6139
+ tokenId,
6140
+ amount: signal.amount,
6141
+ side
6142
+ });
6143
+ } else {
6144
+ result = await this.client.placeLimitOrder({
6145
+ tokenId,
6146
+ price: signal.limitPrice,
6147
+ size: signal.amount,
6148
+ side
6149
+ });
6150
+ }
6151
+ if (result.success) {
6152
+ console.log(
6153
+ `Prediction order placed: ${signal.action} ${signal.amount} @ $${signal.limitPrice} \u2014 "${signal.marketQuestion}" \u2014 order: ${result.orderId}`
6154
+ );
6155
+ return {
6156
+ success: true,
6157
+ orderId: result.orderId,
6158
+ status: signal.orderType === "market" ? "filled" : "resting"
6159
+ };
6160
+ }
6161
+ return { success: false, status: "error", error: "Order placement failed" };
6162
+ } catch (error) {
6163
+ const message = error instanceof Error ? error.message : String(error);
6164
+ console.error(`Prediction order failed for "${signal.marketQuestion}":`, message);
6165
+ return { success: false, status: "error", error: message };
6166
+ }
6167
+ }
6168
+ /**
6169
+ * Cancel an open order by ID.
6170
+ */
6171
+ async cancelOrder(orderId) {
6172
+ return this.client.cancelOrder(orderId);
6173
+ }
6174
+ /**
6175
+ * Cancel all open orders.
6176
+ */
6177
+ async cancelAllOrders() {
6178
+ return this.client.cancelAllOrders();
6179
+ }
6180
+ // ============================================================
6181
+ // FILL TRACKING
6182
+ // ============================================================
6183
+ /**
6184
+ * Poll for new fills since last check.
6185
+ * Returns fills that haven't been seen before.
6186
+ */
6187
+ async pollNewFills() {
6188
+ try {
6189
+ const allFills = await this.client.getTradeHistory();
6190
+ const seenIds = new Set(this.recentFills.map((f) => f.tradeId));
6191
+ const newFills = allFills.filter((f) => !seenIds.has(f.tradeId));
6192
+ for (const fill of newFills) {
6193
+ this.recentFills.push(fill);
6194
+ if (this.recentFills.length > this.MAX_RECENT_FILLS) {
6195
+ this.recentFills.shift();
6196
+ }
6197
+ }
6198
+ return newFills;
6199
+ } catch (error) {
6200
+ const message = error instanceof Error ? error.message : String(error);
6201
+ console.error("Failed to poll prediction fills:", message);
6202
+ return [];
6203
+ }
6204
+ }
6205
+ /**
6206
+ * Get recent fills (from local cache).
6207
+ */
6208
+ getRecentFills() {
6209
+ return [...this.recentFills];
6210
+ }
6211
+ // ============================================================
6212
+ // RISK CHECKS
6213
+ // ============================================================
6214
+ /**
6215
+ * Check if a signal violates risk limits.
6216
+ * Returns error string if violated, null if OK.
6217
+ */
6218
+ checkRiskLimits(signal) {
6219
+ if (signal.amount > this.config.maxNotionalUSD) {
6220
+ return `Trade amount $${signal.amount} exceeds max notional $${this.config.maxNotionalUSD}`;
6221
+ }
6222
+ if (signal.limitPrice <= 0 || signal.limitPrice >= 1) {
6223
+ return `Limit price ${signal.limitPrice} out of bounds (must be 0.01-0.99)`;
6224
+ }
6225
+ if (signal.confidence < 0.3) {
6226
+ return `Confidence ${signal.confidence} below minimum threshold (0.3)`;
6227
+ }
6228
+ if (this.config.allowedCategories && this.config.allowedCategories.length > 0) {
6229
+ }
6230
+ return null;
6231
+ }
6232
+ };
6233
+
6234
+ // src/prediction/position-manager.ts
6235
+ var PredictionPositionManager = class {
6236
+ client;
6237
+ config;
6238
+ /** Local position tracking (conditionId:outcomeIndex -> position data) */
6239
+ positions = /* @__PURE__ */ new Map();
6240
+ /** Cache TTL */
6241
+ lastPriceRefresh = 0;
6242
+ PRICE_REFRESH_MS = 1e4;
6243
+ // 10 seconds
6244
+ constructor(client, config) {
6245
+ this.client = client;
6246
+ this.config = config;
6247
+ }
6248
+ // ============================================================
6249
+ // POSITION QUERIES
6250
+ // ============================================================
6251
+ /**
6252
+ * Get all open prediction positions with current prices.
6253
+ */
6254
+ async getPositions(forceRefresh = false) {
6255
+ if (forceRefresh || Date.now() - this.lastPriceRefresh > this.PRICE_REFRESH_MS) {
6256
+ await this.refreshPrices();
6257
+ }
6258
+ return Array.from(this.positions.values()).filter((p) => p.balance > 0).map((p) => this.toExternalPosition(p));
6259
+ }
6260
+ /**
6261
+ * Get a specific position.
6262
+ */
6263
+ async getPosition(conditionId, outcomeIndex) {
6264
+ const key = `${conditionId}:${outcomeIndex}`;
6265
+ const pos = this.positions.get(key);
6266
+ if (!pos || pos.balance <= 0) return null;
6267
+ return this.toExternalPosition(pos);
6268
+ }
6269
+ /**
6270
+ * Get account summary (balances, exposure, PnL).
6271
+ */
6272
+ async getAccountSummary() {
6273
+ const positions = await this.getPositions();
6274
+ const totalExposure = positions.reduce((sum, p) => sum + p.costBasis, 0);
6275
+ const totalUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
6276
+ const openMarkets = new Set(positions.map((p) => p.marketConditionId));
6277
+ return {
6278
+ polygonUSDC: 0,
6279
+ // Filled in by runtime from on-chain balance
6280
+ baseUSDC: 0,
6281
+ // Filled in by runtime from on-chain balance
6282
+ totalExposure,
6283
+ totalUnrealizedPnl,
6284
+ openMarketCount: openMarkets.size,
6285
+ openPositionCount: positions.length,
6286
+ pendingFees: 0
6287
+ // Filled in by recorder
6288
+ };
6289
+ }
6290
+ /**
6291
+ * Get total exposure across all positions.
6292
+ */
6293
+ getTotalExposure() {
6294
+ let total = 0;
6295
+ for (const pos of this.positions.values()) {
6296
+ if (pos.balance > 0) total += pos.totalCostBasis;
6297
+ }
6298
+ return total;
6299
+ }
6300
+ /**
6301
+ * Get total unrealized PnL.
6302
+ */
6303
+ getTotalUnrealizedPnl() {
6304
+ let total = 0;
6305
+ for (const pos of this.positions.values()) {
6306
+ if (pos.balance > 0) {
6307
+ total += (pos.currentPrice - pos.averageEntryPrice) * pos.balance;
6308
+ }
6309
+ }
6310
+ return total;
6311
+ }
6312
+ // ============================================================
6313
+ // FILL PROCESSING
6314
+ // ============================================================
6315
+ /**
6316
+ * Process a fill and update position tracking.
6317
+ * Called when a new fill is detected by the order manager.
6318
+ */
6319
+ processFill(fill) {
6320
+ const key = `${fill.marketConditionId}:${fill.outcomeIndex}`;
6321
+ let pos = this.positions.get(key);
6322
+ const price = parseFloat(fill.price);
6323
+ const size = parseFloat(fill.size);
6324
+ if (!pos) {
6325
+ pos = {
6326
+ marketConditionId: fill.marketConditionId,
6327
+ outcomeIndex: fill.outcomeIndex,
6328
+ marketQuestion: fill.marketQuestion || "",
6329
+ tokenId: fill.tokenId || "",
6330
+ balance: 0,
6331
+ totalBought: 0,
6332
+ totalSold: 0,
6333
+ totalCostBasis: 0,
6334
+ totalProceeds: 0,
6335
+ averageEntryPrice: 0,
6336
+ currentPrice: price,
6337
+ category: void 0,
6338
+ endDate: void 0
6339
+ };
6340
+ this.positions.set(key, pos);
6341
+ } else if (!pos.tokenId && fill.tokenId) {
6342
+ pos.tokenId = fill.tokenId;
6343
+ }
6344
+ if (fill.side === "BUY") {
6345
+ const oldCost = pos.averageEntryPrice * pos.balance;
6346
+ const newCost = price * size;
6347
+ pos.balance += size;
6348
+ pos.totalBought += size;
6349
+ pos.totalCostBasis += newCost;
6350
+ pos.averageEntryPrice = pos.balance > 0 ? (oldCost + newCost) / pos.balance : 0;
6351
+ } else {
6352
+ pos.balance -= size;
6353
+ pos.totalSold += size;
6354
+ pos.totalProceeds += price * size;
6355
+ if (pos.balance < 0) pos.balance = 0;
6356
+ }
6357
+ }
6358
+ /**
6359
+ * Process multiple fills (batch update).
6360
+ */
6361
+ processFills(fills) {
6362
+ for (const fill of fills) {
6363
+ this.processFill(fill);
6364
+ }
6365
+ }
6366
+ // ============================================================
6367
+ // MARKET RESOLUTION
6368
+ // ============================================================
6369
+ /**
6370
+ * Mark a market as resolved. Positions settle at $0 or $1.
6371
+ */
6372
+ resolveMarket(conditionId, winningOutcome) {
6373
+ for (const [key, pos] of this.positions.entries()) {
6374
+ if (pos.marketConditionId === conditionId) {
6375
+ const isWinner = pos.outcomeIndex === winningOutcome;
6376
+ pos.currentPrice = isWinner ? 1 : 0;
6377
+ if (pos.balance > 0) {
6378
+ pos.totalProceeds += pos.currentPrice * pos.balance;
6379
+ pos.totalSold += pos.balance;
6380
+ pos.balance = 0;
6381
+ }
6382
+ }
6383
+ }
6384
+ }
6385
+ // ============================================================
6386
+ // PERSISTENCE
6387
+ // ============================================================
6388
+ /**
6389
+ * Export position state for persistence across restarts.
6390
+ */
6391
+ exportState() {
6392
+ return Array.from(this.positions.values());
6393
+ }
6394
+ /**
6395
+ * Import position state from previous session.
6396
+ */
6397
+ importState(state) {
6398
+ this.positions.clear();
6399
+ for (const pos of state) {
6400
+ const key = `${pos.marketConditionId}:${pos.outcomeIndex}`;
6401
+ this.positions.set(key, pos);
6402
+ }
6403
+ }
6404
+ // ============================================================
6405
+ // PRIVATE
6406
+ // ============================================================
6407
+ /**
6408
+ * Refresh current prices for all open positions from CLOB.
6409
+ */
6410
+ async refreshPrices() {
6411
+ const openPositions = Array.from(this.positions.values()).filter((p) => p.balance > 0);
6412
+ await Promise.all(
6413
+ openPositions.map(async (pos) => {
6414
+ try {
6415
+ if (!pos.tokenId) {
6416
+ const market = await this.client.getMarketByConditionId(pos.marketConditionId);
6417
+ if (market && market.outcomeTokenIds[pos.outcomeIndex]) {
6418
+ pos.tokenId = market.outcomeTokenIds[pos.outcomeIndex];
6419
+ }
6420
+ }
6421
+ if (pos.tokenId) {
6422
+ const price = await this.client.getMidpointPrice(pos.tokenId);
6423
+ if (price > 0) pos.currentPrice = price;
6424
+ }
6425
+ } catch {
6426
+ }
6427
+ })
6428
+ );
6429
+ this.lastPriceRefresh = Date.now();
6430
+ }
6431
+ toExternalPosition(pos) {
6432
+ const unrealizedPnl = pos.balance > 0 ? (pos.currentPrice - pos.averageEntryPrice) * pos.balance : 0;
6433
+ return {
6434
+ marketConditionId: pos.marketConditionId,
6435
+ marketQuestion: pos.marketQuestion,
6436
+ outcomeIndex: pos.outcomeIndex,
6437
+ outcomeLabel: pos.outcomeIndex === 0 ? "Yes" : "No",
6438
+ tokenId: pos.tokenId,
6439
+ balance: pos.balance,
6440
+ averageEntryPrice: pos.averageEntryPrice,
6441
+ currentPrice: pos.currentPrice,
6442
+ unrealizedPnl,
6443
+ costBasis: pos.totalCostBasis - pos.totalProceeds,
6444
+ endDate: pos.endDate,
6445
+ category: pos.category
6446
+ };
6447
+ }
6448
+ };
6449
+
6450
+ // src/prediction/recorder.ts
6451
+ var import_viem8 = require("viem");
6452
+ var import_chains5 = require("viem/chains");
6453
+ var import_accounts4 = require("viem/accounts");
6454
+ var ROUTER_ADDRESS2 = "0x1BCFa13f677fDCf697D8b7d5120f544817F1de1A";
6455
+ var ROUTER_ABI2 = [
6456
+ {
6457
+ type: "function",
6458
+ name: "recordPerpTrade",
6459
+ stateMutability: "nonpayable",
6460
+ inputs: [
6461
+ { name: "agentId", type: "uint256" },
6462
+ { name: "configHash", type: "bytes32" },
6463
+ { name: "instrument", type: "string" },
6464
+ { name: "isLong", type: "bool" },
6465
+ { name: "notionalUSD", type: "uint256" },
6466
+ { name: "feeUSD", type: "uint256" },
6467
+ { name: "fillId", type: "bytes32" }
6468
+ ],
6469
+ outputs: [{ name: "", type: "bool" }]
6470
+ }
6471
+ ];
6472
+ var MAX_RETRIES2 = 3;
6473
+ var RETRY_DELAY_MS2 = 5e3;
6474
+ var PredictionTradeRecorder = class {
6475
+ publicClient;
6476
+ walletClient;
6477
+ account;
6478
+ agentId;
6479
+ configHash;
6480
+ /** Retry queue for failed recordings */
6481
+ retryQueue = [];
6482
+ /** Set of fill IDs already recorded (or in-progress) to prevent local dups */
6483
+ recordedFills = /* @__PURE__ */ new Set();
6484
+ /** Timer for processing retry queue */
6485
+ retryTimer = null;
6486
+ /** Accumulated fees pending bridge to Base treasury (in USD, 6-decimal) */
6487
+ accumulatedFees = 0n;
6488
+ constructor(opts) {
6489
+ this.agentId = opts.agentId;
6490
+ this.configHash = opts.configHash;
6491
+ this.account = (0, import_accounts4.privateKeyToAccount)(opts.privateKey);
6492
+ const rpcUrl = opts.rpcUrl || "https://mainnet.base.org";
6493
+ const transport = (0, import_viem8.http)(rpcUrl, { timeout: 6e4 });
6494
+ this.publicClient = (0, import_viem8.createPublicClient)({
6495
+ chain: import_chains5.base,
6496
+ transport
6497
+ });
6498
+ this.walletClient = (0, import_viem8.createWalletClient)({
6499
+ chain: import_chains5.base,
6500
+ transport,
6501
+ account: this.account
6502
+ });
6503
+ this.retryTimer = setInterval(() => this.processRetryQueue(), RETRY_DELAY_MS2);
6504
+ }
6505
+ // ============================================================
6506
+ // PUBLIC API
6507
+ // ============================================================
6508
+ /**
6509
+ * Record a prediction fill on-chain.
6510
+ * Converts the Polymarket fill into recordPerpTrade params with POLY: prefix.
6511
+ */
6512
+ async recordFill(fill) {
6513
+ const fillId = tradeIdToBytes32(fill.tradeId);
6514
+ const fillIdStr = fillId.toLowerCase();
6515
+ if (this.recordedFills.has(fillIdStr)) {
6516
+ return { success: true };
6517
+ }
6518
+ this.recordedFills.add(fillIdStr);
6519
+ const notionalUSD = this.calculateNotionalUSD(fill);
6520
+ const feeUSD = calculatePredictionFee(notionalUSD);
6521
+ const params = {
6522
+ agentId: this.agentId,
6523
+ configHash: this.configHash,
6524
+ instrument: encodePredictionInstrument(fill.marketConditionId, fill.outcomeIndex),
6525
+ isLong: fill.side === "BUY",
6526
+ notionalUSD,
6527
+ feeUSD,
6528
+ fillId
6529
+ };
6530
+ this.accumulatedFees += feeUSD;
6531
+ return this.submitRecord(params);
6532
+ }
6533
+ /**
6534
+ * Update the config hash (when epoch changes).
6535
+ */
6536
+ updateConfigHash(configHash) {
6537
+ this.configHash = configHash;
6538
+ }
6539
+ /**
6540
+ * Get the number of fills pending retry.
6541
+ */
6542
+ get pendingRetries() {
6543
+ return this.retryQueue.length;
6544
+ }
6545
+ /**
6546
+ * Get the number of fills recorded (local dedup set size).
6547
+ */
6548
+ get recordedCount() {
6549
+ return this.recordedFills.size;
6550
+ }
6551
+ /**
6552
+ * Get accumulated fees pending bridge (6-decimal USD).
6553
+ */
6554
+ get pendingFees() {
6555
+ return this.accumulatedFees;
6556
+ }
6557
+ /**
6558
+ * Get accumulated fees as human-readable USD.
6559
+ */
6560
+ get pendingFeesUSD() {
6561
+ return Number(this.accumulatedFees) / 1e6;
6562
+ }
6563
+ /**
6564
+ * Reset accumulated fees (after bridge to treasury).
6565
+ */
6566
+ resetAccumulatedFees() {
6567
+ this.accumulatedFees = 0n;
6568
+ }
6569
+ /**
6570
+ * Stop the recorder (clear retry timer).
6571
+ */
6572
+ stop() {
6573
+ if (this.retryTimer) {
6574
+ clearInterval(this.retryTimer);
6575
+ this.retryTimer = null;
6576
+ }
6577
+ }
6578
+ // ============================================================
6579
+ // PRIVATE
6580
+ // ============================================================
6581
+ /**
6582
+ * Submit a recordPerpTrade transaction on Base.
6583
+ */
6584
+ async submitRecord(params) {
6585
+ try {
6586
+ const { request } = await this.publicClient.simulateContract({
6587
+ address: ROUTER_ADDRESS2,
6588
+ abi: ROUTER_ABI2,
6589
+ functionName: "recordPerpTrade",
6590
+ args: [
6591
+ params.agentId,
6592
+ params.configHash,
6593
+ params.instrument,
6594
+ params.isLong,
6595
+ params.notionalUSD,
6596
+ params.feeUSD,
6597
+ params.fillId
6598
+ ],
6599
+ account: this.account
6600
+ });
6601
+ const txHash = await this.walletClient.writeContract(request);
6602
+ const action = params.isLong ? "BUY" : "SELL";
6603
+ console.log(
6604
+ `Prediction trade recorded: ${action} ${params.instrument} $${Number(params.notionalUSD) / 1e6} \u2014 tx: ${txHash}`
6605
+ );
6606
+ return { success: true, txHash };
6607
+ } catch (error) {
6608
+ const message = error instanceof Error ? error.message : String(error);
6609
+ if (message.includes("FillAlreadyProcessed") || message.includes("already recorded")) {
6610
+ console.log(`Prediction fill already recorded on-chain: ${params.fillId}`);
6611
+ return { success: true };
6612
+ }
6613
+ console.error(`Failed to record prediction trade: ${message}`);
6614
+ this.retryQueue.push({
6615
+ params,
6616
+ retries: 0,
6617
+ lastAttempt: Date.now()
6618
+ });
6619
+ return { success: false, error: message };
6620
+ }
6621
+ }
6622
+ /**
6623
+ * Process the retry queue — attempt to re-submit failed recordings.
6624
+ */
6625
+ async processRetryQueue() {
6626
+ if (this.retryQueue.length === 0) return;
6627
+ const now = Date.now();
6628
+ const toRetry = this.retryQueue.filter(
6629
+ (item) => now - item.lastAttempt >= RETRY_DELAY_MS2
6630
+ );
6631
+ for (const item of toRetry) {
6632
+ item.retries++;
6633
+ item.lastAttempt = now;
6634
+ if (item.retries > MAX_RETRIES2) {
6635
+ console.error(
6636
+ `Prediction trade recording permanently failed after ${MAX_RETRIES2} retries: ${item.params.instrument} ${item.params.fillId}`
6637
+ );
6638
+ const idx = this.retryQueue.indexOf(item);
6639
+ if (idx >= 0) this.retryQueue.splice(idx, 1);
6640
+ continue;
6641
+ }
6642
+ console.log(
6643
+ `Retrying prediction trade recording (attempt ${item.retries}/${MAX_RETRIES2}): ${item.params.instrument}`
6644
+ );
6645
+ const result = await this.submitRecord(item.params);
6646
+ if (result.success) {
6647
+ const idx = this.retryQueue.indexOf(item);
6648
+ if (idx >= 0) this.retryQueue.splice(idx, 1);
6649
+ }
6650
+ }
6651
+ }
6652
+ // ============================================================
6653
+ // CONVERSION HELPERS
6654
+ // ============================================================
6655
+ /**
6656
+ * Calculate notional USD from a fill (6-decimal).
6657
+ * notionalUSD = price * size * 1e6
6658
+ */
6659
+ calculateNotionalUSD(fill) {
6660
+ const price = parseFloat(fill.price);
6661
+ const size = parseFloat(fill.size);
6662
+ return BigInt(Math.round(price * size * 1e6));
6663
+ }
6664
+ };
6665
+
6666
+ // src/prediction/market-browser.ts
6667
+ var MarketBrowser = class {
6668
+ gammaUrl;
6669
+ allowedCategories;
6670
+ /** Cache for market listings */
6671
+ listCache = /* @__PURE__ */ new Map();
6672
+ CACHE_TTL_MS = 6e4;
6673
+ constructor(config) {
6674
+ const merged = { ...DEFAULT_PREDICTION_CONFIG, ...config };
6675
+ this.gammaUrl = merged.gammaApiUrl;
6676
+ this.allowedCategories = merged.allowedCategories || [];
6677
+ }
6678
+ // ============================================================
6679
+ // PUBLIC API
6680
+ // ============================================================
6681
+ /**
6682
+ * Get active markets, optionally filtered by category.
6683
+ * Results are sorted by volume (highest first).
6684
+ */
6685
+ async getActiveMarkets(params) {
6686
+ const limit = params?.limit || 20;
6687
+ const offset = params?.offset || 0;
6688
+ const cacheKey = `active:${params?.category || "all"}:${limit}:${offset}`;
6689
+ const cached = this.listCache.get(cacheKey);
6690
+ if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
6691
+ return cached.data;
6692
+ }
6693
+ const query = new URLSearchParams({
6694
+ active: "true",
6695
+ closed: "false",
6696
+ limit: String(limit),
6697
+ offset: String(offset),
6698
+ order: "volume24hr",
6699
+ ascending: "false"
6700
+ });
6701
+ if (params?.category) query.set("tag", params.category);
6702
+ const markets = await this.fetchGamma(`/markets?${query.toString()}`);
6703
+ const parsed = markets.map((m) => this.parseMarket(m));
6704
+ const filtered = this.allowedCategories.length > 0 ? parsed.filter(
6705
+ (m) => this.allowedCategories.some((c) => m.category.toLowerCase().includes(c.toLowerCase()))
6706
+ ) : parsed;
6707
+ this.listCache.set(cacheKey, { data: filtered, cachedAt: Date.now() });
6708
+ return filtered;
6709
+ }
6710
+ /**
6711
+ * Search markets by query text.
6712
+ */
6713
+ async searchMarkets(query, limit = 20) {
6714
+ const params = new URLSearchParams({
6715
+ _q: query,
6716
+ limit: String(limit),
6717
+ active: "true"
6718
+ });
6719
+ const markets = await this.fetchGamma(`/markets?${params.toString()}`);
6720
+ return markets.map((m) => this.parseMarket(m));
6721
+ }
6722
+ /**
6723
+ * Get trending markets (highest 24h volume).
6724
+ */
6725
+ async getTrendingMarkets(limit = 10) {
6726
+ return this.getActiveMarkets({ limit });
6727
+ }
6728
+ /**
6729
+ * Get markets expiring soon (within N days).
6730
+ */
6731
+ async getExpiringMarkets(withinDays = 7, limit = 20) {
6732
+ const markets = await this.getActiveMarkets({ limit: 100 });
6733
+ const cutoff = Date.now() / 1e3 + withinDays * 86400;
6734
+ return markets.filter((m) => m.endDate > 0 && m.endDate < cutoff).sort((a, b) => a.endDate - b.endDate).slice(0, limit);
6735
+ }
6736
+ /**
6737
+ * Get recently resolved markets (for PnL tracking).
6738
+ */
6739
+ async getRecentlyResolved(limit = 20) {
6740
+ const params = new URLSearchParams({
6741
+ closed: "true",
6742
+ limit: String(limit),
6743
+ order: "endDate",
6744
+ ascending: "false"
6745
+ });
6746
+ const markets = await this.fetchGamma(`/markets?${params.toString()}`);
6747
+ return markets.map((m) => this.parseMarket(m)).filter((m) => m.resolved);
6748
+ }
6749
+ /**
6750
+ * Get a single market's details by condition ID.
6751
+ */
6752
+ async getMarketDetail(conditionId) {
6753
+ const markets = await this.fetchGamma(`/markets?condition_id=${conditionId}`);
6754
+ if (!markets || markets.length === 0) return null;
6755
+ return this.parseMarket(markets[0]);
6756
+ }
6757
+ /**
6758
+ * Build a concise market summary string for LLM context.
6759
+ * Keeps the prompt token count manageable.
6760
+ */
6761
+ formatMarketsForLLM(markets, maxMarkets = 15) {
6762
+ const subset = markets.slice(0, maxMarkets);
6763
+ const lines = subset.map((m, i) => {
6764
+ const prices = m.outcomePrices.map((p, j) => `${m.outcomes[j]}: ${(p * 100).toFixed(1)}%`).join(", ");
6765
+ const vol = m.volume24h >= 1e3 ? `$${(m.volume24h / 1e3).toFixed(1)}K` : `$${m.volume24h.toFixed(0)}`;
6766
+ const endStr = m.endDate > 0 ? new Date(m.endDate * 1e3).toISOString().split("T")[0] : "TBD";
6767
+ return `${i + 1}. [${m.category}] "${m.question}" \u2014 ${prices} | Vol: ${vol} | Ends: ${endStr} | ID: ${m.conditionId}`;
6768
+ });
6769
+ return lines.join("\n");
6770
+ }
6771
+ // ============================================================
6772
+ // PRIVATE
6773
+ // ============================================================
6774
+ async fetchGamma(path8) {
6775
+ const url = `${this.gammaUrl}${path8}`;
6776
+ const resp = await fetch(url);
6777
+ if (!resp.ok) {
6778
+ console.error(`Gamma API error: ${resp.status} for ${url}`);
6779
+ return [];
6780
+ }
6781
+ const data = await resp.json();
6782
+ return Array.isArray(data) ? data : [];
6783
+ }
6784
+ parseMarket(raw) {
6785
+ const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
6786
+ const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
6787
+ const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
6788
+ return {
6789
+ conditionId: raw.conditionId || raw.condition_id || "",
6790
+ question: raw.question || "",
6791
+ description: raw.description || "",
6792
+ category: raw.groupItemTitle || raw.category || "Other",
6793
+ outcomes,
6794
+ outcomeTokenIds,
6795
+ outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
6796
+ volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
6797
+ liquidity: parseFloat(raw.liquidity || "0"),
6798
+ endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
6799
+ active: raw.active !== false && raw.closed !== true,
6800
+ resolved: raw.resolved === true,
6801
+ winningOutcome: raw.winningOutcome,
6802
+ resolutionSource: raw.resolutionSource || void 0,
6803
+ uniqueTraders: raw.uniqueTraders || void 0
6804
+ };
6805
+ }
6806
+ };
6807
+
6808
+ // src/prediction/funding.ts
6809
+ var import_viem9 = require("viem");
6810
+ var import_chains6 = require("viem/chains");
6811
+ var import_accounts5 = require("viem/accounts");
6812
+ var CONTRACTS = {
6813
+ // Base
6814
+ BASE_USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
6815
+ BASE_TOKEN_MESSENGER_V2: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
6816
+ BASE_MESSAGE_TRANSMITTER_V2: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
6817
+ // Polygon
6818
+ POLYGON_USDC: POLYGON_USDC_ADDRESS,
6819
+ POLYGON_TOKEN_MESSENGER_V2,
6820
+ POLYGON_MESSAGE_TRANSMITTER_V2
6821
+ };
6822
+ var IRIS_API_URL = "https://iris-api.circle.com/v1/attestations";
6823
+ var MIN_BRIDGE_AMOUNT = 1000000n;
6824
+ var ERC20_ABI2 = (0, import_viem9.parseAbi)([
6825
+ "function approve(address spender, uint256 amount) external returns (bool)",
6826
+ "function balanceOf(address account) external view returns (uint256)",
6827
+ "function allowance(address owner, address spender) external view returns (uint256)"
6828
+ ]);
6829
+ var TOKEN_MESSENGER_V2_ABI2 = (0, import_viem9.parseAbi)([
6830
+ "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) external returns (uint64 nonce)",
6831
+ "event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)"
6832
+ ]);
6833
+ var MESSAGE_TRANSMITTER_V2_ABI2 = (0, import_viem9.parseAbi)([
6834
+ "function receiveMessage(bytes message, bytes attestation) external returns (bool success)",
6835
+ "event MessageSent(bytes message)"
6836
+ ]);
6837
+ var PredictionFunding = class {
6838
+ basePublic;
6839
+ baseWallet;
6840
+ polygonPublic;
6841
+ polygonWallet;
6842
+ account;
6843
+ /** Override recipient for deposits to Polygon (vault address in vault mode) */
6844
+ polygonRecipient;
6845
+ constructor(config) {
6846
+ this.account = (0, import_accounts5.privateKeyToAccount)(config.privateKey);
6847
+ this.polygonRecipient = config.polygonRecipient;
6848
+ const baseTransport = (0, import_viem9.http)(config.baseRpcUrl || "https://mainnet.base.org", { timeout: 6e4 });
6849
+ const polygonTransport = (0, import_viem9.http)(config.polygonRpcUrl || "https://polygon-rpc.com", { timeout: 6e4 });
6850
+ this.basePublic = (0, import_viem9.createPublicClient)({ chain: import_chains6.base, transport: baseTransport });
6851
+ this.baseWallet = (0, import_viem9.createWalletClient)({ chain: import_chains6.base, transport: baseTransport, account: this.account });
6852
+ this.polygonPublic = (0, import_viem9.createPublicClient)({ chain: import_chains6.polygon, transport: polygonTransport });
6853
+ this.polygonWallet = (0, import_viem9.createWalletClient)({ chain: import_chains6.polygon, transport: polygonTransport, account: this.account });
6854
+ }
6855
+ // ============================================================
6856
+ // BALANCE QUERIES
6857
+ // ============================================================
6858
+ /** Get USDC balance on Base (6-decimal) */
6859
+ async getBaseUSDCBalance() {
6860
+ return this.basePublic.readContract({
6861
+ address: CONTRACTS.BASE_USDC,
6862
+ abi: ERC20_ABI2,
6863
+ functionName: "balanceOf",
6864
+ args: [this.account.address]
6865
+ });
6866
+ }
6867
+ /** Get USDC balance on Polygon (6-decimal) */
6868
+ async getPolygonUSDCBalance() {
6869
+ return this.polygonPublic.readContract({
6870
+ address: CONTRACTS.POLYGON_USDC,
6871
+ abi: ERC20_ABI2,
6872
+ functionName: "balanceOf",
6873
+ args: [this.account.address]
6874
+ });
6875
+ }
6876
+ /** Get MATIC balance on Polygon for gas (wei) */
6877
+ async getPolygonGasBalance() {
6878
+ return this.polygonPublic.getBalance({ address: this.account.address });
6879
+ }
6880
+ // ============================================================
6881
+ // DEPOSIT: Base → Polygon
6882
+ // ============================================================
6883
+ /**
6884
+ * Bridge USDC from Base to Polygon via CCTP V2.
6885
+ *
6886
+ * Steps:
6887
+ * 1. Approve USDC on Base for TokenMessengerV2
6888
+ * 2. Call depositForBurn (Base → Polygon)
6889
+ * 3. Wait for Circle attestation (~1-5 minutes)
6890
+ * 4. Call receiveMessage on Polygon
6891
+ */
6892
+ async depositToPolygon(amount, onStep) {
6893
+ const startTime = Date.now();
6894
+ if (amount < MIN_BRIDGE_AMOUNT) {
6895
+ return { success: false, amount, error: "Amount below minimum (1 USDC)" };
6896
+ }
6897
+ try {
6898
+ onStep?.("approve_usdc");
6899
+ await this.approveIfNeeded(
6900
+ this.basePublic,
6901
+ this.baseWallet,
6902
+ CONTRACTS.BASE_USDC,
6903
+ CONTRACTS.BASE_TOKEN_MESSENGER_V2,
6904
+ amount
6905
+ );
6906
+ onStep?.("cctp_burn");
6907
+ const recipient = this.polygonRecipient ?? this.account.address;
6908
+ const mintRecipient = this.addressToBytes32(recipient);
6909
+ const burnHash = await this.baseWallet.writeContract({
6910
+ address: CONTRACTS.BASE_TOKEN_MESSENGER_V2,
6911
+ abi: TOKEN_MESSENGER_V2_ABI2,
6912
+ functionName: "depositForBurn",
6913
+ args: [amount, CCTP_DOMAIN_POLYGON, mintRecipient, CONTRACTS.BASE_USDC]
6914
+ });
6915
+ const burnReceipt = await this.basePublic.waitForTransactionReceipt({ hash: burnHash });
6916
+ const messageBytes = this.extractMessageFromReceipt(burnReceipt);
6917
+ if (!messageBytes) {
6918
+ return { success: false, amount, burnTxHash: burnHash, error: "Failed to extract CCTP message from burn tx" };
6919
+ }
6920
+ onStep?.("wait_attestation");
6921
+ const attestation = await this.waitForAttestation(messageBytes);
6922
+ if (!attestation) {
6923
+ return { success: false, amount, burnTxHash: burnHash, error: "Attestation timeout (15 min)" };
6924
+ }
6925
+ onStep?.("cctp_receive");
6926
+ const receiveHash = await this.polygonWallet.writeContract({
6927
+ address: CONTRACTS.POLYGON_MESSAGE_TRANSMITTER_V2,
6928
+ abi: MESSAGE_TRANSMITTER_V2_ABI2,
6929
+ functionName: "receiveMessage",
6930
+ args: [messageBytes, attestation]
6931
+ });
6932
+ await this.polygonPublic.waitForTransactionReceipt({ hash: receiveHash });
6933
+ console.log(`CCTP deposit complete: ${Number(amount) / 1e6} USDC Base \u2192 Polygon in ${((Date.now() - startTime) / 1e3).toFixed(0)}s`);
6934
+ return {
6935
+ success: true,
6936
+ amount,
6937
+ burnTxHash: burnHash,
6938
+ receiveTxHash: receiveHash,
6939
+ duration: Date.now() - startTime
6940
+ };
6941
+ } catch (error) {
6942
+ const message = error instanceof Error ? error.message : String(error);
6943
+ console.error("CCTP deposit to Polygon failed:", message);
6944
+ return { success: false, amount, error: message, duration: Date.now() - startTime };
6945
+ }
6946
+ }
6947
+ // ============================================================
6948
+ // WITHDRAW: Polygon → Base
6949
+ // ============================================================
6950
+ /**
6951
+ * Bridge USDC from Polygon back to Base via CCTP V2.
6952
+ * Used for fee collection (bridge accumulated fees to Base treasury).
6953
+ */
6954
+ async withdrawToBase(amount, onStep) {
6955
+ const startTime = Date.now();
6956
+ if (amount < MIN_BRIDGE_AMOUNT) {
6957
+ return { success: false, amount, error: "Amount below minimum (1 USDC)" };
6958
+ }
6959
+ try {
6960
+ onStep?.("approve_usdc");
6961
+ await this.approveIfNeeded(
6962
+ this.polygonPublic,
6963
+ this.polygonWallet,
6964
+ CONTRACTS.POLYGON_USDC,
6965
+ CONTRACTS.POLYGON_TOKEN_MESSENGER_V2,
6966
+ amount
6967
+ );
6968
+ onStep?.("cctp_burn");
6969
+ const mintRecipient = this.addressToBytes32(this.account.address);
6970
+ const burnHash = await this.polygonWallet.writeContract({
6971
+ address: CONTRACTS.POLYGON_TOKEN_MESSENGER_V2,
6972
+ abi: TOKEN_MESSENGER_V2_ABI2,
6973
+ functionName: "depositForBurn",
6974
+ args: [amount, CCTP_DOMAIN_BASE, mintRecipient, CONTRACTS.POLYGON_USDC]
6975
+ });
6976
+ const burnReceipt = await this.polygonPublic.waitForTransactionReceipt({ hash: burnHash });
6977
+ const messageBytes = this.extractMessageFromReceipt(burnReceipt);
6978
+ if (!messageBytes) {
6979
+ return { success: false, amount, burnTxHash: burnHash, error: "Failed to extract CCTP message" };
6980
+ }
6981
+ onStep?.("wait_attestation");
6982
+ const attestation = await this.waitForAttestation(messageBytes);
6983
+ if (!attestation) {
6984
+ return { success: false, amount, burnTxHash: burnHash, error: "Attestation timeout" };
6985
+ }
6986
+ onStep?.("cctp_receive");
6987
+ const receiveHash = await this.baseWallet.writeContract({
6988
+ address: CONTRACTS.BASE_MESSAGE_TRANSMITTER_V2,
6989
+ abi: MESSAGE_TRANSMITTER_V2_ABI2,
6990
+ functionName: "receiveMessage",
6991
+ args: [messageBytes, attestation]
6992
+ });
6993
+ await this.basePublic.waitForTransactionReceipt({ hash: receiveHash });
6994
+ console.log(`CCTP withdrawal complete: ${Number(amount) / 1e6} USDC Polygon \u2192 Base in ${((Date.now() - startTime) / 1e3).toFixed(0)}s`);
6995
+ return {
6996
+ success: true,
6997
+ amount,
6998
+ burnTxHash: burnHash,
6999
+ receiveTxHash: receiveHash,
7000
+ duration: Date.now() - startTime
7001
+ };
7002
+ } catch (error) {
7003
+ const message = error instanceof Error ? error.message : String(error);
7004
+ console.error("CCTP withdrawal to Base failed:", message);
7005
+ return { success: false, amount, error: message, duration: Date.now() - startTime };
7006
+ }
7007
+ }
7008
+ // ============================================================
7009
+ // PRIVATE HELPERS
7010
+ // ============================================================
7011
+ /** Convert address to bytes32 (left-padded) for CCTP mintRecipient */
7012
+ addressToBytes32(address) {
7013
+ return `0x000000000000000000000000${address.slice(2)}`;
7014
+ }
7015
+ /** Approve USDC spend if needed (maxUint256 pattern) */
7016
+ async approveIfNeeded(publicClient, walletClient, token, spender, amount) {
7017
+ const allowance = await publicClient.readContract({
7018
+ address: token,
7019
+ abi: ERC20_ABI2,
7020
+ functionName: "allowance",
7021
+ args: [this.account.address, spender]
7022
+ });
7023
+ if (allowance < amount) {
7024
+ const maxUint256 = 2n ** 256n - 1n;
7025
+ const hash = await walletClient.writeContract({
7026
+ address: token,
7027
+ abi: ERC20_ABI2,
7028
+ functionName: "approve",
7029
+ args: [spender, maxUint256]
7030
+ });
7031
+ await publicClient.waitForTransactionReceipt({ hash });
7032
+ }
7033
+ }
7034
+ /** Extract MessageSent bytes from a CCTP burn transaction receipt */
7035
+ extractMessageFromReceipt(receipt) {
7036
+ for (const log of receipt.logs || []) {
7037
+ try {
7038
+ if (log.topics[0] === "0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036") {
7039
+ return log.data;
7040
+ }
7041
+ } catch {
7042
+ continue;
7043
+ }
7044
+ }
7045
+ return null;
7046
+ }
7047
+ /** Wait for Circle attestation (polls Iris API) */
7048
+ async waitForAttestation(messageBytes, timeoutMs = 9e5) {
7049
+ const { keccak256: keccak2565 } = await import("viem");
7050
+ const messageHash = keccak2565(messageBytes);
7051
+ const start = Date.now();
7052
+ const pollInterval = 5e3;
7053
+ while (Date.now() - start < timeoutMs) {
7054
+ try {
7055
+ const resp = await fetch(`${IRIS_API_URL}/${messageHash}`);
7056
+ if (resp.ok) {
7057
+ const data = await resp.json();
7058
+ if (data.status === "complete" && data.attestation) {
7059
+ return data.attestation;
7060
+ }
7061
+ }
7062
+ } catch {
7063
+ }
7064
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
7065
+ }
7066
+ return null;
7067
+ }
7068
+ };
7069
+
7070
+ // src/prediction/vault-manager.ts
7071
+ var import_viem10 = require("viem");
7072
+ var import_chains7 = require("viem/chains");
7073
+ var import_accounts6 = require("viem/accounts");
7074
+ var ERC20_ABI3 = (0, import_viem10.parseAbi)([
7075
+ "function approve(address spender, uint256 amount) external returns (bool)",
7076
+ "function balanceOf(address account) external view returns (uint256)",
7077
+ "function allowance(address owner, address spender) external view returns (uint256)"
7078
+ ]);
7079
+ var VAULT_FACTORY_ABI2 = (0, import_viem10.parseAbi)([
7080
+ "function createVault(uint256 agentId, address agentWallet, uint256 seedAmount, string name, string symbol, address feeRecipient, bool isVerified, bytes agentSig) external returns (address)",
7081
+ "function vaults(uint256 agentId) external view returns (address)",
7082
+ "function isFactoryVault(address vault) external view returns (bool)",
7083
+ "function MIN_SEED_AMOUNT() external view returns (uint256)"
7084
+ ]);
7085
+ var VAULT_ABI2 = (0, import_viem10.parseAbi)([
7086
+ // ERC-4626
7087
+ "function deposit(uint256 assets, address receiver) external returns (uint256)",
7088
+ "function withdraw(uint256 assets, address receiver, address owner) external returns (uint256)",
7089
+ "function redeem(uint256 shares, address receiver, address owner) external returns (uint256)",
7090
+ "function redeemMax(address receiver, address owner) external returns (uint256)",
7091
+ "function totalAssets() external view returns (uint256)",
7092
+ "function totalSupply() external view returns (uint256)",
7093
+ "function balanceOf(address account) external view returns (uint256)",
7094
+ "function convertToAssets(uint256 shares) external view returns (uint256)",
7095
+ "function convertToShares(uint256 assets) external view returns (uint256)",
7096
+ // Vault-specific views
7097
+ "function sharePrice() external view returns (uint256)",
7098
+ "function availableTradingBalance() external view returns (uint256)",
7099
+ "function currentExposureBps() external view returns (uint256)",
7100
+ "function cachedCtfValue() external view returns (uint256)",
7101
+ "function lastCtfUpdate() external view returns (uint256)",
7102
+ "function depositCap() external view returns (uint256)",
7103
+ "function agentSeedAmount() external view returns (uint256)",
7104
+ "function circuitBreakerActive() external view returns (bool)",
7105
+ "function paused() external view returns (bool)",
7106
+ "function pendingTradingVolume() external view returns (uint256)",
7107
+ "function getTrackedTokenCount() external view returns (uint256)",
7108
+ "function agentWallet() external view returns (address)",
7109
+ "function agentId() external view returns (uint256)",
7110
+ "function isVerified() external view returns (bool)",
7111
+ "function maxExposureBps() external view returns (uint256)",
7112
+ // Keeper functions
7113
+ "function updatePositionValues(uint256[] tokenIds, uint256[] values) external",
7114
+ "function collectTradingFees(uint256 volume) external",
7115
+ // Emergency
7116
+ "function emergencyWithdraw() external returns (uint256)"
7117
+ ]);
7118
+ var PredictionVaultManager = class {
7119
+ config;
7120
+ publicClient;
7121
+ walletClient;
7122
+ account;
7123
+ /** Resolved vault address (discovered or created) */
7124
+ vaultAddress = null;
7125
+ /** NAV update timer */
7126
+ navTimer = null;
7127
+ /** Fee collection timer */
7128
+ feeTimer = null;
7129
+ /** Last known accumulated trading volume (for delta tracking) */
7130
+ lastReportedVolume = 0n;
7131
+ constructor(privateKey, config) {
7132
+ this.config = config;
7133
+ this.account = (0, import_accounts6.privateKeyToAccount)(privateKey);
7134
+ const transport = (0, import_viem10.http)(config.polygonRpcUrl, { timeout: 6e4 });
7135
+ this.publicClient = (0, import_viem10.createPublicClient)({ chain: import_chains7.polygon, transport });
7136
+ this.walletClient = (0, import_viem10.createWalletClient)({
7137
+ chain: import_chains7.polygon,
7138
+ transport,
7139
+ account: this.account
7140
+ });
7141
+ if (config.vaultAddress) {
7142
+ this.vaultAddress = config.vaultAddress;
7143
+ }
7144
+ }
7145
+ // ============================================================
7146
+ // INITIALIZATION
7147
+ // ============================================================
7148
+ /**
7149
+ * Initialize the vault manager — discover existing vault or prepare for creation.
7150
+ */
7151
+ async initialize() {
7152
+ if (!this.vaultAddress) {
7153
+ try {
7154
+ const vaultAddr = await this.publicClient.readContract({
7155
+ address: this.config.factoryAddress,
7156
+ abi: VAULT_FACTORY_ABI2,
7157
+ functionName: "vaults",
7158
+ args: [this.config.agentId]
7159
+ });
7160
+ if (vaultAddr && vaultAddr !== "0x0000000000000000000000000000000000000000") {
7161
+ this.vaultAddress = vaultAddr;
7162
+ console.log(`Discovered existing prediction vault: ${this.vaultAddress}`);
7163
+ }
7164
+ } catch {
7165
+ console.log("No existing prediction vault found \u2014 create one via createVault()");
7166
+ }
7167
+ }
7168
+ if (this.vaultAddress) {
7169
+ console.log(`Prediction vault manager initialized for vault: ${this.vaultAddress}`);
7170
+ }
7171
+ }
7172
+ /**
7173
+ * Get the vault address (null if no vault exists).
7174
+ */
7175
+ getVaultAddress() {
7176
+ return this.vaultAddress;
7177
+ }
7178
+ /**
7179
+ * Whether a vault has been created/discovered.
7180
+ */
7181
+ get hasVault() {
7182
+ return this.vaultAddress !== null;
7183
+ }
7184
+ // ============================================================
7185
+ // VAULT CREATION
7186
+ // ============================================================
7187
+ /**
7188
+ * Create a new prediction vault via PredictionVaultFactory.
7189
+ *
7190
+ * Deploys PredictionVault on Polygon with:
7191
+ * - Agent's EOA as the authorized signer for ERC-1271
7192
+ * - Seed USDC transferred from agent wallet to vault
7193
+ * - Deposit cap = seed × multiplier (10x unverified, 100x verified)
7194
+ */
7195
+ async createVault(seedAmount, name, symbol) {
7196
+ if (this.vaultAddress) {
7197
+ return { success: false, error: `Vault already exists: ${this.vaultAddress}` };
7198
+ }
7199
+ try {
7200
+ const message = (0, import_viem10.keccak256)(
7201
+ (0, import_viem10.encodePacked)(
7202
+ ["uint256", "address", "address"],
7203
+ [this.config.agentId, this.account.address, this.config.factoryAddress]
7204
+ )
7205
+ );
7206
+ const agentSig = await this.account.signMessage({ message: { raw: message } });
7207
+ await this.approveIfNeeded(
7208
+ POLYGON_USDC_ADDRESS,
7209
+ this.config.factoryAddress,
7210
+ seedAmount
7211
+ );
7212
+ const hash = await this.walletClient.writeContract({
7213
+ address: this.config.factoryAddress,
7214
+ abi: VAULT_FACTORY_ABI2,
7215
+ functionName: "createVault",
7216
+ args: [
7217
+ this.config.agentId,
7218
+ this.account.address,
7219
+ seedAmount,
7220
+ name,
7221
+ symbol,
7222
+ this.config.feeRecipient,
7223
+ this.config.isVerified ?? false,
7224
+ agentSig
7225
+ ]
7226
+ });
7227
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
7228
+ const vaultAddr = await this.publicClient.readContract({
7229
+ address: this.config.factoryAddress,
7230
+ abi: VAULT_FACTORY_ABI2,
7231
+ functionName: "vaults",
7232
+ args: [this.config.agentId]
7233
+ });
7234
+ this.vaultAddress = vaultAddr;
7235
+ console.log(`Prediction vault created: ${this.vaultAddress} (seed: ${(0, import_viem10.formatUnits)(seedAmount, 6)} USDC)`);
7236
+ return {
7237
+ success: true,
7238
+ vaultAddress: this.vaultAddress,
7239
+ txHash: hash
7240
+ };
7241
+ } catch (error) {
7242
+ const message = error instanceof Error ? error.message : String(error);
7243
+ console.error("Failed to create prediction vault:", message);
7244
+ return { success: false, error: message };
7245
+ }
7246
+ }
7247
+ // ============================================================
7248
+ // DEPOSITS & WITHDRAWALS
7249
+ // ============================================================
7250
+ /**
7251
+ * Deposit USDC into the prediction vault.
7252
+ * Returns the number of shares received.
7253
+ */
7254
+ async deposit(amount) {
7255
+ if (!this.vaultAddress) {
7256
+ return { success: false, error: "No vault exists" };
7257
+ }
7258
+ try {
7259
+ await this.approveIfNeeded(
7260
+ POLYGON_USDC_ADDRESS,
7261
+ this.vaultAddress,
7262
+ amount
7263
+ );
7264
+ const hash = await this.walletClient.writeContract({
7265
+ address: this.vaultAddress,
7266
+ abi: VAULT_ABI2,
7267
+ functionName: "deposit",
7268
+ args: [amount, this.account.address]
7269
+ });
7270
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
7271
+ const shares = await this.publicClient.readContract({
7272
+ address: this.vaultAddress,
7273
+ abi: VAULT_ABI2,
7274
+ functionName: "balanceOf",
7275
+ args: [this.account.address]
7276
+ });
7277
+ console.log(`Deposited ${(0, import_viem10.formatUnits)(amount, 6)} USDC into prediction vault`);
7278
+ return { success: true, shares, txHash: hash };
7279
+ } catch (error) {
7280
+ const message = error instanceof Error ? error.message : String(error);
7281
+ console.error("Vault deposit failed:", message);
7282
+ return { success: false, error: message };
7283
+ }
7284
+ }
7285
+ /**
7286
+ * Withdraw USDC from the prediction vault.
7287
+ * Redeems all shares and returns the USDC received (net of fees).
7288
+ */
7289
+ async withdrawAll() {
7290
+ if (!this.vaultAddress) {
7291
+ return { success: false, error: "No vault exists" };
7292
+ }
7293
+ try {
7294
+ const hash = await this.walletClient.writeContract({
7295
+ address: this.vaultAddress,
7296
+ abi: VAULT_ABI2,
7297
+ functionName: "redeemMax",
7298
+ args: [this.account.address, this.account.address]
7299
+ });
7300
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
7301
+ const balance = await this.publicClient.readContract({
7302
+ address: POLYGON_USDC_ADDRESS,
7303
+ abi: ERC20_ABI3,
7304
+ functionName: "balanceOf",
7305
+ args: [this.account.address]
7306
+ });
7307
+ console.log("Withdrew all shares from prediction vault");
7308
+ return { success: true, assets: balance, txHash: hash };
7309
+ } catch (error) {
7310
+ const message = error instanceof Error ? error.message : String(error);
7311
+ console.error("Vault withdrawal failed:", message);
7312
+ return { success: false, error: message };
7313
+ }
7314
+ }
7315
+ /**
7316
+ * Emergency withdraw with penalty.
7317
+ */
7318
+ async emergencyWithdraw() {
7319
+ if (!this.vaultAddress) {
7320
+ return { success: false, error: "No vault exists" };
7321
+ }
7322
+ try {
7323
+ const hash = await this.walletClient.writeContract({
7324
+ address: this.vaultAddress,
7325
+ abi: VAULT_ABI2,
7326
+ functionName: "emergencyWithdraw",
7327
+ args: []
7328
+ });
7329
+ await this.publicClient.waitForTransactionReceipt({ hash });
7330
+ console.log("Emergency withdrew from prediction vault (penalty applied)");
7331
+ return { success: true, txHash: hash };
7332
+ } catch (error) {
7333
+ const message = error instanceof Error ? error.message : String(error);
7334
+ return { success: false, error: message };
7335
+ }
7336
+ }
7337
+ // ============================================================
7338
+ // VAULT STATUS
7339
+ // ============================================================
7340
+ /**
7341
+ * Get comprehensive vault status.
7342
+ */
7343
+ async getVaultStatus() {
7344
+ if (!this.vaultAddress) {
7345
+ return {
7346
+ vaultAddress: "0x0000000000000000000000000000000000000000",
7347
+ hasVault: false,
7348
+ totalAssets: 0n,
7349
+ totalSupply: 0n,
7350
+ sharePrice: 0,
7351
+ usdcBalance: 0n,
7352
+ cachedCtfValue: 0n,
7353
+ currentExposureBps: 0,
7354
+ depositCap: 0n,
7355
+ seedAmount: 0n,
7356
+ circuitBreakerActive: false,
7357
+ isPaused: false,
7358
+ lastCtfUpdate: 0,
7359
+ pendingTradingVolume: 0n,
7360
+ trackedTokenCount: 0
7361
+ };
7362
+ }
7363
+ try {
7364
+ const [
7365
+ totalAssets,
7366
+ totalSupply,
7367
+ sharePrice,
7368
+ usdcBalance,
7369
+ cachedCtfValue,
7370
+ currentExposureBps,
7371
+ depositCap,
7372
+ seedAmount,
7373
+ circuitBreakerActive,
7374
+ isPaused,
7375
+ lastCtfUpdate,
7376
+ pendingTradingVolume,
7377
+ trackedTokenCount
7378
+ ] = await Promise.all([
7379
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "totalAssets" }),
7380
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "totalSupply" }),
7381
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "sharePrice" }),
7382
+ this.publicClient.readContract({ address: POLYGON_USDC_ADDRESS, abi: ERC20_ABI3, functionName: "balanceOf", args: [this.vaultAddress] }),
7383
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "cachedCtfValue" }),
7384
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "currentExposureBps" }),
7385
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "depositCap" }),
7386
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "agentSeedAmount" }),
7387
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "circuitBreakerActive" }),
7388
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "paused" }),
7389
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "lastCtfUpdate" }),
7390
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "pendingTradingVolume" }),
7391
+ this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "getTrackedTokenCount" })
7392
+ ]);
7393
+ return {
7394
+ vaultAddress: this.vaultAddress,
7395
+ hasVault: true,
7396
+ totalAssets,
7397
+ totalSupply,
7398
+ sharePrice: Number(sharePrice) / 1e6,
7399
+ usdcBalance,
7400
+ cachedCtfValue,
7401
+ currentExposureBps: Number(currentExposureBps),
7402
+ depositCap,
7403
+ seedAmount,
7404
+ circuitBreakerActive,
7405
+ isPaused,
7406
+ lastCtfUpdate: Number(lastCtfUpdate),
7407
+ pendingTradingVolume,
7408
+ trackedTokenCount: Number(trackedTokenCount)
7409
+ };
7410
+ } catch (error) {
7411
+ const message = error instanceof Error ? error.message : String(error);
7412
+ console.error("Failed to read vault status:", message);
7413
+ return {
7414
+ vaultAddress: this.vaultAddress,
7415
+ hasVault: true,
7416
+ totalAssets: 0n,
7417
+ totalSupply: 0n,
7418
+ sharePrice: 0,
7419
+ usdcBalance: 0n,
7420
+ cachedCtfValue: 0n,
7421
+ currentExposureBps: 0,
7422
+ depositCap: 0n,
7423
+ seedAmount: 0n,
7424
+ circuitBreakerActive: false,
7425
+ isPaused: false,
7426
+ lastCtfUpdate: 0,
7427
+ pendingTradingVolume: 0n,
7428
+ trackedTokenCount: 0
7429
+ };
7430
+ }
7431
+ }
7432
+ // ============================================================
7433
+ // KEEPER: NAV UPDATES
7434
+ // ============================================================
7435
+ /**
7436
+ * Update cached CTF position values (keeper function).
7437
+ * Fetches CLOB midpoint prices for all tracked tokens and updates the vault.
7438
+ *
7439
+ * @param tokenIds - CTF token IDs
7440
+ * @param values - USD values in 6-decimal (midpoint price × balance for each token)
7441
+ */
7442
+ async updatePositionValues(tokenIds, values) {
7443
+ if (!this.vaultAddress) {
7444
+ return { success: false, error: "No vault exists" };
7445
+ }
7446
+ try {
7447
+ const hash = await this.walletClient.writeContract({
7448
+ address: this.vaultAddress,
7449
+ abi: VAULT_ABI2,
7450
+ functionName: "updatePositionValues",
7451
+ args: [tokenIds, values]
7452
+ });
7453
+ await this.publicClient.waitForTransactionReceipt({ hash });
7454
+ console.log(`Updated ${tokenIds.length} position values in prediction vault`);
7455
+ return { success: true, txHash: hash };
7456
+ } catch (error) {
7457
+ const message = error instanceof Error ? error.message : String(error);
7458
+ console.error("Failed to update position values:", message);
7459
+ return { success: false, error: message };
7460
+ }
7461
+ }
7462
+ /**
7463
+ * Collect trading fees based on reported volume (keeper function).
7464
+ * Transfers 0.2% of volume as fees to PredictionFeeCollector.
7465
+ *
7466
+ * @param volume - Trading volume to report (6-decimal USDC)
7467
+ */
7468
+ async collectTradingFees(volume) {
7469
+ if (!this.vaultAddress) {
7470
+ return { success: false, error: "No vault exists" };
7471
+ }
7472
+ try {
7473
+ const hash = await this.walletClient.writeContract({
7474
+ address: this.vaultAddress,
7475
+ abi: VAULT_ABI2,
7476
+ functionName: "collectTradingFees",
7477
+ args: [volume]
7478
+ });
7479
+ await this.publicClient.waitForTransactionReceipt({ hash });
7480
+ const feeAmount = volume * 20n / 10000n;
7481
+ console.log(`Collected trading fees: ${(0, import_viem10.formatUnits)(feeAmount, 6)} USDC from ${(0, import_viem10.formatUnits)(volume, 6)} USDC volume`);
7482
+ return { success: true, txHash: hash };
7483
+ } catch (error) {
7484
+ const message = error instanceof Error ? error.message : String(error);
7485
+ console.error("Failed to collect trading fees:", message);
7486
+ return { success: false, error: message };
7487
+ }
7488
+ }
7489
+ // ============================================================
7490
+ // KEEPER LOOPS
7491
+ // ============================================================
7492
+ /**
7493
+ * Start the keeper loops (NAV updates + fee collection).
7494
+ * These run on timers and automatically update vault state.
7495
+ *
7496
+ * @param getPositionValues - Callback that returns current CTF position values
7497
+ * (token IDs and their midpoint-price USD values)
7498
+ * @param getAccumulatedVolume - Callback that returns accumulated trading volume
7499
+ * since last fee collection
7500
+ */
7501
+ startKeeperLoops(getPositionValues, getAccumulatedVolume) {
7502
+ if (!this.vaultAddress) {
7503
+ console.log("Cannot start keeper loops \u2014 no vault exists");
7504
+ return;
7505
+ }
7506
+ const navInterval = this.config.navUpdateIntervalMs ?? 15 * 60 * 1e3;
7507
+ this.navTimer = setInterval(async () => {
7508
+ try {
7509
+ const { tokenIds, values } = await getPositionValues();
7510
+ if (tokenIds.length > 0) {
7511
+ await this.updatePositionValues(tokenIds, values);
7512
+ }
7513
+ } catch (error) {
7514
+ console.error("Keeper NAV update failed:", error instanceof Error ? error.message : error);
7515
+ }
7516
+ }, navInterval);
7517
+ const feeInterval = this.config.feeCollectionIntervalMs ?? 24 * 60 * 60 * 1e3;
7518
+ this.feeTimer = setInterval(async () => {
7519
+ try {
7520
+ const currentVolume = getAccumulatedVolume();
7521
+ const volumeDelta = currentVolume - this.lastReportedVolume;
7522
+ if (volumeDelta > 0n) {
7523
+ await this.collectTradingFees(volumeDelta);
7524
+ this.lastReportedVolume = currentVolume;
7525
+ }
7526
+ } catch (error) {
7527
+ console.error("Keeper fee collection failed:", error instanceof Error ? error.message : error);
7528
+ }
7529
+ }, feeInterval);
7530
+ console.log(`Keeper loops started (NAV: every ${navInterval / 6e4}min, fees: every ${feeInterval / 36e5}h)`);
7531
+ }
7532
+ /**
7533
+ * Stop keeper loops.
7534
+ */
7535
+ stopKeeperLoops() {
7536
+ if (this.navTimer) {
7537
+ clearInterval(this.navTimer);
7538
+ this.navTimer = null;
7539
+ }
7540
+ if (this.feeTimer) {
7541
+ clearInterval(this.feeTimer);
7542
+ this.feeTimer = null;
7543
+ }
7544
+ console.log("Keeper loops stopped");
7545
+ }
7546
+ // ============================================================
7547
+ // CLEANUP
7548
+ // ============================================================
7549
+ /**
7550
+ * Stop all timers and clean up.
7551
+ */
7552
+ stop() {
7553
+ this.stopKeeperLoops();
7554
+ }
7555
+ // ============================================================
7556
+ // PRIVATE HELPERS
7557
+ // ============================================================
7558
+ /** Approve USDC spend if needed (maxUint256 pattern) */
7559
+ async approveIfNeeded(token, spender, amount) {
7560
+ const allowance = await this.publicClient.readContract({
7561
+ address: token,
7562
+ abi: ERC20_ABI3,
7563
+ functionName: "allowance",
7564
+ args: [this.account.address, spender]
7565
+ });
7566
+ if (allowance < amount) {
7567
+ const maxUint256 = 2n ** 256n - 1n;
7568
+ const hash = await this.walletClient.writeContract({
7569
+ address: token,
7570
+ abi: ERC20_ABI3,
7571
+ functionName: "approve",
7572
+ args: [spender, maxUint256]
7573
+ });
7574
+ await this.publicClient.waitForTransactionReceipt({ hash });
7575
+ }
7576
+ }
7577
+ };
7578
+
7579
+ // src/secure-env.ts
7580
+ var crypto = __toESM(require("crypto"));
7581
+ var fs = __toESM(require("fs"));
7582
+ var path = __toESM(require("path"));
7583
+ var ALGORITHM = "aes-256-gcm";
7584
+ var PBKDF2_ITERATIONS = 1e5;
7585
+ var SALT_LENGTH = 32;
7586
+ var IV_LENGTH = 16;
7587
+ var KEY_LENGTH = 32;
7588
+ var SENSITIVE_PATTERNS = [
7589
+ /PRIVATE_KEY$/i,
7590
+ /_API_KEY$/i,
7591
+ /API_KEY$/i,
7592
+ /_SECRET$/i,
7593
+ /^OPENAI_API_KEY$/i,
7594
+ /^ANTHROPIC_API_KEY$/i,
7595
+ /^GOOGLE_AI_API_KEY$/i,
7596
+ /^DEEPSEEK_API_KEY$/i,
7597
+ /^MISTRAL_API_KEY$/i,
7598
+ /^GROQ_API_KEY$/i,
7599
+ /^TOGETHER_API_KEY$/i
7600
+ ];
7601
+ function isSensitiveKey(key) {
7602
+ return SENSITIVE_PATTERNS.some((pattern) => pattern.test(key));
7603
+ }
7604
+ function deriveKey(passphrase, salt) {
7605
+ return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
7606
+ }
7607
+ function encryptValue(value, key) {
7608
+ const iv = crypto.randomBytes(IV_LENGTH);
7609
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
7610
+ let encrypted = cipher.update(value, "utf8", "hex");
7611
+ encrypted += cipher.final("hex");
7612
+ const tag = cipher.getAuthTag();
7613
+ return {
7614
+ iv: iv.toString("hex"),
7615
+ encrypted,
7616
+ tag: tag.toString("hex")
7617
+ };
7618
+ }
7619
+ function decryptValue(encrypted, key, iv, tag) {
7620
+ const decipher = crypto.createDecipheriv(
7621
+ ALGORITHM,
7622
+ key,
7623
+ Buffer.from(iv, "hex")
7624
+ );
7625
+ decipher.setAuthTag(Buffer.from(tag, "hex"));
7626
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
7627
+ decrypted += decipher.final("utf8");
7628
+ return decrypted;
7629
+ }
7630
+ function parseEnvFile(content) {
7631
+ const entries = [];
7632
+ for (const line of content.split("\n")) {
7633
+ const trimmed = line.trim();
7634
+ if (!trimmed || trimmed.startsWith("#")) continue;
7635
+ const eqIndex = trimmed.indexOf("=");
7636
+ if (eqIndex === -1) continue;
7637
+ const key = trimmed.slice(0, eqIndex).trim();
7638
+ let value = trimmed.slice(eqIndex + 1).trim();
7639
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
7640
+ value = value.slice(1, -1);
7641
+ }
7642
+ if (key && value) {
7643
+ entries.push({ key, value });
7644
+ }
7645
+ }
7646
+ return entries;
7647
+ }
7648
+ function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
7649
+ if (!fs.existsSync(envPath)) {
7650
+ throw new Error(`File not found: ${envPath}`);
7651
+ }
7652
+ const content = fs.readFileSync(envPath, "utf-8");
7653
+ const entries = parseEnvFile(content);
7654
+ if (entries.length === 0) {
7655
+ throw new Error("No environment variables found in file");
7656
+ }
7657
+ const salt = crypto.randomBytes(SALT_LENGTH);
7658
+ const key = deriveKey(passphrase, salt);
7659
+ const encryptedEntries = entries.map(({ key: envKey, value }) => {
7660
+ if (isSensitiveKey(envKey)) {
7661
+ const { iv, encrypted, tag } = encryptValue(value, key);
7662
+ return {
7663
+ key: envKey,
7664
+ value: encrypted,
7665
+ encrypted: true,
7666
+ iv,
7667
+ tag
7668
+ };
7669
+ }
7670
+ return {
7671
+ key: envKey,
7672
+ value,
7673
+ encrypted: false
7674
+ };
7675
+ });
7676
+ const encryptedEnv = {
7677
+ version: 1,
7678
+ salt: salt.toString("hex"),
7679
+ entries: encryptedEntries
7680
+ };
7681
+ const encPath = envPath + ".enc";
7682
+ fs.writeFileSync(encPath, JSON.stringify(encryptedEnv, null, 2), { mode: 384 });
7683
+ if (deleteOriginal) {
7684
+ fs.unlinkSync(envPath);
7685
+ }
7686
+ const sensitiveCount = encryptedEntries.filter((e) => e.encrypted).length;
7687
+ const plainCount = encryptedEntries.filter((e) => !e.encrypted).length;
7688
+ console.log(
7689
+ `Encrypted ${sensitiveCount} sensitive values (${plainCount} non-sensitive kept as plaintext)`
7690
+ );
7691
+ return encPath;
7692
+ }
7693
+ function decryptEnvFile(encPath, passphrase) {
7694
+ if (!fs.existsSync(encPath)) {
7695
+ throw new Error(`Encrypted env file not found: ${encPath}`);
7696
+ }
7697
+ const content = JSON.parse(fs.readFileSync(encPath, "utf-8"));
7698
+ if (content.version !== 1) {
7699
+ throw new Error(`Unsupported encrypted env version: ${content.version}`);
7700
+ }
7701
+ const salt = Buffer.from(content.salt, "hex");
7702
+ const key = deriveKey(passphrase, salt);
7703
+ const result = {};
7704
+ for (const entry of content.entries) {
7705
+ if (entry.encrypted) {
7706
+ if (!entry.iv || !entry.tag) {
7707
+ throw new Error(`Missing encryption metadata for ${entry.key}`);
7708
+ }
7709
+ try {
7710
+ result[entry.key] = decryptValue(entry.value, key, entry.iv, entry.tag);
7711
+ } catch {
7712
+ throw new Error(
7713
+ `Failed to decrypt ${entry.key}. Wrong passphrase or corrupted data.`
7714
+ );
7715
+ }
7716
+ } else {
7717
+ result[entry.key] = entry.value;
7718
+ }
7719
+ }
7720
+ return result;
7721
+ }
7722
+ function loadSecureEnv(basePath, passphrase) {
7723
+ const encPath = path.join(basePath, ".env.enc");
7724
+ const envPath = path.join(basePath, ".env");
7725
+ if (fs.existsSync(encPath)) {
7726
+ if (!passphrase) {
7727
+ passphrase = process.env.EXAGENT_PASSPHRASE;
7728
+ }
7729
+ if (!passphrase) {
7730
+ console.warn("");
7731
+ console.warn("WARNING: Found .env.enc but no passphrase provided.");
7732
+ console.warn(" Set EXAGENT_PASSPHRASE environment variable or");
7733
+ console.warn(" pass --passphrase when running the agent.");
7734
+ console.warn(" Falling back to plaintext .env file.");
7735
+ console.warn("");
7736
+ } else {
7737
+ const vars = decryptEnvFile(encPath, passphrase);
7738
+ for (const [key, value] of Object.entries(vars)) {
7739
+ process.env[key] = value;
7740
+ }
7741
+ return true;
7742
+ }
7743
+ }
7744
+ if (fs.existsSync(envPath)) {
5812
7745
  const content = fs.readFileSync(envPath, "utf-8");
5813
7746
  const entries = parseEnvFile(content);
5814
7747
  const sensitiveKeys = entries.filter(({ key }) => isSensitiveKey(key)).map(({ key }) => key);
@@ -5828,7 +7761,7 @@ function loadSecureEnv(basePath, passphrase) {
5828
7761
  }
5829
7762
 
5830
7763
  // src/index.ts
5831
- var AGENT_VERSION = "0.1.39";
7764
+ var AGENT_VERSION = "0.1.41";
5832
7765
 
5833
7766
  // src/relay.ts
5834
7767
  var RelayClient = class {
@@ -5921,10 +7854,10 @@ var RelayClient = class {
5921
7854
  * Authenticate with the relay server using wallet signature
5922
7855
  */
5923
7856
  async authenticate() {
5924
- const account = (0, import_accounts4.privateKeyToAccount)(this.config.privateKey);
7857
+ const account = (0, import_accounts7.privateKeyToAccount)(this.config.privateKey);
5925
7858
  const timestamp = Math.floor(Date.now() / 1e3);
5926
7859
  const message = `ExagentRelay:${this.config.agentId}:${timestamp}`;
5927
- const signature = await (0, import_accounts4.signMessage)({
7860
+ const signature = await (0, import_accounts7.signMessage)({
5928
7861
  message,
5929
7862
  privateKey: this.config.privateKey
5930
7863
  });
@@ -6153,6 +8086,30 @@ var AgentRuntime = class {
6153
8086
  cachedPerpMarginUsed = 0;
6154
8087
  cachedPerpLeverage = 0;
6155
8088
  cachedPerpOpenPositions = 0;
8089
+ // Prediction market components (null if prediction not enabled)
8090
+ predictionClient = null;
8091
+ predictionOrders = null;
8092
+ predictionPositions = null;
8093
+ predictionRecorder = null;
8094
+ predictionFunding = null;
8095
+ predictionBrowser = null;
8096
+ predictionStrategy = null;
8097
+ // Two-layer prediction control (mirrors perp pattern):
8098
+ // predictionConnected = Polymarket infrastructure initialized (CLOB client, recorder ready)
8099
+ // predictionTradingActive = Dedicated prediction trading cycle is mandated to run
8100
+ predictionConnected = false;
8101
+ predictionTradingActive = false;
8102
+ // Cached prediction data for synchronous heartbeat inclusion
8103
+ cachedPredictionPolygonUSDC = 0;
8104
+ cachedPredictionBaseUSDC = 0;
8105
+ cachedPredictionExposure = 0;
8106
+ cachedPredictionOpenMarkets = 0;
8107
+ cachedPredictionOpenPositions = 0;
8108
+ cachedPredictionUnrealizedPnl = 0;
8109
+ // Prediction vault manager (null if vault mode not enabled)
8110
+ predictionVaultManager = null;
8111
+ // Cached vault status for synchronous heartbeat
8112
+ cachedPredictionVaultStatus = void 0;
6156
8113
  constructor(config, options) {
6157
8114
  this.config = config;
6158
8115
  this.startInPaperMode = options?.paperMode ?? false;
@@ -6200,6 +8157,7 @@ var AgentRuntime = class {
6200
8157
  }
6201
8158
  await this.initializeVaultManager();
6202
8159
  await this.initializePerp();
8160
+ await this.initializePrediction();
6203
8161
  await this.initializeRelay();
6204
8162
  console.log("Agent initialized successfully");
6205
8163
  }
@@ -6289,10 +8247,10 @@ var AgentRuntime = class {
6289
8247
  };
6290
8248
  this.perpClient = new HyperliquidClient(config);
6291
8249
  const perpKey = perpConfig.perpRelayerKey || this.config.privateKey;
6292
- const account = (0, import_accounts5.privateKeyToAccount)(perpKey);
6293
- const walletClient = (0, import_viem7.createWalletClient)({
8250
+ const account = (0, import_accounts8.privateKeyToAccount)(perpKey);
8251
+ const walletClient = (0, import_viem11.createWalletClient)({
6294
8252
  chain: { id: 42161, name: "Arbitrum", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://arb1.arbitrum.io/rpc"] } } },
6295
- transport: (0, import_viem7.http)("https://arb1.arbitrum.io/rpc", { timeout: 6e4 }),
8253
+ transport: (0, import_viem11.http)("https://arb1.arbitrum.io/rpc", { timeout: 6e4 }),
6296
8254
  account
6297
8255
  });
6298
8256
  this.perpSigner = new HyperliquidSigner(walletClient);
@@ -6368,6 +8326,101 @@ var AgentRuntime = class {
6368
8326
  console.warn("Perp trading will be disabled for this session");
6369
8327
  }
6370
8328
  }
8329
+ /**
8330
+ * Initialize Polymarket prediction market components.
8331
+ * Only initializes if prediction is enabled in config AND risk universe >= 2.
8332
+ */
8333
+ async initializePrediction() {
8334
+ const predictionConfig = this.config.prediction;
8335
+ if (!predictionConfig?.enabled) {
8336
+ console.log("Prediction markets: Disabled");
8337
+ return;
8338
+ }
8339
+ if (this.riskUniverse < 2) {
8340
+ console.warn(`Prediction markets: Blocked by risk universe ${this.riskUniverse} (need >= 2)`);
8341
+ return;
8342
+ }
8343
+ try {
8344
+ const config = {
8345
+ enabled: true,
8346
+ clobApiUrl: predictionConfig.clobApiUrl || "https://clob.polymarket.com",
8347
+ gammaApiUrl: predictionConfig.gammaApiUrl || "https://gamma-api.polymarket.com",
8348
+ polygonRpcUrl: predictionConfig.polygonRpcUrl || "https://polygon-rpc.com",
8349
+ maxNotionalUSD: predictionConfig.maxNotionalUSD ?? 500,
8350
+ maxTotalExposureUSD: predictionConfig.maxTotalExposureUSD ?? 5e3,
8351
+ polygonAllocationUSD: predictionConfig.polygonAllocationUSD ?? 100,
8352
+ allowedCategories: predictionConfig.allowedCategories,
8353
+ feeBridgeThresholdUSD: predictionConfig.feeBridgeThresholdUSD ?? 10
8354
+ };
8355
+ const vaultConfig = predictionConfig.vault;
8356
+ let vaultOpts;
8357
+ if (vaultConfig?.enabled && vaultConfig.factoryAddress) {
8358
+ this.predictionVaultManager = new PredictionVaultManager(
8359
+ this.config.privateKey,
8360
+ {
8361
+ enabled: true,
8362
+ factoryAddress: vaultConfig.factoryAddress,
8363
+ feeCollectorAddress: vaultConfig.feeCollectorAddress || "0x0000000000000000000000000000000000000000",
8364
+ polygonRpcUrl: predictionConfig.polygonRpcUrl || "https://polygon-rpc.com",
8365
+ vaultAddress: vaultConfig.vaultAddress,
8366
+ agentId: BigInt(this.config.agentId),
8367
+ feeRecipient: vaultConfig.feeRecipient || this.client.address,
8368
+ isVerified: vaultConfig.isVerified ?? false
8369
+ }
8370
+ );
8371
+ await this.predictionVaultManager.initialize();
8372
+ if (this.predictionVaultManager.hasVault) {
8373
+ const vaultAddr = this.predictionVaultManager.getVaultAddress();
8374
+ vaultOpts = { vaultMode: true, vaultAddress: vaultAddr };
8375
+ console.log(` Vault mode: ENABLED \u2014 vault ${vaultAddr}`);
8376
+ } else {
8377
+ console.log(" Vault mode: configured but no vault exists \u2014 create one via command center");
8378
+ }
8379
+ }
8380
+ this.predictionClient = new PolymarketClient(this.config.privateKey, config, vaultOpts);
8381
+ await this.predictionClient.initialize();
8382
+ this.predictionOrders = new PredictionOrderManager(this.predictionClient, config);
8383
+ this.predictionPositions = new PredictionPositionManager(this.predictionClient, config);
8384
+ this.predictionBrowser = new MarketBrowser(config);
8385
+ const recorderKey = predictionConfig.predictionRelayerKey || this.config.privateKey;
8386
+ this.predictionRecorder = new PredictionTradeRecorder({
8387
+ privateKey: recorderKey,
8388
+ rpcUrl: this.getRpcUrl(),
8389
+ agentId: BigInt(this.config.agentId),
8390
+ configHash: this.configHash
8391
+ });
8392
+ this.predictionFunding = new PredictionFunding({
8393
+ privateKey: this.config.privateKey,
8394
+ baseRpcUrl: this.getRpcUrl(),
8395
+ polygonRpcUrl: predictionConfig.polygonRpcUrl,
8396
+ ...vaultOpts?.vaultMode && vaultOpts?.vaultAddress && {
8397
+ polygonRecipient: vaultOpts.vaultAddress
8398
+ }
8399
+ });
8400
+ this.predictionConnected = true;
8401
+ console.log(`Polymarket: Connected`);
8402
+ console.log(` CLOB: ${config.clobApiUrl}`);
8403
+ console.log(` Max notional: $${config.maxNotionalUSD}, Max exposure: $${config.maxTotalExposureUSD}`);
8404
+ if (config.allowedCategories?.length) {
8405
+ console.log(` Categories: ${config.allowedCategories.join(", ")}`);
8406
+ }
8407
+ try {
8408
+ const polygonBalance = await this.predictionFunding.getPolygonUSDCBalance();
8409
+ const polygonUSDC = Number(polygonBalance) / 1e6;
8410
+ this.cachedPredictionPolygonUSDC = polygonUSDC;
8411
+ console.log(` Polygon USDC: $${polygonUSDC.toFixed(2)}`);
8412
+ if (polygonUSDC < 1) {
8413
+ console.warn(" No USDC on Polygon \u2014 bridge required before trading");
8414
+ }
8415
+ } catch {
8416
+ console.warn(" Could not check Polygon USDC balance");
8417
+ }
8418
+ } catch (error) {
8419
+ const message = error instanceof Error ? error.message : String(error);
8420
+ console.error(`Prediction initialization failed: ${message}`);
8421
+ console.warn("Prediction market trading will be disabled for this session");
8422
+ }
8423
+ }
6371
8424
  /**
6372
8425
  * Ensure the current wallet is linked to the agent.
6373
8426
  * If the trading wallet differs from the owner, enters a recovery loop
@@ -6532,9 +8585,9 @@ var AgentRuntime = class {
6532
8585
  const message = error instanceof Error ? error.message : String(error);
6533
8586
  if (message.includes("insufficient funds") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
6534
8587
  const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
6535
- const publicClientInstance = (0, import_viem7.createPublicClient)({
6536
- chain: import_chains5.base,
6537
- transport: (0, import_viem7.http)(this.getRpcUrl(), { timeout: 6e4 })
8588
+ const publicClientInstance = (0, import_viem11.createPublicClient)({
8589
+ chain: import_chains8.base,
8590
+ transport: (0, import_viem11.http)(this.getRpcUrl(), { timeout: 6e4 })
6538
8591
  });
6539
8592
  console.log("");
6540
8593
  console.log("=== ETH NEEDED FOR GAS ===");
@@ -6902,6 +8955,224 @@ var AgentRuntime = class {
6902
8955
  }
6903
8956
  break;
6904
8957
  }
8958
+ case "enable_polymarket":
8959
+ if (this.predictionConnected) {
8960
+ this.relay?.sendCommandResult(cmd.id, true, "Polymarket already connected");
8961
+ break;
8962
+ }
8963
+ if (!this.config.prediction?.enabled) {
8964
+ this.relay?.sendCommandResult(cmd.id, false, "Prediction trading not configured in agent config");
8965
+ break;
8966
+ }
8967
+ if (this.riskUniverse < 2) {
8968
+ this.relay?.sendCommandResult(cmd.id, false, `Risk universe ${this.riskUniverse} too low (need >= 2)`);
8969
+ break;
8970
+ }
8971
+ try {
8972
+ await this.initializePrediction();
8973
+ if (this.predictionConnected) {
8974
+ this.relay?.sendCommandResult(cmd.id, true, "Polymarket connected");
8975
+ this.relay?.sendMessage(
8976
+ "system",
8977
+ "success",
8978
+ "Polymarket Enabled",
8979
+ 'Polymarket infrastructure connected. Use "Start Prediction Trading" to begin a dedicated prediction cycle.'
8980
+ );
8981
+ } else {
8982
+ this.relay?.sendCommandResult(cmd.id, false, "Failed to connect to Polymarket");
8983
+ }
8984
+ } catch (error) {
8985
+ const msg = error instanceof Error ? error.message : String(error);
8986
+ this.relay?.sendCommandResult(cmd.id, false, `Polymarket init failed: ${msg}`);
8987
+ }
8988
+ this.sendRelayStatus();
8989
+ break;
8990
+ case "disable_polymarket":
8991
+ if (!this.predictionConnected) {
8992
+ this.relay?.sendCommandResult(cmd.id, true, "Polymarket already disconnected");
8993
+ break;
8994
+ }
8995
+ this.predictionTradingActive = false;
8996
+ this.predictionConnected = false;
8997
+ this.predictionClient = null;
8998
+ this.predictionOrders = null;
8999
+ this.predictionPositions = null;
9000
+ this.predictionRecorder = null;
9001
+ this.predictionFunding = null;
9002
+ this.predictionBrowser = null;
9003
+ console.log("Polymarket disabled via command center");
9004
+ this.relay?.sendCommandResult(cmd.id, true, "Polymarket disconnected");
9005
+ this.relay?.sendMessage(
9006
+ "system",
9007
+ "info",
9008
+ "Polymarket Disabled",
9009
+ "Polymarket infrastructure disconnected."
9010
+ );
9011
+ this.sendRelayStatus();
9012
+ break;
9013
+ case "start_prediction_trading":
9014
+ if (!this.predictionConnected) {
9015
+ this.relay?.sendCommandResult(cmd.id, false, "Polymarket not connected. Enable Polymarket first.");
9016
+ break;
9017
+ }
9018
+ if (this.predictionTradingActive) {
9019
+ this.relay?.sendCommandResult(cmd.id, true, "Prediction trading already active");
9020
+ break;
9021
+ }
9022
+ this.predictionTradingActive = true;
9023
+ if (this.mode !== "trading") {
9024
+ this.mode = "trading";
9025
+ this.isRunning = true;
9026
+ }
9027
+ console.log("Prediction trading mandated via command center");
9028
+ this.relay?.sendCommandResult(cmd.id, true, "Prediction trading cycle active");
9029
+ this.relay?.sendMessage(
9030
+ "system",
9031
+ "success",
9032
+ "Prediction Trading Active",
9033
+ "Dedicated prediction trading cycle is now running every interval."
9034
+ );
9035
+ this.sendRelayStatus();
9036
+ break;
9037
+ case "stop_prediction_trading":
9038
+ if (!this.predictionTradingActive) {
9039
+ this.relay?.sendCommandResult(cmd.id, true, "Prediction trading already stopped");
9040
+ break;
9041
+ }
9042
+ this.predictionTradingActive = false;
9043
+ console.log("Prediction trading cycle stopped via command center");
9044
+ this.relay?.sendCommandResult(cmd.id, true, "Prediction trading cycle stopped");
9045
+ this.relay?.sendMessage(
9046
+ "system",
9047
+ "info",
9048
+ "Prediction Trading Stopped",
9049
+ "Dedicated prediction cycle stopped. Polymarket remains connected."
9050
+ );
9051
+ this.sendRelayStatus();
9052
+ break;
9053
+ case "update_prediction_params": {
9054
+ const predParams = cmd.params || {};
9055
+ let predUpdated = false;
9056
+ if (this.config.prediction && predParams.maxNotionalUSD !== void 0) {
9057
+ const val = Number(predParams.maxNotionalUSD);
9058
+ if (val >= 1) {
9059
+ this.config.prediction.maxNotionalUSD = val;
9060
+ predUpdated = true;
9061
+ }
9062
+ }
9063
+ if (this.config.prediction && predParams.maxTotalExposureUSD !== void 0) {
9064
+ const val = Number(predParams.maxTotalExposureUSD);
9065
+ if (val >= 1) {
9066
+ this.config.prediction.maxTotalExposureUSD = val;
9067
+ predUpdated = true;
9068
+ }
9069
+ }
9070
+ if (predUpdated) {
9071
+ this.relay?.sendCommandResult(cmd.id, true, "Prediction parameters updated");
9072
+ this.relay?.sendMessage(
9073
+ "config_updated",
9074
+ "info",
9075
+ "Prediction Params Updated",
9076
+ `Max notional: $${this.config.prediction?.maxNotionalUSD}, Max exposure: $${this.config.prediction?.maxTotalExposureUSD}`
9077
+ );
9078
+ } else {
9079
+ this.relay?.sendCommandResult(cmd.id, false, "No valid prediction parameters provided");
9080
+ }
9081
+ break;
9082
+ }
9083
+ case "create_prediction_vault": {
9084
+ if (!this.predictionVaultManager) {
9085
+ this.relay?.sendCommandResult(cmd.id, false, "Vault mode not configured. Set prediction.vault config.");
9086
+ break;
9087
+ }
9088
+ if (this.predictionVaultManager.hasVault) {
9089
+ const addr = this.predictionVaultManager.getVaultAddress();
9090
+ this.relay?.sendCommandResult(cmd.id, true, `Vault already exists: ${addr}`);
9091
+ break;
9092
+ }
9093
+ try {
9094
+ const vaultParams = cmd.params || {};
9095
+ const seedAmount = BigInt(Number(vaultParams.seedAmount || 100) * 1e6);
9096
+ const name = vaultParams.name || `Prediction Fund ${this.config.agentId}`;
9097
+ const symbol = vaultParams.symbol || `pxAGT${this.config.agentId}`;
9098
+ const result = await this.predictionVaultManager.createVault(seedAmount, name, symbol);
9099
+ if (result.success) {
9100
+ this.relay?.sendCommandResult(cmd.id, true, `Vault created: ${result.vaultAddress}`);
9101
+ this.relay?.sendMessage(
9102
+ "prediction_vault_created",
9103
+ "success",
9104
+ "Prediction Vault Created",
9105
+ `Vault deployed at ${result.vaultAddress} with ${Number(seedAmount) / 1e6} USDC seed`,
9106
+ { vaultAddress: result.vaultAddress, txHash: result.txHash }
9107
+ );
9108
+ } else {
9109
+ this.relay?.sendCommandResult(cmd.id, false, result.error || "Vault creation failed");
9110
+ }
9111
+ } catch (error) {
9112
+ const msg = error instanceof Error ? error.message : String(error);
9113
+ this.relay?.sendCommandResult(cmd.id, false, `Vault creation failed: ${msg}`);
9114
+ }
9115
+ this.sendRelayStatus();
9116
+ break;
9117
+ }
9118
+ case "deposit_prediction_vault": {
9119
+ if (!this.predictionVaultManager?.hasVault) {
9120
+ this.relay?.sendCommandResult(cmd.id, false, "No prediction vault exists");
9121
+ break;
9122
+ }
9123
+ try {
9124
+ const amount = BigInt(Number(cmd.params?.amount || 0) * 1e6);
9125
+ if (amount <= 0n) {
9126
+ this.relay?.sendCommandResult(cmd.id, false, "Amount must be positive");
9127
+ break;
9128
+ }
9129
+ const result = await this.predictionVaultManager.deposit(amount);
9130
+ if (result.success) {
9131
+ this.relay?.sendCommandResult(cmd.id, true, `Deposited ${Number(amount) / 1e6} USDC`);
9132
+ this.relay?.sendMessage(
9133
+ "prediction_vault_deposit",
9134
+ "success",
9135
+ "Vault Deposit",
9136
+ `${Number(amount) / 1e6} USDC deposited into prediction vault`,
9137
+ { amount: Number(amount) / 1e6, txHash: result.txHash }
9138
+ );
9139
+ } else {
9140
+ this.relay?.sendCommandResult(cmd.id, false, result.error || "Deposit failed");
9141
+ }
9142
+ } catch (error) {
9143
+ const msg = error instanceof Error ? error.message : String(error);
9144
+ this.relay?.sendCommandResult(cmd.id, false, `Deposit failed: ${msg}`);
9145
+ }
9146
+ this.sendRelayStatus();
9147
+ break;
9148
+ }
9149
+ case "withdraw_prediction_vault": {
9150
+ if (!this.predictionVaultManager?.hasVault) {
9151
+ this.relay?.sendCommandResult(cmd.id, false, "No prediction vault exists");
9152
+ break;
9153
+ }
9154
+ try {
9155
+ const isEmergency = cmd.params?.emergency === true;
9156
+ const result = isEmergency ? await this.predictionVaultManager.emergencyWithdraw() : await this.predictionVaultManager.withdrawAll();
9157
+ if (result.success) {
9158
+ this.relay?.sendCommandResult(cmd.id, true, isEmergency ? "Emergency withdrawn (penalty applied)" : "Withdrawn all shares");
9159
+ this.relay?.sendMessage(
9160
+ "prediction_vault_withdraw",
9161
+ isEmergency ? "warning" : "success",
9162
+ isEmergency ? "Emergency Withdrawal" : "Vault Withdrawal",
9163
+ isEmergency ? "Emergency withdrawn with penalty" : "All shares redeemed from prediction vault",
9164
+ { emergency: isEmergency, txHash: result.txHash }
9165
+ );
9166
+ } else {
9167
+ this.relay?.sendCommandResult(cmd.id, false, result.error || "Withdrawal failed");
9168
+ }
9169
+ } catch (error) {
9170
+ const msg = error instanceof Error ? error.message : String(error);
9171
+ this.relay?.sendCommandResult(cmd.id, false, `Withdrawal failed: ${msg}`);
9172
+ }
9173
+ this.sendRelayStatus();
9174
+ break;
9175
+ }
6905
9176
  case "start_paper_trading":
6906
9177
  if (this.mode === "paper") {
6907
9178
  this.relay?.sendCommandResult(cmd.id, true, "Already paper trading");
@@ -7029,7 +9300,7 @@ var AgentRuntime = class {
7029
9300
  try {
7030
9301
  const onChain = await this.client.registry.getConfigHash(BigInt(this.config.agentId));
7031
9302
  if (onChain === this.pendingConfigHash) {
7032
- this.configHash = this.pendingConfigHash;
9303
+ this.updateConfigHashEverywhere(this.pendingConfigHash);
7033
9304
  this.pendingConfigHash = null;
7034
9305
  console.log("Config verified on-chain! Your agent will now appear on the leaderboard.");
7035
9306
  this.relay?.sendMessage(
@@ -7042,6 +9313,34 @@ var AgentRuntime = class {
7042
9313
  } catch {
7043
9314
  }
7044
9315
  }
9316
+ /**
9317
+ * Periodically sync config hash from on-chain to detect external changes.
9318
+ * This catches cases where the config was changed via the website or another
9319
+ * session while this agent is running. Without this, all trades and recordings
9320
+ * fail with ConfigMismatch until the agent is restarted.
9321
+ *
9322
+ * Called from sendRelayStatus at most every 5 minutes (timestamp-throttled).
9323
+ */
9324
+ lastConfigSyncAt = 0;
9325
+ async syncConfigHashFromChain() {
9326
+ try {
9327
+ const onChain = await this.client.registry.getConfigHash(BigInt(this.config.agentId));
9328
+ if (onChain && onChain !== this.configHash && onChain !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
9329
+ console.log(`Config hash changed on-chain (external update detected): ${this.configHash} \u2192 ${onChain}`);
9330
+ this.updateConfigHashEverywhere(onChain);
9331
+ }
9332
+ } catch {
9333
+ }
9334
+ }
9335
+ /**
9336
+ * Propagate a config hash update to runtime + all consumers (recorders).
9337
+ * Ensures executor, perp recorder, and prediction recorder all stay in sync.
9338
+ */
9339
+ updateConfigHashEverywhere(newHash) {
9340
+ this.configHash = newHash;
9341
+ this.perpRecorder?.updateConfigHash(newHash);
9342
+ this.predictionRecorder?.updateConfigHash(newHash);
9343
+ }
7045
9344
  /**
7046
9345
  * Send current status to the relay
7047
9346
  */
@@ -7052,6 +9351,11 @@ var AgentRuntime = class {
7052
9351
  this.lastConfigCheckAt = Date.now();
7053
9352
  this.checkPendingConfigApproval();
7054
9353
  }
9354
+ const CONFIG_SYNC_INTERVAL_MS = 3e5;
9355
+ if (!this.pendingConfigHash && Date.now() - this.lastConfigSyncAt >= CONFIG_SYNC_INTERVAL_MS) {
9356
+ this.lastConfigSyncAt = Date.now();
9357
+ this.syncConfigHashFromChain();
9358
+ }
7055
9359
  const vaultConfig = this.config.vault || { policy: "disabled" };
7056
9360
  const status = {
7057
9361
  mode: this.mode,
@@ -7089,6 +9393,19 @@ var AgentRuntime = class {
7089
9393
  effectiveLeverage: this.cachedPerpLeverage,
7090
9394
  pendingRecords: this.perpRecorder?.pendingRetries ?? 0
7091
9395
  } : void 0,
9396
+ prediction: this.predictionConnected ? {
9397
+ enabled: true,
9398
+ trading: this.predictionTradingActive,
9399
+ polygonUSDC: this.cachedPredictionPolygonUSDC,
9400
+ baseUSDC: this.cachedPredictionBaseUSDC,
9401
+ totalExposure: this.cachedPredictionExposure,
9402
+ openMarkets: this.cachedPredictionOpenMarkets,
9403
+ openPositions: this.cachedPredictionOpenPositions,
9404
+ unrealizedPnl: this.cachedPredictionUnrealizedPnl,
9405
+ pendingFees: this.predictionRecorder?.pendingFeesUSD ?? 0,
9406
+ pendingRecords: this.predictionRecorder?.pendingRetries ?? 0,
9407
+ vault: this.cachedPredictionVaultStatus
9408
+ } : void 0,
7092
9409
  positions: this.positionTracker ? this.positionTracker.getPositionSummary(this.lastPrices) : void 0,
7093
9410
  paper: this.mode === "paper" && this.paperPortfolio ? (() => {
7094
9411
  const summary = this.paperPortfolio.getSummary(this.lastPrices);
@@ -7131,6 +9448,39 @@ var AgentRuntime = class {
7131
9448
  }).catch(() => {
7132
9449
  });
7133
9450
  }
9451
+ if (this.predictionConnected && this.predictionPositions && this.predictionFunding) {
9452
+ this.predictionPositions.getAccountSummary().then((summary) => {
9453
+ this.cachedPredictionExposure = summary.totalExposure;
9454
+ this.cachedPredictionOpenMarkets = summary.openMarketCount;
9455
+ this.cachedPredictionOpenPositions = summary.openPositionCount;
9456
+ this.cachedPredictionUnrealizedPnl = summary.totalUnrealizedPnl;
9457
+ }).catch(() => {
9458
+ });
9459
+ this.predictionFunding.getPolygonUSDCBalance().then((bal) => {
9460
+ this.cachedPredictionPolygonUSDC = Number(bal) / 1e6;
9461
+ }).catch(() => {
9462
+ });
9463
+ this.predictionFunding.getBaseUSDCBalance().then((bal) => {
9464
+ this.cachedPredictionBaseUSDC = Number(bal) / 1e6;
9465
+ }).catch(() => {
9466
+ });
9467
+ if (this.predictionVaultManager?.hasVault) {
9468
+ this.predictionVaultManager.getVaultStatus().then((vs) => {
9469
+ if (vs.hasVault) {
9470
+ this.cachedPredictionVaultStatus = {
9471
+ address: vs.vaultAddress,
9472
+ totalAssets: Number(vs.totalAssets) / 1e6,
9473
+ sharePrice: vs.sharePrice,
9474
+ cachedCtfValue: Number(vs.cachedCtfValue) / 1e6,
9475
+ currentExposureBps: vs.currentExposureBps,
9476
+ circuitBreakerActive: vs.circuitBreakerActive,
9477
+ isPaused: vs.isPaused
9478
+ };
9479
+ }
9480
+ }).catch(() => {
9481
+ });
9482
+ }
9483
+ }
7134
9484
  }
7135
9485
  /**
7136
9486
  * Run a single trading cycle
@@ -7328,6 +9678,15 @@ var AgentRuntime = class {
7328
9678
  this.relay?.sendMessage("system", "error", "Perp Cycle Error", message);
7329
9679
  }
7330
9680
  }
9681
+ if (!isPaper && this.predictionConnected && this.predictionTradingActive) {
9682
+ try {
9683
+ await this.runPredictionCycle();
9684
+ } catch (error) {
9685
+ const message = error instanceof Error ? error.message : String(error);
9686
+ console.error("Error in prediction cycle:", message);
9687
+ this.relay?.sendMessage("system", "error", "Prediction Cycle Error", message);
9688
+ }
9689
+ }
7331
9690
  this.sendRelayStatus();
7332
9691
  }
7333
9692
  /**
@@ -7388,6 +9747,101 @@ var AgentRuntime = class {
7388
9747
  }
7389
9748
  }
7390
9749
  }
9750
+ /**
9751
+ * Run a single prediction market trading cycle.
9752
+ * Fetches active markets, positions, calls prediction strategy, executes signals.
9753
+ * Fills are recorded on Base via Router.recordPerpTrade() with POLY: prefix.
9754
+ */
9755
+ async runPredictionCycle() {
9756
+ if (!this.predictionClient || !this.predictionOrders || !this.predictionPositions || !this.predictionRecorder || !this.predictionBrowser || !this.predictionConnected) return;
9757
+ const predictionConfig = this.config.prediction;
9758
+ if (!predictionConfig?.enabled) return;
9759
+ console.log(" [POLY] Running prediction cycle...");
9760
+ try {
9761
+ const newFills = await this.predictionOrders.pollNewFills();
9762
+ if (newFills.length > 0) {
9763
+ console.log(` [POLY] ${newFills.length} new fill(s) detected`);
9764
+ for (const fill of newFills) {
9765
+ this.predictionPositions.processFill(fill);
9766
+ const result = await this.predictionRecorder.recordFill(fill);
9767
+ if (result.success) {
9768
+ this.relay?.sendMessage(
9769
+ "prediction_fill",
9770
+ "success",
9771
+ "Prediction Fill",
9772
+ `${fill.side} ${fill.size} @ $${parseFloat(fill.price).toFixed(2)} \u2014 market ${fill.marketConditionId.slice(0, 12)}...`,
9773
+ { tradeId: fill.tradeId, side: fill.side, size: fill.size, price: fill.price, txHash: result.txHash }
9774
+ );
9775
+ }
9776
+ }
9777
+ }
9778
+ } catch (error) {
9779
+ const message = error instanceof Error ? error.message : String(error);
9780
+ console.warn(" [POLY] Fill polling failed:", message);
9781
+ }
9782
+ const account = await this.predictionPositions.getAccountSummary();
9783
+ const positions = await this.predictionPositions.getPositions();
9784
+ console.log(` [POLY] Polygon USDC: $${account.polygonUSDC.toFixed(2)}, Positions: ${positions.length}, Exposure: $${account.totalExposure.toFixed(2)}`);
9785
+ if (!this.predictionStrategy) {
9786
+ return;
9787
+ }
9788
+ let markets;
9789
+ try {
9790
+ const category = predictionConfig.allowedCategories?.[0];
9791
+ markets = await this.predictionBrowser.getActiveMarkets({
9792
+ limit: 50,
9793
+ category
9794
+ });
9795
+ } catch (error) {
9796
+ const message = error instanceof Error ? error.message : String(error);
9797
+ console.error(" [POLY] Market fetch error:", message);
9798
+ return;
9799
+ }
9800
+ let signals;
9801
+ try {
9802
+ signals = await this.predictionStrategy(
9803
+ markets,
9804
+ positions,
9805
+ account,
9806
+ this.llm,
9807
+ this.config
9808
+ );
9809
+ } catch (error) {
9810
+ const message = error instanceof Error ? error.message : String(error);
9811
+ console.error(" [POLY] Strategy error:", message);
9812
+ return;
9813
+ }
9814
+ console.log(` [POLY] Strategy generated ${signals.length} signals`);
9815
+ for (const signal of signals) {
9816
+ if (signal.action === "hold") continue;
9817
+ const result = await this.predictionOrders.executeSignal(signal);
9818
+ if (result.success) {
9819
+ console.log(` [POLY] Order placed: ${signal.action} "${signal.marketQuestion.slice(0, 50)}..." \u2014 ${result.status}`);
9820
+ } else {
9821
+ console.warn(` [POLY] Order failed: ${signal.action} \u2014 ${result.error}`);
9822
+ }
9823
+ }
9824
+ const pendingFees = this.predictionRecorder.pendingFeesUSD;
9825
+ const threshold = predictionConfig.feeBridgeThresholdUSD ?? 10;
9826
+ if (pendingFees >= threshold && this.predictionFunding) {
9827
+ console.log(` [POLY] Bridging $${pendingFees.toFixed(2)} in accumulated fees to Base...`);
9828
+ const feeAmount = BigInt(Math.floor(pendingFees * 1e6));
9829
+ try {
9830
+ const bridgeResult = await this.predictionFunding.withdrawToBase(feeAmount, (step) => {
9831
+ console.log(` [POLY] Fee bridge: ${step}`);
9832
+ });
9833
+ if (bridgeResult.success) {
9834
+ this.predictionRecorder.resetAccumulatedFees();
9835
+ console.log(` [POLY] Fee bridge complete: $${pendingFees.toFixed(2)} \u2192 Base`);
9836
+ } else {
9837
+ console.warn(` [POLY] Fee bridge failed: ${bridgeResult.error}`);
9838
+ }
9839
+ } catch (error) {
9840
+ const message = error instanceof Error ? error.message : String(error);
9841
+ console.warn(` [POLY] Fee bridge error: ${message}`);
9842
+ }
9843
+ }
9844
+ }
7391
9845
  /**
7392
9846
  * Check if ETH balance is below threshold and notify.
7393
9847
  * Returns true if trading should continue, false if ETH is critically low.
@@ -7439,6 +9893,14 @@ var AgentRuntime = class {
7439
9893
  if (this.perpRecorder) {
7440
9894
  this.perpRecorder.stop();
7441
9895
  }
9896
+ this.predictionConnected = false;
9897
+ this.predictionTradingActive = false;
9898
+ if (this.predictionVaultManager) {
9899
+ this.predictionVaultManager.stop();
9900
+ }
9901
+ if (this.predictionRecorder) {
9902
+ this.predictionRecorder.stop();
9903
+ }
7442
9904
  if (this.relay) {
7443
9905
  this.relay.disconnect();
7444
9906
  }
@@ -7456,8 +9918,8 @@ var AgentRuntime = class {
7456
9918
  * for Frontier agents trading outside the whitelist) stay visible.
7457
9919
  */
7458
9920
  getTokensToTrack() {
7459
- const base6 = this.config.allowedTokens || this.getDefaultTokens();
7460
- const baseSet = new Set(base6.map((t) => t.toLowerCase()));
9921
+ const base8 = this.config.allowedTokens || this.getDefaultTokens();
9922
+ const baseSet = new Set(base8.map((t) => t.toLowerCase()));
7461
9923
  const trackedPositions = this.positionTracker.getPositions();
7462
9924
  const extras = [];
7463
9925
  for (const pos of trackedPositions) {
@@ -7480,7 +9942,7 @@ var AgentRuntime = class {
7480
9942
  console.log(`Auto-tracking ${extras.length} additional token(s) from position history`);
7481
9943
  }
7482
9944
  const resolver = this.marketData.getResolver();
7483
- const allTokens = [...base6, ...extras];
9945
+ const allTokens = [...base8, ...extras];
7484
9946
  const filtered = allTokens.filter((t) => !resolver.isUnresolvable(t.toLowerCase()));
7485
9947
  const dropped = allTokens.length - filtered.length;
7486
9948
  if (dropped > 0) {
@@ -7633,7 +10095,7 @@ var AgentRuntime = class {
7633
10095
  };
7634
10096
 
7635
10097
  // src/cli.ts
7636
- var import_accounts6 = require("viem/accounts");
10098
+ var import_accounts9 = require("viem/accounts");
7637
10099
  (0, import_dotenv2.config)();
7638
10100
  var program = new import_commander.Command();
7639
10101
  program.name("exagent").description("Exagent autonomous trading agent").version("0.1.0");
@@ -7741,8 +10203,8 @@ async function checkFirstRunSetup(configPath) {
7741
10203
  console.log("");
7742
10204
  console.log("[WALLET] Generating a new wallet for your agent...");
7743
10205
  console.log("");
7744
- const generatedKey = (0, import_accounts6.generatePrivateKey)();
7745
- const account = (0, import_accounts6.privateKeyToAccount)(generatedKey);
10206
+ const generatedKey = (0, import_accounts9.generatePrivateKey)();
10207
+ const account = (0, import_accounts9.privateKeyToAccount)(generatedKey);
7746
10208
  privateKey = generatedKey;
7747
10209
  walletAddress = account.address;
7748
10210
  console.log(" New wallet created!");
@@ -7773,7 +10235,7 @@ async function checkFirstRunSetup(configPath) {
7773
10235
  process.exit(1);
7774
10236
  }
7775
10237
  try {
7776
- const account = (0, import_accounts6.privateKeyToAccount)(privateKey);
10238
+ const account = (0, import_accounts9.privateKeyToAccount)(privateKey);
7777
10239
  walletAddress = account.address;
7778
10240
  console.log("");
7779
10241
  console.log(` Wallet address: ${walletAddress}`);
@@ -8268,8 +10730,8 @@ program.command("export-key").description("Display your trading wallet private k
8268
10730
  console.error("");
8269
10731
  process.exit(1);
8270
10732
  }
8271
- const { privateKeyToAccount: privateKeyToAccount7 } = await import("viem/accounts");
8272
- const account = privateKeyToAccount7(privateKey);
10733
+ const { privateKeyToAccount: privateKeyToAccount10 } = await import("viem/accounts");
10734
+ const account = privateKeyToAccount10(privateKey);
8273
10735
  console.log("");
8274
10736
  console.log("=".repeat(60));
8275
10737
  console.log(" WALLET PRIVATE KEY EXPORT");