@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/chunk-34JBZQO6.mjs +9475 -0
- package/dist/chunk-47FQBTKE.mjs +9394 -0
- package/dist/chunk-6VSAJQU7.mjs +9394 -0
- package/dist/chunk-AEASQNGT.mjs +9475 -0
- package/dist/chunk-MVUE6EPJ.mjs +8634 -0
- package/dist/chunk-NNIBSMJQ.mjs +9333 -0
- package/dist/chunk-OBESLXRL.mjs +8634 -0
- package/dist/chunk-TE3P727T.mjs +8634 -0
- package/dist/cli.js +2669 -207
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +1063 -18
- package/dist/index.d.ts +1063 -18
- package/dist/index.js +2716 -204
- package/dist/index.mjs +27 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -45,6 +45,8 @@ __export(index_exports, {
|
|
|
45
45
|
HyperliquidWebSocket: () => HyperliquidWebSocket,
|
|
46
46
|
LLMConfigSchema: () => LLMConfigSchema,
|
|
47
47
|
LLMProviderSchema: () => LLMProviderSchema,
|
|
48
|
+
MARKET_CATEGORIES: () => MARKET_CATEGORIES,
|
|
49
|
+
MarketBrowser: () => MarketBrowser,
|
|
48
50
|
MarketDataService: () => MarketDataService,
|
|
49
51
|
MistralAdapter: () => MistralAdapter,
|
|
50
52
|
OllamaAdapter: () => OllamaAdapter,
|
|
@@ -53,8 +55,14 @@ __export(index_exports, {
|
|
|
53
55
|
PerpConfigSchema: () => PerpConfigSchema,
|
|
54
56
|
PerpOnboarding: () => PerpOnboarding,
|
|
55
57
|
PerpTradeRecorder: () => PerpTradeRecorder,
|
|
58
|
+
PolymarketClient: () => PolymarketClient,
|
|
56
59
|
PositionManager: () => PositionManager,
|
|
57
60
|
PositionTracker: () => PositionTracker,
|
|
61
|
+
PredictionConfigSchema: () => PredictionConfigSchema,
|
|
62
|
+
PredictionFunding: () => PredictionFunding,
|
|
63
|
+
PredictionOrderManager: () => PredictionOrderManager,
|
|
64
|
+
PredictionPositionManager: () => PredictionPositionManager,
|
|
65
|
+
PredictionTradeRecorder: () => PredictionTradeRecorder,
|
|
58
66
|
RelayClient: () => RelayClient,
|
|
59
67
|
RelayConfigSchema: () => RelayConfigSchema,
|
|
60
68
|
RiskManager: () => RiskManager,
|
|
@@ -66,9 +74,12 @@ __export(index_exports, {
|
|
|
66
74
|
VaultConfigSchema: () => VaultConfigSchema,
|
|
67
75
|
VaultManager: () => VaultManager,
|
|
68
76
|
VaultPolicySchema: () => VaultPolicySchema,
|
|
77
|
+
calculatePredictionFee: () => calculatePredictionFee,
|
|
69
78
|
createLLMAdapter: () => createLLMAdapter,
|
|
70
79
|
createSampleConfig: () => createSampleConfig,
|
|
80
|
+
decodePredictionInstrument: () => decodePredictionInstrument,
|
|
71
81
|
decryptEnvFile: () => decryptEnvFile,
|
|
82
|
+
encodePredictionInstrument: () => encodePredictionInstrument,
|
|
72
83
|
encryptEnvFile: () => encryptEnvFile,
|
|
73
84
|
fillHashToBytes32: () => fillHashToBytes32,
|
|
74
85
|
fillOidToBytes32: () => fillOidToBytes32,
|
|
@@ -78,6 +89,8 @@ __export(index_exports, {
|
|
|
78
89
|
loadConfig: () => loadConfig,
|
|
79
90
|
loadSecureEnv: () => loadSecureEnv,
|
|
80
91
|
loadStrategy: () => loadStrategy,
|
|
92
|
+
orderIdToBytes32: () => orderIdToBytes32,
|
|
93
|
+
tradeIdToBytes32: () => tradeIdToBytes32,
|
|
81
94
|
validateConfig: () => validateConfig,
|
|
82
95
|
validateStrategy: () => validateStrategy
|
|
83
96
|
});
|
|
@@ -85,9 +98,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
85
98
|
|
|
86
99
|
// src/runtime.ts
|
|
87
100
|
var import_sdk = require("@exagent/sdk");
|
|
88
|
-
var
|
|
89
|
-
var
|
|
90
|
-
var
|
|
101
|
+
var import_viem11 = require("viem");
|
|
102
|
+
var import_chains8 = require("viem/chains");
|
|
103
|
+
var import_accounts8 = require("viem/accounts");
|
|
91
104
|
|
|
92
105
|
// src/trading/market.ts
|
|
93
106
|
var import_viem2 = require("viem");
|
|
@@ -2001,6 +2014,49 @@ var PerpConfigSchema = import_zod.z.object({
|
|
|
2001
2014
|
/** Allowed perp instruments (e.g. ["ETH", "BTC", "SOL"]). If empty, all instruments allowed. */
|
|
2002
2015
|
allowedInstruments: import_zod.z.array(import_zod.z.string()).optional()
|
|
2003
2016
|
}).optional();
|
|
2017
|
+
var PredictionConfigSchema = import_zod.z.object({
|
|
2018
|
+
/** Enable prediction market trading */
|
|
2019
|
+
enabled: import_zod.z.boolean().default(false),
|
|
2020
|
+
/** Polymarket CLOB API URL */
|
|
2021
|
+
clobApiUrl: import_zod.z.string().url().default("https://clob.polymarket.com"),
|
|
2022
|
+
/** Gamma API URL for market discovery */
|
|
2023
|
+
gammaApiUrl: import_zod.z.string().url().default("https://gamma-api.polymarket.com"),
|
|
2024
|
+
/** Polygon RPC URL */
|
|
2025
|
+
polygonRpcUrl: import_zod.z.string().url().optional(),
|
|
2026
|
+
/** CLOB API key (L2 auth) — derived from wallet signature if not provided */
|
|
2027
|
+
clobApiKey: import_zod.z.string().optional(),
|
|
2028
|
+
/** CLOB API secret (L2 auth) */
|
|
2029
|
+
clobApiSecret: import_zod.z.string().optional(),
|
|
2030
|
+
/** CLOB API passphrase (L2 auth) */
|
|
2031
|
+
clobApiPassphrase: import_zod.z.string().optional(),
|
|
2032
|
+
/** Private key for the prediction relayer (calls recordPerpTrade on Base). Falls back to agent wallet. */
|
|
2033
|
+
predictionRelayerKey: import_zod.z.string().optional(),
|
|
2034
|
+
/** Maximum notional per trade in USD (default: 500) */
|
|
2035
|
+
maxNotionalUSD: import_zod.z.number().min(1).default(500),
|
|
2036
|
+
/** Maximum total exposure across all markets in USD (default: 5000) */
|
|
2037
|
+
maxTotalExposureUSD: import_zod.z.number().min(1).default(5e3),
|
|
2038
|
+
/** Target USDC allocation on Polygon in USD (default: 100) */
|
|
2039
|
+
polygonAllocationUSD: import_zod.z.number().min(1).default(100),
|
|
2040
|
+
/** Allowed market categories (empty = all categories) */
|
|
2041
|
+
allowedCategories: import_zod.z.array(import_zod.z.string()).optional(),
|
|
2042
|
+
/** Fee bridge threshold in USD — accumulated fees are bridged to Base when exceeded (default: 10) */
|
|
2043
|
+
feeBridgeThresholdUSD: import_zod.z.number().min(1).default(10),
|
|
2044
|
+
/** Prediction vault configuration (ERC-4626 + ERC-1271 on Polygon) */
|
|
2045
|
+
vault: import_zod.z.object({
|
|
2046
|
+
/** Enable vault mode for prediction trading */
|
|
2047
|
+
enabled: import_zod.z.boolean().default(false),
|
|
2048
|
+
/** PredictionVaultFactory address on Polygon */
|
|
2049
|
+
factoryAddress: import_zod.z.string(),
|
|
2050
|
+
/** PredictionFeeCollector address on Polygon */
|
|
2051
|
+
feeCollectorAddress: import_zod.z.string().optional(),
|
|
2052
|
+
/** Existing vault address (if already created) */
|
|
2053
|
+
vaultAddress: import_zod.z.string().optional(),
|
|
2054
|
+
/** Agent's fee recipient address */
|
|
2055
|
+
feeRecipient: import_zod.z.string().optional(),
|
|
2056
|
+
/** Whether agent is verified (higher deposit cap) */
|
|
2057
|
+
isVerified: import_zod.z.boolean().default(false)
|
|
2058
|
+
}).optional()
|
|
2059
|
+
}).optional();
|
|
2004
2060
|
var AgentConfigSchema = import_zod.z.object({
|
|
2005
2061
|
// Identity (from on-chain registration)
|
|
2006
2062
|
agentId: import_zod.z.union([import_zod.z.number().positive(), import_zod.z.string()]),
|
|
@@ -2020,6 +2076,8 @@ var AgentConfigSchema = import_zod.z.object({
|
|
|
2020
2076
|
relay: RelayConfigSchema,
|
|
2021
2077
|
// Perp trading configuration (Hyperliquid)
|
|
2022
2078
|
perp: PerpConfigSchema,
|
|
2079
|
+
// Prediction market configuration (Polymarket)
|
|
2080
|
+
prediction: PredictionConfigSchema,
|
|
2023
2081
|
// Allowed tokens (addresses)
|
|
2024
2082
|
allowedTokens: import_zod.z.array(import_zod.z.string()).optional()
|
|
2025
2083
|
});
|
|
@@ -2608,13 +2666,6 @@ var VAULT_FACTORY_ABI = [
|
|
|
2608
2666
|
outputs: [{ type: "address" }],
|
|
2609
2667
|
stateMutability: "view"
|
|
2610
2668
|
},
|
|
2611
|
-
{
|
|
2612
|
-
type: "function",
|
|
2613
|
-
name: "canCreateVault",
|
|
2614
|
-
inputs: [{ name: "creator", type: "address" }],
|
|
2615
|
-
outputs: [{ name: "canCreate", type: "bool" }, { name: "reason", type: "string" }],
|
|
2616
|
-
stateMutability: "view"
|
|
2617
|
-
},
|
|
2618
2669
|
{
|
|
2619
2670
|
type: "function",
|
|
2620
2671
|
name: "createVault",
|
|
@@ -2631,10 +2682,17 @@ var VAULT_FACTORY_ABI = [
|
|
|
2631
2682
|
},
|
|
2632
2683
|
{
|
|
2633
2684
|
type: "function",
|
|
2634
|
-
name: "
|
|
2685
|
+
name: "MIN_SEED_AMOUNT",
|
|
2635
2686
|
inputs: [],
|
|
2636
2687
|
outputs: [{ type: "uint256" }],
|
|
2637
2688
|
stateMutability: "view"
|
|
2689
|
+
},
|
|
2690
|
+
{
|
|
2691
|
+
type: "function",
|
|
2692
|
+
name: "allowedAssets",
|
|
2693
|
+
inputs: [{ name: "asset", type: "address" }],
|
|
2694
|
+
outputs: [{ type: "bool" }],
|
|
2695
|
+
stateMutability: "view"
|
|
2638
2696
|
}
|
|
2639
2697
|
];
|
|
2640
2698
|
var VAULT_ABI = [
|
|
@@ -2732,23 +2790,34 @@ var VaultManager = class {
|
|
|
2732
2790
|
} catch {
|
|
2733
2791
|
}
|
|
2734
2792
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2793
|
+
let canCreate = true;
|
|
2794
|
+
let cannotCreateReason = null;
|
|
2795
|
+
if (hasVault) {
|
|
2796
|
+
canCreate = false;
|
|
2797
|
+
cannotCreateReason = "Vault already exists for this agent";
|
|
2798
|
+
} else {
|
|
2799
|
+
try {
|
|
2800
|
+
const isAssetAllowed = await this.publicClient.readContract({
|
|
2801
|
+
address: this.addresses.vaultFactory,
|
|
2802
|
+
abi: VAULT_FACTORY_ABI,
|
|
2803
|
+
functionName: "allowedAssets",
|
|
2804
|
+
args: [this.addresses.usdc]
|
|
2805
|
+
});
|
|
2806
|
+
if (!isAssetAllowed) {
|
|
2807
|
+
canCreate = false;
|
|
2808
|
+
cannotCreateReason = "USDC is not an allowed asset on the vault factory";
|
|
2809
|
+
}
|
|
2810
|
+
} catch {
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2744
2813
|
return {
|
|
2745
2814
|
hasVault,
|
|
2746
2815
|
vaultAddress,
|
|
2747
2816
|
totalAssets,
|
|
2748
|
-
canCreateVault:
|
|
2749
|
-
cannotCreateReason
|
|
2750
|
-
requirementsMet:
|
|
2751
|
-
requirements
|
|
2817
|
+
canCreateVault: canCreate,
|
|
2818
|
+
cannotCreateReason,
|
|
2819
|
+
requirementsMet: canCreate,
|
|
2820
|
+
requirements: { isBypassed: true }
|
|
2752
2821
|
};
|
|
2753
2822
|
}
|
|
2754
2823
|
/**
|
|
@@ -2796,11 +2865,67 @@ var VaultManager = class {
|
|
|
2796
2865
|
if (existingVault) {
|
|
2797
2866
|
return { success: false, error: "Vault already exists", vaultAddress: existingVault };
|
|
2798
2867
|
}
|
|
2799
|
-
const status = await this.getVaultStatus();
|
|
2800
|
-
if (!status.canCreateVault) {
|
|
2801
|
-
return { success: false, error: status.cannotCreateReason || "Requirements not met" };
|
|
2802
|
-
}
|
|
2803
2868
|
const seed = seedAmount || BigInt(1e8);
|
|
2869
|
+
let usdcBalance;
|
|
2870
|
+
try {
|
|
2871
|
+
usdcBalance = await this.publicClient.readContract({
|
|
2872
|
+
address: this.addresses.usdc,
|
|
2873
|
+
abi: import_viem3.erc20Abi,
|
|
2874
|
+
functionName: "balanceOf",
|
|
2875
|
+
args: [this.account.address]
|
|
2876
|
+
});
|
|
2877
|
+
} catch {
|
|
2878
|
+
return { success: false, error: "Failed to read USDC balance" };
|
|
2879
|
+
}
|
|
2880
|
+
if (usdcBalance < seed) {
|
|
2881
|
+
const needed = Number(seed) / 1e6;
|
|
2882
|
+
const have = Number(usdcBalance) / 1e6;
|
|
2883
|
+
return {
|
|
2884
|
+
success: false,
|
|
2885
|
+
error: `Insufficient USDC: need ${needed} USDC seed but wallet has ${have} USDC. Fund ${this.account.address} with at least ${needed} USDC and retry.`
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
try {
|
|
2889
|
+
const isAllowed = await this.publicClient.readContract({
|
|
2890
|
+
address: this.addresses.vaultFactory,
|
|
2891
|
+
abi: VAULT_FACTORY_ABI,
|
|
2892
|
+
functionName: "allowedAssets",
|
|
2893
|
+
args: [this.addresses.usdc]
|
|
2894
|
+
});
|
|
2895
|
+
if (!isAllowed) {
|
|
2896
|
+
return { success: false, error: "USDC is not whitelisted on the vault factory. Contact protocol admin." };
|
|
2897
|
+
}
|
|
2898
|
+
} catch {
|
|
2899
|
+
}
|
|
2900
|
+
try {
|
|
2901
|
+
const currentAllowance = await this.publicClient.readContract({
|
|
2902
|
+
address: this.addresses.usdc,
|
|
2903
|
+
abi: import_viem3.erc20Abi,
|
|
2904
|
+
functionName: "allowance",
|
|
2905
|
+
args: [this.account.address, this.addresses.vaultFactory]
|
|
2906
|
+
});
|
|
2907
|
+
if (currentAllowance < seed) {
|
|
2908
|
+
console.log(`Approving ${Number(seed) / 1e6} USDC to VaultFactory...`);
|
|
2909
|
+
const approveHash = await this.walletClient.writeContract({
|
|
2910
|
+
address: this.addresses.usdc,
|
|
2911
|
+
abi: import_viem3.erc20Abi,
|
|
2912
|
+
functionName: "approve",
|
|
2913
|
+
args: [this.addresses.vaultFactory, seed],
|
|
2914
|
+
chain: import_chains2.base,
|
|
2915
|
+
account: this.account
|
|
2916
|
+
});
|
|
2917
|
+
const approveReceipt = await this.publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
2918
|
+
if (approveReceipt.status !== "success") {
|
|
2919
|
+
return { success: false, error: "USDC approval transaction failed" };
|
|
2920
|
+
}
|
|
2921
|
+
console.log("USDC approved successfully");
|
|
2922
|
+
}
|
|
2923
|
+
} catch (error) {
|
|
2924
|
+
return {
|
|
2925
|
+
success: false,
|
|
2926
|
+
error: `USDC approval failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2804
2929
|
const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
|
|
2805
2930
|
const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
|
|
2806
2931
|
const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
|
|
@@ -2822,8 +2947,9 @@ var VaultManager = class {
|
|
|
2822
2947
|
});
|
|
2823
2948
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
2824
2949
|
if (receipt.status !== "success") {
|
|
2825
|
-
return { success: false, error: "
|
|
2950
|
+
return { success: false, error: "createVault transaction reverted on-chain", txHash: hash };
|
|
2826
2951
|
}
|
|
2952
|
+
this.lastVaultCheck = 0;
|
|
2827
2953
|
const vaultAddress = await this.getVaultAddress();
|
|
2828
2954
|
this.cachedVaultAddress = vaultAddress;
|
|
2829
2955
|
return {
|
|
@@ -2832,10 +2958,23 @@ var VaultManager = class {
|
|
|
2832
2958
|
txHash: hash
|
|
2833
2959
|
};
|
|
2834
2960
|
} catch (error) {
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
}
|
|
2961
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
2962
|
+
if (msg.includes("NotAgentOwner")) {
|
|
2963
|
+
return { success: false, error: "This wallet is not the owner of the agent. Only the agent owner can create a fund." };
|
|
2964
|
+
}
|
|
2965
|
+
if (msg.includes("InsufficientStake")) {
|
|
2966
|
+
return { success: false, error: "Staking requirement not met. This should not happen with StakingStub \u2014 contact protocol admin." };
|
|
2967
|
+
}
|
|
2968
|
+
if (msg.includes("FrontierCannotCreateVault")) {
|
|
2969
|
+
return { success: false, error: "Frontier-risk agents cannot create funds. Change the agent risk universe first." };
|
|
2970
|
+
}
|
|
2971
|
+
if (msg.includes("VaultAlreadyExists")) {
|
|
2972
|
+
return { success: false, error: "A fund already exists for this agent and asset." };
|
|
2973
|
+
}
|
|
2974
|
+
if (msg.includes("InsufficientSeed")) {
|
|
2975
|
+
return { success: false, error: `Seed amount too low. Minimum is 100 USDC.` };
|
|
2976
|
+
}
|
|
2977
|
+
return { success: false, error: msg };
|
|
2839
2978
|
}
|
|
2840
2979
|
}
|
|
2841
2980
|
/**
|
|
@@ -4987,184 +5126,2040 @@ var CORE_DEPOSIT_WALLET_ABI = (0, import_viem6.parseAbi)([
|
|
|
4987
5126
|
"function deposit(uint256 amount, uint32 destinationDex) external"
|
|
4988
5127
|
]);
|
|
4989
5128
|
|
|
4990
|
-
// src/
|
|
4991
|
-
var
|
|
4992
|
-
|
|
4993
|
-
|
|
5129
|
+
// src/prediction/types.ts
|
|
5130
|
+
var DEFAULT_PREDICTION_CONFIG = {
|
|
5131
|
+
clobApiUrl: "https://clob.polymarket.com",
|
|
5132
|
+
gammaApiUrl: "https://gamma-api.polymarket.com",
|
|
5133
|
+
polygonRpcUrl: "https://polygon-rpc.com",
|
|
5134
|
+
maxNotionalUSD: 1e3,
|
|
5135
|
+
maxTotalExposureUSD: 5e3,
|
|
5136
|
+
polygonAllocationUSD: 100,
|
|
5137
|
+
feeBridgeThresholdUSD: 10
|
|
5138
|
+
};
|
|
5139
|
+
var POLYGON_CHAIN_ID = 137;
|
|
5140
|
+
var POLYMARKET_SIGNATURE_TYPE_EOA = 0;
|
|
5141
|
+
var POLYMARKET_SIGNATURE_TYPE_GNOSIS_SAFE = 1;
|
|
5142
|
+
var POLYGON_USDC_ADDRESS = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359";
|
|
5143
|
+
var POLYGON_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
|
|
5144
|
+
var POLYGON_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
|
|
5145
|
+
var CCTP_DOMAIN_BASE = 6;
|
|
5146
|
+
var CCTP_DOMAIN_POLYGON = 7;
|
|
5147
|
+
var PREDICTION_INSTRUMENT_PREFIX = "POLY:";
|
|
5148
|
+
|
|
5149
|
+
// src/prediction/client.ts
|
|
5150
|
+
var import_clob_client = require("@polymarket/clob-client");
|
|
5151
|
+
var import_ethers = require("ethers");
|
|
5152
|
+
var PolymarketClient = class {
|
|
4994
5153
|
config;
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
lastPrices = {};
|
|
5014
|
-
processAlive = true;
|
|
5015
|
-
riskUniverse = 0;
|
|
5016
|
-
allowedTokens = /* @__PURE__ */ new Set();
|
|
5017
|
-
strategyContext;
|
|
5018
|
-
positionTracker;
|
|
5019
|
-
// Paper trading components (null when not in paper mode)
|
|
5020
|
-
paperExecutor = null;
|
|
5021
|
-
paperPortfolio = null;
|
|
5022
|
-
/** Whether agent was started directly in paper mode via CLI */
|
|
5023
|
-
startInPaperMode = false;
|
|
5024
|
-
// Perp trading components (null if perp not enabled)
|
|
5025
|
-
perpClient = null;
|
|
5026
|
-
perpSigner = null;
|
|
5027
|
-
perpOrders = null;
|
|
5028
|
-
perpPositions = null;
|
|
5029
|
-
perpWebSocket = null;
|
|
5030
|
-
perpRecorder = null;
|
|
5031
|
-
perpOnboarding = null;
|
|
5032
|
-
perpStrategy = null;
|
|
5033
|
-
// Two-layer perp control:
|
|
5034
|
-
// perpConnected = Hyperliquid infrastructure is initialized (WS, signer, recorder ready)
|
|
5035
|
-
// perpTradingActive = Dedicated perp trading cycle is mandated to run
|
|
5036
|
-
// When perpConnected && !perpTradingActive: agent's strategy can optionally return perp signals
|
|
5037
|
-
// When perpConnected && perpTradingActive: dedicated runPerpCycle() runs every interval
|
|
5038
|
-
perpConnected = false;
|
|
5039
|
-
perpTradingActive = false;
|
|
5040
|
-
// Cached perp account data for synchronous heartbeat inclusion (refreshed async)
|
|
5041
|
-
cachedPerpEquity = 0;
|
|
5042
|
-
cachedPerpUnrealizedPnl = 0;
|
|
5043
|
-
cachedPerpMarginUsed = 0;
|
|
5044
|
-
cachedPerpLeverage = 0;
|
|
5045
|
-
cachedPerpOpenPositions = 0;
|
|
5046
|
-
constructor(config, options) {
|
|
5047
|
-
this.config = config;
|
|
5048
|
-
this.startInPaperMode = options?.paperMode ?? false;
|
|
5049
|
-
this._paperInitialBalances = options?.paperBalances;
|
|
5154
|
+
clobClient = null;
|
|
5155
|
+
apiCreds = null;
|
|
5156
|
+
signer;
|
|
5157
|
+
walletAddress;
|
|
5158
|
+
/** Vault mode: when true, orders use maker=vaultAddress with POLY_GNOSIS_SAFE signature type */
|
|
5159
|
+
vaultMode;
|
|
5160
|
+
/** Vault contract address (only set in vault mode) */
|
|
5161
|
+
vaultAddress;
|
|
5162
|
+
/** Cache for Gamma API market data (conditionId -> market) */
|
|
5163
|
+
marketCache = /* @__PURE__ */ new Map();
|
|
5164
|
+
CACHE_TTL_MS = 6e4;
|
|
5165
|
+
// 60 seconds
|
|
5166
|
+
constructor(privateKey, config, vaultOpts) {
|
|
5167
|
+
this.config = { ...DEFAULT_PREDICTION_CONFIG, ...config };
|
|
5168
|
+
this.signer = new import_ethers.Wallet(privateKey);
|
|
5169
|
+
this.walletAddress = this.signer.address;
|
|
5170
|
+
this.vaultMode = vaultOpts?.vaultMode ?? false;
|
|
5171
|
+
this.vaultAddress = vaultOpts?.vaultAddress;
|
|
5050
5172
|
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5173
|
+
// ============================================================
|
|
5174
|
+
// INITIALIZATION
|
|
5175
|
+
// ============================================================
|
|
5053
5176
|
/**
|
|
5054
|
-
* Initialize the
|
|
5177
|
+
* Initialize the CLOB client with L2 API credentials.
|
|
5178
|
+
* Must be called once before placing orders.
|
|
5055
5179
|
*/
|
|
5056
5180
|
async initialize() {
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
const
|
|
5064
|
-
|
|
5065
|
-
|
|
5181
|
+
const initClient = new import_clob_client.ClobClient(
|
|
5182
|
+
this.config.clobApiUrl,
|
|
5183
|
+
POLYGON_CHAIN_ID,
|
|
5184
|
+
this.signer
|
|
5185
|
+
);
|
|
5186
|
+
this.apiCreds = await initClient.createOrDeriveApiKey();
|
|
5187
|
+
const signatureType = this.vaultMode ? POLYMARKET_SIGNATURE_TYPE_GNOSIS_SAFE : POLYMARKET_SIGNATURE_TYPE_EOA;
|
|
5188
|
+
this.clobClient = new import_clob_client.ClobClient(
|
|
5189
|
+
this.config.clobApiUrl,
|
|
5190
|
+
POLYGON_CHAIN_ID,
|
|
5191
|
+
this.signer,
|
|
5192
|
+
this.apiCreds,
|
|
5193
|
+
signatureType
|
|
5194
|
+
);
|
|
5195
|
+
if (this.vaultMode) {
|
|
5196
|
+
console.log(`Polymarket CLOB initialized in VAULT MODE \u2014 maker: ${this.vaultAddress}, signer: ${this.walletAddress}`);
|
|
5197
|
+
} else {
|
|
5198
|
+
console.log(`Polymarket CLOB initialized for ${this.walletAddress}`);
|
|
5066
5199
|
}
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5200
|
+
}
|
|
5201
|
+
/**
|
|
5202
|
+
* Check if the client is initialized with CLOB credentials.
|
|
5203
|
+
*/
|
|
5204
|
+
get isInitialized() {
|
|
5205
|
+
return this.clobClient !== null && this.apiCreds !== null;
|
|
5206
|
+
}
|
|
5207
|
+
// ============================================================
|
|
5208
|
+
// CLOB API — ORDER BOOK & PRICES
|
|
5209
|
+
// ============================================================
|
|
5210
|
+
/**
|
|
5211
|
+
* Get the order book for a specific outcome token.
|
|
5212
|
+
*/
|
|
5213
|
+
async getOrderBook(tokenId) {
|
|
5214
|
+
this.ensureInitialized();
|
|
5215
|
+
const book = await this.clobClient.getOrderBook(tokenId);
|
|
5216
|
+
return {
|
|
5217
|
+
bids: (book.bids || []).map((b) => ({ price: parseFloat(b.price), size: parseFloat(b.size) })),
|
|
5218
|
+
asks: (book.asks || []).map((a) => ({ price: parseFloat(a.price), size: parseFloat(a.size) }))
|
|
5081
5219
|
};
|
|
5082
|
-
this.positionTracker = new PositionTracker(store, { maxTradeHistory: 50 });
|
|
5083
|
-
this.executor = new TradeExecutor(this.client, this.config, () => this.getConfigHash());
|
|
5084
|
-
this.riskManager = new RiskManager(this.config.trading, this.riskUniverse);
|
|
5085
|
-
this.marketData = new MarketDataService(this.getRpcUrl(), store);
|
|
5086
|
-
setGlobalResolver(this.marketData.getResolver());
|
|
5087
|
-
const savedRisk = this.positionTracker.getRiskState();
|
|
5088
|
-
if (savedRisk.lastResetDate) {
|
|
5089
|
-
this.riskManager.restoreState(savedRisk);
|
|
5090
|
-
}
|
|
5091
|
-
await this.initializeVaultManager();
|
|
5092
|
-
await this.initializePerp();
|
|
5093
|
-
await this.initializeRelay();
|
|
5094
|
-
console.log("Agent initialized successfully");
|
|
5095
5220
|
}
|
|
5096
5221
|
/**
|
|
5097
|
-
*
|
|
5222
|
+
* Get the midpoint price for an outcome token.
|
|
5098
5223
|
*/
|
|
5099
|
-
async
|
|
5100
|
-
const
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5224
|
+
async getMidpointPrice(tokenId) {
|
|
5225
|
+
const book = await this.getOrderBook(tokenId);
|
|
5226
|
+
if (book.bids.length === 0 || book.asks.length === 0) return 0;
|
|
5227
|
+
return (book.bids[0].price + book.asks[0].price) / 2;
|
|
5228
|
+
}
|
|
5229
|
+
/**
|
|
5230
|
+
* Get the last trade price for an outcome token.
|
|
5231
|
+
*/
|
|
5232
|
+
async getLastTradePrice(tokenId) {
|
|
5233
|
+
this.ensureInitialized();
|
|
5234
|
+
const resp = await this.clobClient.getLastTradePrice(tokenId);
|
|
5235
|
+
return parseFloat(resp?.price || "0");
|
|
5236
|
+
}
|
|
5237
|
+
// ============================================================
|
|
5238
|
+
// CLOB API — ORDERS
|
|
5239
|
+
// ============================================================
|
|
5240
|
+
/**
|
|
5241
|
+
* Place a limit order on the CLOB.
|
|
5242
|
+
*/
|
|
5243
|
+
async placeLimitOrder(params) {
|
|
5244
|
+
this.ensureInitialized();
|
|
5245
|
+
const order = await this.clobClient.createAndPostOrder({
|
|
5246
|
+
tokenID: params.tokenId,
|
|
5247
|
+
price: params.price,
|
|
5248
|
+
side: params.side,
|
|
5249
|
+
size: params.size
|
|
5119
5250
|
});
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
} catch (error) {
|
|
5125
|
-
console.warn(
|
|
5126
|
-
"Relay: Failed to connect (agent will work locally):",
|
|
5127
|
-
error instanceof Error ? error.message : error
|
|
5128
|
-
);
|
|
5129
|
-
}
|
|
5251
|
+
return {
|
|
5252
|
+
orderId: order?.orderID || "",
|
|
5253
|
+
success: !!order?.orderID
|
|
5254
|
+
};
|
|
5130
5255
|
}
|
|
5131
5256
|
/**
|
|
5132
|
-
*
|
|
5257
|
+
* Place a market order (aggressive limit at best available price).
|
|
5133
5258
|
*/
|
|
5134
|
-
async
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
walletKey: this.config.privateKey,
|
|
5141
|
-
vaultConfig
|
|
5259
|
+
async placeMarketOrder(params) {
|
|
5260
|
+
this.ensureInitialized();
|
|
5261
|
+
const order = await this.clobClient.createMarketOrder({
|
|
5262
|
+
tokenID: params.tokenId,
|
|
5263
|
+
amount: params.amount,
|
|
5264
|
+
side: params.side
|
|
5142
5265
|
});
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
} else {
|
|
5149
|
-
console.log("No vault exists for this agent");
|
|
5150
|
-
if (vaultConfig.policy === "manual") {
|
|
5151
|
-
console.log("Vault creation is manual \u2014 use the command center to create one");
|
|
5152
|
-
}
|
|
5153
|
-
}
|
|
5266
|
+
const result = await this.clobClient.postOrder(order);
|
|
5267
|
+
return {
|
|
5268
|
+
orderId: result?.orderID || "",
|
|
5269
|
+
success: !!result?.orderID
|
|
5270
|
+
};
|
|
5154
5271
|
}
|
|
5155
5272
|
/**
|
|
5156
|
-
*
|
|
5157
|
-
* Only initializes if perp is enabled in config AND risk universe >= 2.
|
|
5273
|
+
* Cancel an open order by ID.
|
|
5158
5274
|
*/
|
|
5159
|
-
async
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
return;
|
|
5164
|
-
}
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5275
|
+
async cancelOrder(orderId) {
|
|
5276
|
+
this.ensureInitialized();
|
|
5277
|
+
try {
|
|
5278
|
+
await this.clobClient.cancelOrder({ orderID: orderId });
|
|
5279
|
+
return true;
|
|
5280
|
+
} catch {
|
|
5281
|
+
return false;
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
/**
|
|
5285
|
+
* Cancel all open orders.
|
|
5286
|
+
*/
|
|
5287
|
+
async cancelAllOrders() {
|
|
5288
|
+
this.ensureInitialized();
|
|
5289
|
+
try {
|
|
5290
|
+
await this.clobClient.cancelAll();
|
|
5291
|
+
return true;
|
|
5292
|
+
} catch {
|
|
5293
|
+
return false;
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
/**
|
|
5297
|
+
* Get open orders for the agent's wallet.
|
|
5298
|
+
*/
|
|
5299
|
+
async getOpenOrders() {
|
|
5300
|
+
this.ensureInitialized();
|
|
5301
|
+
return this.clobClient.getOpenOrders();
|
|
5302
|
+
}
|
|
5303
|
+
/**
|
|
5304
|
+
* Get trade history (fills) for the agent's wallet.
|
|
5305
|
+
*/
|
|
5306
|
+
async getTradeHistory() {
|
|
5307
|
+
this.ensureInitialized();
|
|
5308
|
+
const trades = await this.clobClient.getTrades();
|
|
5309
|
+
return (trades || []).map((t) => this.parseRawFill(t));
|
|
5310
|
+
}
|
|
5311
|
+
// ============================================================
|
|
5312
|
+
// GAMMA API — MARKET DISCOVERY (public, no auth)
|
|
5313
|
+
// ============================================================
|
|
5314
|
+
/**
|
|
5315
|
+
* Get active prediction markets from Gamma API.
|
|
5316
|
+
*/
|
|
5317
|
+
async getMarkets(params) {
|
|
5318
|
+
const query = new URLSearchParams();
|
|
5319
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
5320
|
+
if (params?.offset) query.set("offset", String(params.offset));
|
|
5321
|
+
if (params?.active !== void 0) query.set("active", String(params.active));
|
|
5322
|
+
if (params?.category) query.set("tag", params.category);
|
|
5323
|
+
const url = `${this.config.gammaApiUrl}/markets?${query.toString()}`;
|
|
5324
|
+
const resp = await fetch(url);
|
|
5325
|
+
if (!resp.ok) throw new Error(`Gamma API error: ${resp.status} ${await resp.text()}`);
|
|
5326
|
+
const raw = await resp.json();
|
|
5327
|
+
return (raw || []).map((m) => this.parseGammaMarket(m));
|
|
5328
|
+
}
|
|
5329
|
+
/**
|
|
5330
|
+
* Get a single market by condition ID.
|
|
5331
|
+
*/
|
|
5332
|
+
async getMarketByConditionId(conditionId) {
|
|
5333
|
+
const cached = this.marketCache.get(conditionId);
|
|
5334
|
+
if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
|
|
5335
|
+
return cached.market;
|
|
5336
|
+
}
|
|
5337
|
+
const url = `${this.config.gammaApiUrl}/markets?condition_id=${conditionId}`;
|
|
5338
|
+
const resp = await fetch(url);
|
|
5339
|
+
if (!resp.ok) return null;
|
|
5340
|
+
const raw = await resp.json();
|
|
5341
|
+
if (!raw || raw.length === 0) return null;
|
|
5342
|
+
const market = this.parseGammaMarket(raw[0]);
|
|
5343
|
+
this.marketCache.set(conditionId, { market, cachedAt: Date.now() });
|
|
5344
|
+
return market;
|
|
5345
|
+
}
|
|
5346
|
+
/**
|
|
5347
|
+
* Search markets by query string.
|
|
5348
|
+
*/
|
|
5349
|
+
async searchMarkets(query, limit = 20) {
|
|
5350
|
+
const url = `${this.config.gammaApiUrl}/markets?_q=${encodeURIComponent(query)}&limit=${limit}&active=true`;
|
|
5351
|
+
const resp = await fetch(url);
|
|
5352
|
+
if (!resp.ok) return [];
|
|
5353
|
+
const raw = await resp.json();
|
|
5354
|
+
return (raw || []).map((m) => this.parseGammaMarket(m));
|
|
5355
|
+
}
|
|
5356
|
+
/**
|
|
5357
|
+
* Get trending markets sorted by volume.
|
|
5358
|
+
*/
|
|
5359
|
+
async getTrendingMarkets(limit = 10) {
|
|
5360
|
+
const url = `${this.config.gammaApiUrl}/markets?active=true&limit=${limit}&order=volume24hr&ascending=false`;
|
|
5361
|
+
const resp = await fetch(url);
|
|
5362
|
+
if (!resp.ok) return [];
|
|
5363
|
+
const raw = await resp.json();
|
|
5364
|
+
return (raw || []).map((m) => this.parseGammaMarket(m));
|
|
5365
|
+
}
|
|
5366
|
+
// ============================================================
|
|
5367
|
+
// WALLET ADDRESS
|
|
5368
|
+
// ============================================================
|
|
5369
|
+
getWalletAddress() {
|
|
5370
|
+
return this.walletAddress;
|
|
5371
|
+
}
|
|
5372
|
+
/**
|
|
5373
|
+
* Get the effective maker address for CLOB orders.
|
|
5374
|
+
* In vault mode, this is the vault contract address.
|
|
5375
|
+
* In normal mode, this is the agent's EOA address.
|
|
5376
|
+
*/
|
|
5377
|
+
getMakerAddress() {
|
|
5378
|
+
return this.vaultMode && this.vaultAddress ? this.vaultAddress : this.walletAddress;
|
|
5379
|
+
}
|
|
5380
|
+
/**
|
|
5381
|
+
* Whether the client is operating in vault mode.
|
|
5382
|
+
*/
|
|
5383
|
+
get isVaultMode() {
|
|
5384
|
+
return this.vaultMode;
|
|
5385
|
+
}
|
|
5386
|
+
/**
|
|
5387
|
+
* Get the vault address (null if not in vault mode).
|
|
5388
|
+
*/
|
|
5389
|
+
getVaultAddress() {
|
|
5390
|
+
return this.vaultAddress;
|
|
5391
|
+
}
|
|
5392
|
+
// ============================================================
|
|
5393
|
+
// PRIVATE HELPERS
|
|
5394
|
+
// ============================================================
|
|
5395
|
+
ensureInitialized() {
|
|
5396
|
+
if (!this.clobClient) {
|
|
5397
|
+
throw new Error("PolymarketClient not initialized. Call initialize() first.");
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
parseGammaMarket(raw) {
|
|
5401
|
+
const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
|
|
5402
|
+
const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
|
|
5403
|
+
const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
|
|
5404
|
+
return {
|
|
5405
|
+
conditionId: raw.conditionId || raw.condition_id || "",
|
|
5406
|
+
question: raw.question || "",
|
|
5407
|
+
description: raw.description || "",
|
|
5408
|
+
category: raw.groupItemTitle || raw.category || "Other",
|
|
5409
|
+
outcomes,
|
|
5410
|
+
outcomeTokenIds,
|
|
5411
|
+
outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
|
|
5412
|
+
volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
|
|
5413
|
+
liquidity: parseFloat(raw.liquidity || "0"),
|
|
5414
|
+
endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
|
|
5415
|
+
active: raw.active !== false && raw.closed !== true,
|
|
5416
|
+
resolved: raw.resolved === true,
|
|
5417
|
+
winningOutcome: raw.winningOutcome,
|
|
5418
|
+
resolutionSource: raw.resolutionSource || void 0,
|
|
5419
|
+
uniqueTraders: raw.uniqueTraders || void 0
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
parseRawFill(raw) {
|
|
5423
|
+
const tokenId = raw.asset_id || void 0;
|
|
5424
|
+
const marketConditionId = raw.market || raw.asset_id || "";
|
|
5425
|
+
return {
|
|
5426
|
+
orderId: raw.orderId || raw.order_id || "",
|
|
5427
|
+
tradeId: raw.id || raw.tradeId || "",
|
|
5428
|
+
marketConditionId,
|
|
5429
|
+
outcomeIndex: raw.outcome_index ?? (raw.side === "BUY" ? 0 : 1),
|
|
5430
|
+
side: raw.side === "BUY" || raw.side === "buy" ? "BUY" : "SELL",
|
|
5431
|
+
price: String(raw.price || "0"),
|
|
5432
|
+
size: String(raw.size || "0"),
|
|
5433
|
+
fee: String(raw.fee || "0"),
|
|
5434
|
+
timestamp: raw.timestamp || raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
|
|
5435
|
+
isMaker: raw.maker_order || raw.is_maker || false,
|
|
5436
|
+
tokenId
|
|
5437
|
+
};
|
|
5438
|
+
}
|
|
5439
|
+
};
|
|
5440
|
+
|
|
5441
|
+
// src/prediction/signer.ts
|
|
5442
|
+
var import_viem7 = require("viem");
|
|
5443
|
+
function tradeIdToBytes32(tradeId) {
|
|
5444
|
+
if (tradeId.startsWith("0x") && tradeId.length === 66) {
|
|
5445
|
+
return tradeId;
|
|
5446
|
+
}
|
|
5447
|
+
return (0, import_viem7.keccak256)((0, import_viem7.encodePacked)(["string"], [tradeId]));
|
|
5448
|
+
}
|
|
5449
|
+
function orderIdToBytes32(orderId) {
|
|
5450
|
+
return (0, import_viem7.keccak256)((0, import_viem7.encodePacked)(["string"], [`poly-order:${orderId}`]));
|
|
5451
|
+
}
|
|
5452
|
+
function encodePredictionInstrument(conditionId, outcomeIndex) {
|
|
5453
|
+
return `${PREDICTION_INSTRUMENT_PREFIX}${conditionId}:${outcomeIndex}`;
|
|
5454
|
+
}
|
|
5455
|
+
function decodePredictionInstrument(instrument) {
|
|
5456
|
+
if (!instrument.startsWith(PREDICTION_INSTRUMENT_PREFIX)) return null;
|
|
5457
|
+
const payload = instrument.slice(PREDICTION_INSTRUMENT_PREFIX.length);
|
|
5458
|
+
const lastColon = payload.lastIndexOf(":");
|
|
5459
|
+
if (lastColon === -1) return null;
|
|
5460
|
+
const conditionId = payload.slice(0, lastColon);
|
|
5461
|
+
const outcomeIndex = parseInt(payload.slice(lastColon + 1), 10);
|
|
5462
|
+
if (isNaN(outcomeIndex)) return null;
|
|
5463
|
+
return { conditionId, outcomeIndex };
|
|
5464
|
+
}
|
|
5465
|
+
function calculatePredictionFee(notionalUSD) {
|
|
5466
|
+
return notionalUSD * 20n / 10000n;
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
// src/prediction/order-manager.ts
|
|
5470
|
+
var PredictionOrderManager = class {
|
|
5471
|
+
client;
|
|
5472
|
+
config;
|
|
5473
|
+
/** Recent fills tracked for recording */
|
|
5474
|
+
recentFills = [];
|
|
5475
|
+
MAX_RECENT_FILLS = 100;
|
|
5476
|
+
constructor(client, config) {
|
|
5477
|
+
this.client = client;
|
|
5478
|
+
this.config = config;
|
|
5479
|
+
}
|
|
5480
|
+
// ============================================================
|
|
5481
|
+
// ORDER PLACEMENT
|
|
5482
|
+
// ============================================================
|
|
5483
|
+
/**
|
|
5484
|
+
* Execute a prediction trade signal.
|
|
5485
|
+
* Routes to limit or market order based on signal.orderType.
|
|
5486
|
+
*/
|
|
5487
|
+
async executeSignal(signal) {
|
|
5488
|
+
try {
|
|
5489
|
+
const violation = this.checkRiskLimits(signal);
|
|
5490
|
+
if (violation) {
|
|
5491
|
+
return { success: false, status: "error", error: violation };
|
|
5492
|
+
}
|
|
5493
|
+
const market = await this.client.getMarketByConditionId(signal.marketConditionId);
|
|
5494
|
+
if (!market) {
|
|
5495
|
+
return { success: false, status: "error", error: `Market not found: ${signal.marketConditionId}` };
|
|
5496
|
+
}
|
|
5497
|
+
if (!market.active) {
|
|
5498
|
+
return { success: false, status: "error", error: `Market is closed: ${signal.marketQuestion}` };
|
|
5499
|
+
}
|
|
5500
|
+
const tokenId = market.outcomeTokenIds[signal.outcomeIndex];
|
|
5501
|
+
if (!tokenId) {
|
|
5502
|
+
return { success: false, status: "error", error: `Invalid outcome index ${signal.outcomeIndex} for market` };
|
|
5503
|
+
}
|
|
5504
|
+
const isBuy = signal.action === "buy_yes" || signal.action === "buy_no";
|
|
5505
|
+
const side = isBuy ? "BUY" : "SELL";
|
|
5506
|
+
let result;
|
|
5507
|
+
if (signal.orderType === "market") {
|
|
5508
|
+
result = await this.client.placeMarketOrder({
|
|
5509
|
+
tokenId,
|
|
5510
|
+
amount: signal.amount,
|
|
5511
|
+
side
|
|
5512
|
+
});
|
|
5513
|
+
} else {
|
|
5514
|
+
result = await this.client.placeLimitOrder({
|
|
5515
|
+
tokenId,
|
|
5516
|
+
price: signal.limitPrice,
|
|
5517
|
+
size: signal.amount,
|
|
5518
|
+
side
|
|
5519
|
+
});
|
|
5520
|
+
}
|
|
5521
|
+
if (result.success) {
|
|
5522
|
+
console.log(
|
|
5523
|
+
`Prediction order placed: ${signal.action} ${signal.amount} @ $${signal.limitPrice} \u2014 "${signal.marketQuestion}" \u2014 order: ${result.orderId}`
|
|
5524
|
+
);
|
|
5525
|
+
return {
|
|
5526
|
+
success: true,
|
|
5527
|
+
orderId: result.orderId,
|
|
5528
|
+
status: signal.orderType === "market" ? "filled" : "resting"
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
return { success: false, status: "error", error: "Order placement failed" };
|
|
5532
|
+
} catch (error) {
|
|
5533
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5534
|
+
console.error(`Prediction order failed for "${signal.marketQuestion}":`, message);
|
|
5535
|
+
return { success: false, status: "error", error: message };
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
/**
|
|
5539
|
+
* Cancel an open order by ID.
|
|
5540
|
+
*/
|
|
5541
|
+
async cancelOrder(orderId) {
|
|
5542
|
+
return this.client.cancelOrder(orderId);
|
|
5543
|
+
}
|
|
5544
|
+
/**
|
|
5545
|
+
* Cancel all open orders.
|
|
5546
|
+
*/
|
|
5547
|
+
async cancelAllOrders() {
|
|
5548
|
+
return this.client.cancelAllOrders();
|
|
5549
|
+
}
|
|
5550
|
+
// ============================================================
|
|
5551
|
+
// FILL TRACKING
|
|
5552
|
+
// ============================================================
|
|
5553
|
+
/**
|
|
5554
|
+
* Poll for new fills since last check.
|
|
5555
|
+
* Returns fills that haven't been seen before.
|
|
5556
|
+
*/
|
|
5557
|
+
async pollNewFills() {
|
|
5558
|
+
try {
|
|
5559
|
+
const allFills = await this.client.getTradeHistory();
|
|
5560
|
+
const seenIds = new Set(this.recentFills.map((f) => f.tradeId));
|
|
5561
|
+
const newFills = allFills.filter((f) => !seenIds.has(f.tradeId));
|
|
5562
|
+
for (const fill of newFills) {
|
|
5563
|
+
this.recentFills.push(fill);
|
|
5564
|
+
if (this.recentFills.length > this.MAX_RECENT_FILLS) {
|
|
5565
|
+
this.recentFills.shift();
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
return newFills;
|
|
5569
|
+
} catch (error) {
|
|
5570
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5571
|
+
console.error("Failed to poll prediction fills:", message);
|
|
5572
|
+
return [];
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
/**
|
|
5576
|
+
* Get recent fills (from local cache).
|
|
5577
|
+
*/
|
|
5578
|
+
getRecentFills() {
|
|
5579
|
+
return [...this.recentFills];
|
|
5580
|
+
}
|
|
5581
|
+
// ============================================================
|
|
5582
|
+
// RISK CHECKS
|
|
5583
|
+
// ============================================================
|
|
5584
|
+
/**
|
|
5585
|
+
* Check if a signal violates risk limits.
|
|
5586
|
+
* Returns error string if violated, null if OK.
|
|
5587
|
+
*/
|
|
5588
|
+
checkRiskLimits(signal) {
|
|
5589
|
+
if (signal.amount > this.config.maxNotionalUSD) {
|
|
5590
|
+
return `Trade amount $${signal.amount} exceeds max notional $${this.config.maxNotionalUSD}`;
|
|
5591
|
+
}
|
|
5592
|
+
if (signal.limitPrice <= 0 || signal.limitPrice >= 1) {
|
|
5593
|
+
return `Limit price ${signal.limitPrice} out of bounds (must be 0.01-0.99)`;
|
|
5594
|
+
}
|
|
5595
|
+
if (signal.confidence < 0.3) {
|
|
5596
|
+
return `Confidence ${signal.confidence} below minimum threshold (0.3)`;
|
|
5597
|
+
}
|
|
5598
|
+
if (this.config.allowedCategories && this.config.allowedCategories.length > 0) {
|
|
5599
|
+
}
|
|
5600
|
+
return null;
|
|
5601
|
+
}
|
|
5602
|
+
};
|
|
5603
|
+
|
|
5604
|
+
// src/prediction/position-manager.ts
|
|
5605
|
+
var PredictionPositionManager = class {
|
|
5606
|
+
client;
|
|
5607
|
+
config;
|
|
5608
|
+
/** Local position tracking (conditionId:outcomeIndex -> position data) */
|
|
5609
|
+
positions = /* @__PURE__ */ new Map();
|
|
5610
|
+
/** Cache TTL */
|
|
5611
|
+
lastPriceRefresh = 0;
|
|
5612
|
+
PRICE_REFRESH_MS = 1e4;
|
|
5613
|
+
// 10 seconds
|
|
5614
|
+
constructor(client, config) {
|
|
5615
|
+
this.client = client;
|
|
5616
|
+
this.config = config;
|
|
5617
|
+
}
|
|
5618
|
+
// ============================================================
|
|
5619
|
+
// POSITION QUERIES
|
|
5620
|
+
// ============================================================
|
|
5621
|
+
/**
|
|
5622
|
+
* Get all open prediction positions with current prices.
|
|
5623
|
+
*/
|
|
5624
|
+
async getPositions(forceRefresh = false) {
|
|
5625
|
+
if (forceRefresh || Date.now() - this.lastPriceRefresh > this.PRICE_REFRESH_MS) {
|
|
5626
|
+
await this.refreshPrices();
|
|
5627
|
+
}
|
|
5628
|
+
return Array.from(this.positions.values()).filter((p) => p.balance > 0).map((p) => this.toExternalPosition(p));
|
|
5629
|
+
}
|
|
5630
|
+
/**
|
|
5631
|
+
* Get a specific position.
|
|
5632
|
+
*/
|
|
5633
|
+
async getPosition(conditionId, outcomeIndex) {
|
|
5634
|
+
const key = `${conditionId}:${outcomeIndex}`;
|
|
5635
|
+
const pos = this.positions.get(key);
|
|
5636
|
+
if (!pos || pos.balance <= 0) return null;
|
|
5637
|
+
return this.toExternalPosition(pos);
|
|
5638
|
+
}
|
|
5639
|
+
/**
|
|
5640
|
+
* Get account summary (balances, exposure, PnL).
|
|
5641
|
+
*/
|
|
5642
|
+
async getAccountSummary() {
|
|
5643
|
+
const positions = await this.getPositions();
|
|
5644
|
+
const totalExposure = positions.reduce((sum, p) => sum + p.costBasis, 0);
|
|
5645
|
+
const totalUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
|
|
5646
|
+
const openMarkets = new Set(positions.map((p) => p.marketConditionId));
|
|
5647
|
+
return {
|
|
5648
|
+
polygonUSDC: 0,
|
|
5649
|
+
// Filled in by runtime from on-chain balance
|
|
5650
|
+
baseUSDC: 0,
|
|
5651
|
+
// Filled in by runtime from on-chain balance
|
|
5652
|
+
totalExposure,
|
|
5653
|
+
totalUnrealizedPnl,
|
|
5654
|
+
openMarketCount: openMarkets.size,
|
|
5655
|
+
openPositionCount: positions.length,
|
|
5656
|
+
pendingFees: 0
|
|
5657
|
+
// Filled in by recorder
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
/**
|
|
5661
|
+
* Get total exposure across all positions.
|
|
5662
|
+
*/
|
|
5663
|
+
getTotalExposure() {
|
|
5664
|
+
let total = 0;
|
|
5665
|
+
for (const pos of this.positions.values()) {
|
|
5666
|
+
if (pos.balance > 0) total += pos.totalCostBasis;
|
|
5667
|
+
}
|
|
5668
|
+
return total;
|
|
5669
|
+
}
|
|
5670
|
+
/**
|
|
5671
|
+
* Get total unrealized PnL.
|
|
5672
|
+
*/
|
|
5673
|
+
getTotalUnrealizedPnl() {
|
|
5674
|
+
let total = 0;
|
|
5675
|
+
for (const pos of this.positions.values()) {
|
|
5676
|
+
if (pos.balance > 0) {
|
|
5677
|
+
total += (pos.currentPrice - pos.averageEntryPrice) * pos.balance;
|
|
5678
|
+
}
|
|
5679
|
+
}
|
|
5680
|
+
return total;
|
|
5681
|
+
}
|
|
5682
|
+
// ============================================================
|
|
5683
|
+
// FILL PROCESSING
|
|
5684
|
+
// ============================================================
|
|
5685
|
+
/**
|
|
5686
|
+
* Process a fill and update position tracking.
|
|
5687
|
+
* Called when a new fill is detected by the order manager.
|
|
5688
|
+
*/
|
|
5689
|
+
processFill(fill) {
|
|
5690
|
+
const key = `${fill.marketConditionId}:${fill.outcomeIndex}`;
|
|
5691
|
+
let pos = this.positions.get(key);
|
|
5692
|
+
const price = parseFloat(fill.price);
|
|
5693
|
+
const size = parseFloat(fill.size);
|
|
5694
|
+
if (!pos) {
|
|
5695
|
+
pos = {
|
|
5696
|
+
marketConditionId: fill.marketConditionId,
|
|
5697
|
+
outcomeIndex: fill.outcomeIndex,
|
|
5698
|
+
marketQuestion: fill.marketQuestion || "",
|
|
5699
|
+
tokenId: fill.tokenId || "",
|
|
5700
|
+
balance: 0,
|
|
5701
|
+
totalBought: 0,
|
|
5702
|
+
totalSold: 0,
|
|
5703
|
+
totalCostBasis: 0,
|
|
5704
|
+
totalProceeds: 0,
|
|
5705
|
+
averageEntryPrice: 0,
|
|
5706
|
+
currentPrice: price,
|
|
5707
|
+
category: void 0,
|
|
5708
|
+
endDate: void 0
|
|
5709
|
+
};
|
|
5710
|
+
this.positions.set(key, pos);
|
|
5711
|
+
} else if (!pos.tokenId && fill.tokenId) {
|
|
5712
|
+
pos.tokenId = fill.tokenId;
|
|
5713
|
+
}
|
|
5714
|
+
if (fill.side === "BUY") {
|
|
5715
|
+
const oldCost = pos.averageEntryPrice * pos.balance;
|
|
5716
|
+
const newCost = price * size;
|
|
5717
|
+
pos.balance += size;
|
|
5718
|
+
pos.totalBought += size;
|
|
5719
|
+
pos.totalCostBasis += newCost;
|
|
5720
|
+
pos.averageEntryPrice = pos.balance > 0 ? (oldCost + newCost) / pos.balance : 0;
|
|
5721
|
+
} else {
|
|
5722
|
+
pos.balance -= size;
|
|
5723
|
+
pos.totalSold += size;
|
|
5724
|
+
pos.totalProceeds += price * size;
|
|
5725
|
+
if (pos.balance < 0) pos.balance = 0;
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
/**
|
|
5729
|
+
* Process multiple fills (batch update).
|
|
5730
|
+
*/
|
|
5731
|
+
processFills(fills) {
|
|
5732
|
+
for (const fill of fills) {
|
|
5733
|
+
this.processFill(fill);
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
// ============================================================
|
|
5737
|
+
// MARKET RESOLUTION
|
|
5738
|
+
// ============================================================
|
|
5739
|
+
/**
|
|
5740
|
+
* Mark a market as resolved. Positions settle at $0 or $1.
|
|
5741
|
+
*/
|
|
5742
|
+
resolveMarket(conditionId, winningOutcome) {
|
|
5743
|
+
for (const [key, pos] of this.positions.entries()) {
|
|
5744
|
+
if (pos.marketConditionId === conditionId) {
|
|
5745
|
+
const isWinner = pos.outcomeIndex === winningOutcome;
|
|
5746
|
+
pos.currentPrice = isWinner ? 1 : 0;
|
|
5747
|
+
if (pos.balance > 0) {
|
|
5748
|
+
pos.totalProceeds += pos.currentPrice * pos.balance;
|
|
5749
|
+
pos.totalSold += pos.balance;
|
|
5750
|
+
pos.balance = 0;
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
// ============================================================
|
|
5756
|
+
// PERSISTENCE
|
|
5757
|
+
// ============================================================
|
|
5758
|
+
/**
|
|
5759
|
+
* Export position state for persistence across restarts.
|
|
5760
|
+
*/
|
|
5761
|
+
exportState() {
|
|
5762
|
+
return Array.from(this.positions.values());
|
|
5763
|
+
}
|
|
5764
|
+
/**
|
|
5765
|
+
* Import position state from previous session.
|
|
5766
|
+
*/
|
|
5767
|
+
importState(state) {
|
|
5768
|
+
this.positions.clear();
|
|
5769
|
+
for (const pos of state) {
|
|
5770
|
+
const key = `${pos.marketConditionId}:${pos.outcomeIndex}`;
|
|
5771
|
+
this.positions.set(key, pos);
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
// ============================================================
|
|
5775
|
+
// PRIVATE
|
|
5776
|
+
// ============================================================
|
|
5777
|
+
/**
|
|
5778
|
+
* Refresh current prices for all open positions from CLOB.
|
|
5779
|
+
*/
|
|
5780
|
+
async refreshPrices() {
|
|
5781
|
+
const openPositions = Array.from(this.positions.values()).filter((p) => p.balance > 0);
|
|
5782
|
+
await Promise.all(
|
|
5783
|
+
openPositions.map(async (pos) => {
|
|
5784
|
+
try {
|
|
5785
|
+
if (!pos.tokenId) {
|
|
5786
|
+
const market = await this.client.getMarketByConditionId(pos.marketConditionId);
|
|
5787
|
+
if (market && market.outcomeTokenIds[pos.outcomeIndex]) {
|
|
5788
|
+
pos.tokenId = market.outcomeTokenIds[pos.outcomeIndex];
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
if (pos.tokenId) {
|
|
5792
|
+
const price = await this.client.getMidpointPrice(pos.tokenId);
|
|
5793
|
+
if (price > 0) pos.currentPrice = price;
|
|
5794
|
+
}
|
|
5795
|
+
} catch {
|
|
5796
|
+
}
|
|
5797
|
+
})
|
|
5798
|
+
);
|
|
5799
|
+
this.lastPriceRefresh = Date.now();
|
|
5800
|
+
}
|
|
5801
|
+
toExternalPosition(pos) {
|
|
5802
|
+
const unrealizedPnl = pos.balance > 0 ? (pos.currentPrice - pos.averageEntryPrice) * pos.balance : 0;
|
|
5803
|
+
return {
|
|
5804
|
+
marketConditionId: pos.marketConditionId,
|
|
5805
|
+
marketQuestion: pos.marketQuestion,
|
|
5806
|
+
outcomeIndex: pos.outcomeIndex,
|
|
5807
|
+
outcomeLabel: pos.outcomeIndex === 0 ? "Yes" : "No",
|
|
5808
|
+
tokenId: pos.tokenId,
|
|
5809
|
+
balance: pos.balance,
|
|
5810
|
+
averageEntryPrice: pos.averageEntryPrice,
|
|
5811
|
+
currentPrice: pos.currentPrice,
|
|
5812
|
+
unrealizedPnl,
|
|
5813
|
+
costBasis: pos.totalCostBasis - pos.totalProceeds,
|
|
5814
|
+
endDate: pos.endDate,
|
|
5815
|
+
category: pos.category
|
|
5816
|
+
};
|
|
5817
|
+
}
|
|
5818
|
+
};
|
|
5819
|
+
|
|
5820
|
+
// src/prediction/recorder.ts
|
|
5821
|
+
var import_viem8 = require("viem");
|
|
5822
|
+
var import_chains5 = require("viem/chains");
|
|
5823
|
+
var import_accounts5 = require("viem/accounts");
|
|
5824
|
+
var ROUTER_ADDRESS2 = "0x1BCFa13f677fDCf697D8b7d5120f544817F1de1A";
|
|
5825
|
+
var ROUTER_ABI2 = [
|
|
5826
|
+
{
|
|
5827
|
+
type: "function",
|
|
5828
|
+
name: "recordPerpTrade",
|
|
5829
|
+
stateMutability: "nonpayable",
|
|
5830
|
+
inputs: [
|
|
5831
|
+
{ name: "agentId", type: "uint256" },
|
|
5832
|
+
{ name: "configHash", type: "bytes32" },
|
|
5833
|
+
{ name: "instrument", type: "string" },
|
|
5834
|
+
{ name: "isLong", type: "bool" },
|
|
5835
|
+
{ name: "notionalUSD", type: "uint256" },
|
|
5836
|
+
{ name: "feeUSD", type: "uint256" },
|
|
5837
|
+
{ name: "fillId", type: "bytes32" }
|
|
5838
|
+
],
|
|
5839
|
+
outputs: [{ name: "", type: "bool" }]
|
|
5840
|
+
}
|
|
5841
|
+
];
|
|
5842
|
+
var MAX_RETRIES2 = 3;
|
|
5843
|
+
var RETRY_DELAY_MS2 = 5e3;
|
|
5844
|
+
var PredictionTradeRecorder = class {
|
|
5845
|
+
publicClient;
|
|
5846
|
+
walletClient;
|
|
5847
|
+
account;
|
|
5848
|
+
agentId;
|
|
5849
|
+
configHash;
|
|
5850
|
+
/** Retry queue for failed recordings */
|
|
5851
|
+
retryQueue = [];
|
|
5852
|
+
/** Set of fill IDs already recorded (or in-progress) to prevent local dups */
|
|
5853
|
+
recordedFills = /* @__PURE__ */ new Set();
|
|
5854
|
+
/** Timer for processing retry queue */
|
|
5855
|
+
retryTimer = null;
|
|
5856
|
+
/** Accumulated fees pending bridge to Base treasury (in USD, 6-decimal) */
|
|
5857
|
+
accumulatedFees = 0n;
|
|
5858
|
+
constructor(opts) {
|
|
5859
|
+
this.agentId = opts.agentId;
|
|
5860
|
+
this.configHash = opts.configHash;
|
|
5861
|
+
this.account = (0, import_accounts5.privateKeyToAccount)(opts.privateKey);
|
|
5862
|
+
const rpcUrl = opts.rpcUrl || "https://mainnet.base.org";
|
|
5863
|
+
const transport = (0, import_viem8.http)(rpcUrl, { timeout: 6e4 });
|
|
5864
|
+
this.publicClient = (0, import_viem8.createPublicClient)({
|
|
5865
|
+
chain: import_chains5.base,
|
|
5866
|
+
transport
|
|
5867
|
+
});
|
|
5868
|
+
this.walletClient = (0, import_viem8.createWalletClient)({
|
|
5869
|
+
chain: import_chains5.base,
|
|
5870
|
+
transport,
|
|
5871
|
+
account: this.account
|
|
5872
|
+
});
|
|
5873
|
+
this.retryTimer = setInterval(() => this.processRetryQueue(), RETRY_DELAY_MS2);
|
|
5874
|
+
}
|
|
5875
|
+
// ============================================================
|
|
5876
|
+
// PUBLIC API
|
|
5877
|
+
// ============================================================
|
|
5878
|
+
/**
|
|
5879
|
+
* Record a prediction fill on-chain.
|
|
5880
|
+
* Converts the Polymarket fill into recordPerpTrade params with POLY: prefix.
|
|
5881
|
+
*/
|
|
5882
|
+
async recordFill(fill) {
|
|
5883
|
+
const fillId = tradeIdToBytes32(fill.tradeId);
|
|
5884
|
+
const fillIdStr = fillId.toLowerCase();
|
|
5885
|
+
if (this.recordedFills.has(fillIdStr)) {
|
|
5886
|
+
return { success: true };
|
|
5887
|
+
}
|
|
5888
|
+
this.recordedFills.add(fillIdStr);
|
|
5889
|
+
const notionalUSD = this.calculateNotionalUSD(fill);
|
|
5890
|
+
const feeUSD = calculatePredictionFee(notionalUSD);
|
|
5891
|
+
const params = {
|
|
5892
|
+
agentId: this.agentId,
|
|
5893
|
+
configHash: this.configHash,
|
|
5894
|
+
instrument: encodePredictionInstrument(fill.marketConditionId, fill.outcomeIndex),
|
|
5895
|
+
isLong: fill.side === "BUY",
|
|
5896
|
+
notionalUSD,
|
|
5897
|
+
feeUSD,
|
|
5898
|
+
fillId
|
|
5899
|
+
};
|
|
5900
|
+
this.accumulatedFees += feeUSD;
|
|
5901
|
+
return this.submitRecord(params);
|
|
5902
|
+
}
|
|
5903
|
+
/**
|
|
5904
|
+
* Update the config hash (when epoch changes).
|
|
5905
|
+
*/
|
|
5906
|
+
updateConfigHash(configHash) {
|
|
5907
|
+
this.configHash = configHash;
|
|
5908
|
+
}
|
|
5909
|
+
/**
|
|
5910
|
+
* Get the number of fills pending retry.
|
|
5911
|
+
*/
|
|
5912
|
+
get pendingRetries() {
|
|
5913
|
+
return this.retryQueue.length;
|
|
5914
|
+
}
|
|
5915
|
+
/**
|
|
5916
|
+
* Get the number of fills recorded (local dedup set size).
|
|
5917
|
+
*/
|
|
5918
|
+
get recordedCount() {
|
|
5919
|
+
return this.recordedFills.size;
|
|
5920
|
+
}
|
|
5921
|
+
/**
|
|
5922
|
+
* Get accumulated fees pending bridge (6-decimal USD).
|
|
5923
|
+
*/
|
|
5924
|
+
get pendingFees() {
|
|
5925
|
+
return this.accumulatedFees;
|
|
5926
|
+
}
|
|
5927
|
+
/**
|
|
5928
|
+
* Get accumulated fees as human-readable USD.
|
|
5929
|
+
*/
|
|
5930
|
+
get pendingFeesUSD() {
|
|
5931
|
+
return Number(this.accumulatedFees) / 1e6;
|
|
5932
|
+
}
|
|
5933
|
+
/**
|
|
5934
|
+
* Reset accumulated fees (after bridge to treasury).
|
|
5935
|
+
*/
|
|
5936
|
+
resetAccumulatedFees() {
|
|
5937
|
+
this.accumulatedFees = 0n;
|
|
5938
|
+
}
|
|
5939
|
+
/**
|
|
5940
|
+
* Stop the recorder (clear retry timer).
|
|
5941
|
+
*/
|
|
5942
|
+
stop() {
|
|
5943
|
+
if (this.retryTimer) {
|
|
5944
|
+
clearInterval(this.retryTimer);
|
|
5945
|
+
this.retryTimer = null;
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
// ============================================================
|
|
5949
|
+
// PRIVATE
|
|
5950
|
+
// ============================================================
|
|
5951
|
+
/**
|
|
5952
|
+
* Submit a recordPerpTrade transaction on Base.
|
|
5953
|
+
*/
|
|
5954
|
+
async submitRecord(params) {
|
|
5955
|
+
try {
|
|
5956
|
+
const { request } = await this.publicClient.simulateContract({
|
|
5957
|
+
address: ROUTER_ADDRESS2,
|
|
5958
|
+
abi: ROUTER_ABI2,
|
|
5959
|
+
functionName: "recordPerpTrade",
|
|
5960
|
+
args: [
|
|
5961
|
+
params.agentId,
|
|
5962
|
+
params.configHash,
|
|
5963
|
+
params.instrument,
|
|
5964
|
+
params.isLong,
|
|
5965
|
+
params.notionalUSD,
|
|
5966
|
+
params.feeUSD,
|
|
5967
|
+
params.fillId
|
|
5968
|
+
],
|
|
5969
|
+
account: this.account
|
|
5970
|
+
});
|
|
5971
|
+
const txHash = await this.walletClient.writeContract(request);
|
|
5972
|
+
const action = params.isLong ? "BUY" : "SELL";
|
|
5973
|
+
console.log(
|
|
5974
|
+
`Prediction trade recorded: ${action} ${params.instrument} $${Number(params.notionalUSD) / 1e6} \u2014 tx: ${txHash}`
|
|
5975
|
+
);
|
|
5976
|
+
return { success: true, txHash };
|
|
5977
|
+
} catch (error) {
|
|
5978
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5979
|
+
if (message.includes("FillAlreadyProcessed") || message.includes("already recorded")) {
|
|
5980
|
+
console.log(`Prediction fill already recorded on-chain: ${params.fillId}`);
|
|
5981
|
+
return { success: true };
|
|
5982
|
+
}
|
|
5983
|
+
console.error(`Failed to record prediction trade: ${message}`);
|
|
5984
|
+
this.retryQueue.push({
|
|
5985
|
+
params,
|
|
5986
|
+
retries: 0,
|
|
5987
|
+
lastAttempt: Date.now()
|
|
5988
|
+
});
|
|
5989
|
+
return { success: false, error: message };
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
/**
|
|
5993
|
+
* Process the retry queue — attempt to re-submit failed recordings.
|
|
5994
|
+
*/
|
|
5995
|
+
async processRetryQueue() {
|
|
5996
|
+
if (this.retryQueue.length === 0) return;
|
|
5997
|
+
const now = Date.now();
|
|
5998
|
+
const toRetry = this.retryQueue.filter(
|
|
5999
|
+
(item) => now - item.lastAttempt >= RETRY_DELAY_MS2
|
|
6000
|
+
);
|
|
6001
|
+
for (const item of toRetry) {
|
|
6002
|
+
item.retries++;
|
|
6003
|
+
item.lastAttempt = now;
|
|
6004
|
+
if (item.retries > MAX_RETRIES2) {
|
|
6005
|
+
console.error(
|
|
6006
|
+
`Prediction trade recording permanently failed after ${MAX_RETRIES2} retries: ${item.params.instrument} ${item.params.fillId}`
|
|
6007
|
+
);
|
|
6008
|
+
const idx = this.retryQueue.indexOf(item);
|
|
6009
|
+
if (idx >= 0) this.retryQueue.splice(idx, 1);
|
|
6010
|
+
continue;
|
|
6011
|
+
}
|
|
6012
|
+
console.log(
|
|
6013
|
+
`Retrying prediction trade recording (attempt ${item.retries}/${MAX_RETRIES2}): ${item.params.instrument}`
|
|
6014
|
+
);
|
|
6015
|
+
const result = await this.submitRecord(item.params);
|
|
6016
|
+
if (result.success) {
|
|
6017
|
+
const idx = this.retryQueue.indexOf(item);
|
|
6018
|
+
if (idx >= 0) this.retryQueue.splice(idx, 1);
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
// ============================================================
|
|
6023
|
+
// CONVERSION HELPERS
|
|
6024
|
+
// ============================================================
|
|
6025
|
+
/**
|
|
6026
|
+
* Calculate notional USD from a fill (6-decimal).
|
|
6027
|
+
* notionalUSD = price * size * 1e6
|
|
6028
|
+
*/
|
|
6029
|
+
calculateNotionalUSD(fill) {
|
|
6030
|
+
const price = parseFloat(fill.price);
|
|
6031
|
+
const size = parseFloat(fill.size);
|
|
6032
|
+
return BigInt(Math.round(price * size * 1e6));
|
|
6033
|
+
}
|
|
6034
|
+
};
|
|
6035
|
+
|
|
6036
|
+
// src/prediction/market-browser.ts
|
|
6037
|
+
var MARKET_CATEGORIES = [
|
|
6038
|
+
"crypto",
|
|
6039
|
+
"politics",
|
|
6040
|
+
"sports",
|
|
6041
|
+
"entertainment",
|
|
6042
|
+
"science",
|
|
6043
|
+
"business",
|
|
6044
|
+
"culture",
|
|
6045
|
+
"weather",
|
|
6046
|
+
"tech"
|
|
6047
|
+
];
|
|
6048
|
+
var MarketBrowser = class {
|
|
6049
|
+
gammaUrl;
|
|
6050
|
+
allowedCategories;
|
|
6051
|
+
/** Cache for market listings */
|
|
6052
|
+
listCache = /* @__PURE__ */ new Map();
|
|
6053
|
+
CACHE_TTL_MS = 6e4;
|
|
6054
|
+
constructor(config) {
|
|
6055
|
+
const merged = { ...DEFAULT_PREDICTION_CONFIG, ...config };
|
|
6056
|
+
this.gammaUrl = merged.gammaApiUrl;
|
|
6057
|
+
this.allowedCategories = merged.allowedCategories || [];
|
|
6058
|
+
}
|
|
6059
|
+
// ============================================================
|
|
6060
|
+
// PUBLIC API
|
|
6061
|
+
// ============================================================
|
|
6062
|
+
/**
|
|
6063
|
+
* Get active markets, optionally filtered by category.
|
|
6064
|
+
* Results are sorted by volume (highest first).
|
|
6065
|
+
*/
|
|
6066
|
+
async getActiveMarkets(params) {
|
|
6067
|
+
const limit = params?.limit || 20;
|
|
6068
|
+
const offset = params?.offset || 0;
|
|
6069
|
+
const cacheKey = `active:${params?.category || "all"}:${limit}:${offset}`;
|
|
6070
|
+
const cached = this.listCache.get(cacheKey);
|
|
6071
|
+
if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
|
|
6072
|
+
return cached.data;
|
|
6073
|
+
}
|
|
6074
|
+
const query = new URLSearchParams({
|
|
6075
|
+
active: "true",
|
|
6076
|
+
closed: "false",
|
|
6077
|
+
limit: String(limit),
|
|
6078
|
+
offset: String(offset),
|
|
6079
|
+
order: "volume24hr",
|
|
6080
|
+
ascending: "false"
|
|
6081
|
+
});
|
|
6082
|
+
if (params?.category) query.set("tag", params.category);
|
|
6083
|
+
const markets = await this.fetchGamma(`/markets?${query.toString()}`);
|
|
6084
|
+
const parsed = markets.map((m) => this.parseMarket(m));
|
|
6085
|
+
const filtered = this.allowedCategories.length > 0 ? parsed.filter(
|
|
6086
|
+
(m) => this.allowedCategories.some((c) => m.category.toLowerCase().includes(c.toLowerCase()))
|
|
6087
|
+
) : parsed;
|
|
6088
|
+
this.listCache.set(cacheKey, { data: filtered, cachedAt: Date.now() });
|
|
6089
|
+
return filtered;
|
|
6090
|
+
}
|
|
6091
|
+
/**
|
|
6092
|
+
* Search markets by query text.
|
|
6093
|
+
*/
|
|
6094
|
+
async searchMarkets(query, limit = 20) {
|
|
6095
|
+
const params = new URLSearchParams({
|
|
6096
|
+
_q: query,
|
|
6097
|
+
limit: String(limit),
|
|
6098
|
+
active: "true"
|
|
6099
|
+
});
|
|
6100
|
+
const markets = await this.fetchGamma(`/markets?${params.toString()}`);
|
|
6101
|
+
return markets.map((m) => this.parseMarket(m));
|
|
6102
|
+
}
|
|
6103
|
+
/**
|
|
6104
|
+
* Get trending markets (highest 24h volume).
|
|
6105
|
+
*/
|
|
6106
|
+
async getTrendingMarkets(limit = 10) {
|
|
6107
|
+
return this.getActiveMarkets({ limit });
|
|
6108
|
+
}
|
|
6109
|
+
/**
|
|
6110
|
+
* Get markets expiring soon (within N days).
|
|
6111
|
+
*/
|
|
6112
|
+
async getExpiringMarkets(withinDays = 7, limit = 20) {
|
|
6113
|
+
const markets = await this.getActiveMarkets({ limit: 100 });
|
|
6114
|
+
const cutoff = Date.now() / 1e3 + withinDays * 86400;
|
|
6115
|
+
return markets.filter((m) => m.endDate > 0 && m.endDate < cutoff).sort((a, b) => a.endDate - b.endDate).slice(0, limit);
|
|
6116
|
+
}
|
|
6117
|
+
/**
|
|
6118
|
+
* Get recently resolved markets (for PnL tracking).
|
|
6119
|
+
*/
|
|
6120
|
+
async getRecentlyResolved(limit = 20) {
|
|
6121
|
+
const params = new URLSearchParams({
|
|
6122
|
+
closed: "true",
|
|
6123
|
+
limit: String(limit),
|
|
6124
|
+
order: "endDate",
|
|
6125
|
+
ascending: "false"
|
|
6126
|
+
});
|
|
6127
|
+
const markets = await this.fetchGamma(`/markets?${params.toString()}`);
|
|
6128
|
+
return markets.map((m) => this.parseMarket(m)).filter((m) => m.resolved);
|
|
6129
|
+
}
|
|
6130
|
+
/**
|
|
6131
|
+
* Get a single market's details by condition ID.
|
|
6132
|
+
*/
|
|
6133
|
+
async getMarketDetail(conditionId) {
|
|
6134
|
+
const markets = await this.fetchGamma(`/markets?condition_id=${conditionId}`);
|
|
6135
|
+
if (!markets || markets.length === 0) return null;
|
|
6136
|
+
return this.parseMarket(markets[0]);
|
|
6137
|
+
}
|
|
6138
|
+
/**
|
|
6139
|
+
* Build a concise market summary string for LLM context.
|
|
6140
|
+
* Keeps the prompt token count manageable.
|
|
6141
|
+
*/
|
|
6142
|
+
formatMarketsForLLM(markets, maxMarkets = 15) {
|
|
6143
|
+
const subset = markets.slice(0, maxMarkets);
|
|
6144
|
+
const lines = subset.map((m, i) => {
|
|
6145
|
+
const prices = m.outcomePrices.map((p, j) => `${m.outcomes[j]}: ${(p * 100).toFixed(1)}%`).join(", ");
|
|
6146
|
+
const vol = m.volume24h >= 1e3 ? `$${(m.volume24h / 1e3).toFixed(1)}K` : `$${m.volume24h.toFixed(0)}`;
|
|
6147
|
+
const endStr = m.endDate > 0 ? new Date(m.endDate * 1e3).toISOString().split("T")[0] : "TBD";
|
|
6148
|
+
return `${i + 1}. [${m.category}] "${m.question}" \u2014 ${prices} | Vol: ${vol} | Ends: ${endStr} | ID: ${m.conditionId}`;
|
|
6149
|
+
});
|
|
6150
|
+
return lines.join("\n");
|
|
6151
|
+
}
|
|
6152
|
+
// ============================================================
|
|
6153
|
+
// PRIVATE
|
|
6154
|
+
// ============================================================
|
|
6155
|
+
async fetchGamma(path4) {
|
|
6156
|
+
const url = `${this.gammaUrl}${path4}`;
|
|
6157
|
+
const resp = await fetch(url);
|
|
6158
|
+
if (!resp.ok) {
|
|
6159
|
+
console.error(`Gamma API error: ${resp.status} for ${url}`);
|
|
6160
|
+
return [];
|
|
6161
|
+
}
|
|
6162
|
+
const data = await resp.json();
|
|
6163
|
+
return Array.isArray(data) ? data : [];
|
|
6164
|
+
}
|
|
6165
|
+
parseMarket(raw) {
|
|
6166
|
+
const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
|
|
6167
|
+
const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
|
|
6168
|
+
const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
|
|
6169
|
+
return {
|
|
6170
|
+
conditionId: raw.conditionId || raw.condition_id || "",
|
|
6171
|
+
question: raw.question || "",
|
|
6172
|
+
description: raw.description || "",
|
|
6173
|
+
category: raw.groupItemTitle || raw.category || "Other",
|
|
6174
|
+
outcomes,
|
|
6175
|
+
outcomeTokenIds,
|
|
6176
|
+
outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
|
|
6177
|
+
volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
|
|
6178
|
+
liquidity: parseFloat(raw.liquidity || "0"),
|
|
6179
|
+
endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
|
|
6180
|
+
active: raw.active !== false && raw.closed !== true,
|
|
6181
|
+
resolved: raw.resolved === true,
|
|
6182
|
+
winningOutcome: raw.winningOutcome,
|
|
6183
|
+
resolutionSource: raw.resolutionSource || void 0,
|
|
6184
|
+
uniqueTraders: raw.uniqueTraders || void 0
|
|
6185
|
+
};
|
|
6186
|
+
}
|
|
6187
|
+
};
|
|
6188
|
+
|
|
6189
|
+
// src/prediction/funding.ts
|
|
6190
|
+
var import_viem9 = require("viem");
|
|
6191
|
+
var import_chains6 = require("viem/chains");
|
|
6192
|
+
var import_accounts6 = require("viem/accounts");
|
|
6193
|
+
var CONTRACTS = {
|
|
6194
|
+
// Base
|
|
6195
|
+
BASE_USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
6196
|
+
BASE_TOKEN_MESSENGER_V2: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
|
|
6197
|
+
BASE_MESSAGE_TRANSMITTER_V2: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
|
|
6198
|
+
// Polygon
|
|
6199
|
+
POLYGON_USDC: POLYGON_USDC_ADDRESS,
|
|
6200
|
+
POLYGON_TOKEN_MESSENGER_V2,
|
|
6201
|
+
POLYGON_MESSAGE_TRANSMITTER_V2
|
|
6202
|
+
};
|
|
6203
|
+
var IRIS_API_URL = "https://iris-api.circle.com/v1/attestations";
|
|
6204
|
+
var MIN_BRIDGE_AMOUNT = 1000000n;
|
|
6205
|
+
var ERC20_ABI2 = (0, import_viem9.parseAbi)([
|
|
6206
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
6207
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
6208
|
+
"function allowance(address owner, address spender) external view returns (uint256)"
|
|
6209
|
+
]);
|
|
6210
|
+
var TOKEN_MESSENGER_V2_ABI2 = (0, import_viem9.parseAbi)([
|
|
6211
|
+
"function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) external returns (uint64 nonce)",
|
|
6212
|
+
"event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)"
|
|
6213
|
+
]);
|
|
6214
|
+
var MESSAGE_TRANSMITTER_V2_ABI2 = (0, import_viem9.parseAbi)([
|
|
6215
|
+
"function receiveMessage(bytes message, bytes attestation) external returns (bool success)",
|
|
6216
|
+
"event MessageSent(bytes message)"
|
|
6217
|
+
]);
|
|
6218
|
+
var PredictionFunding = class {
|
|
6219
|
+
basePublic;
|
|
6220
|
+
baseWallet;
|
|
6221
|
+
polygonPublic;
|
|
6222
|
+
polygonWallet;
|
|
6223
|
+
account;
|
|
6224
|
+
/** Override recipient for deposits to Polygon (vault address in vault mode) */
|
|
6225
|
+
polygonRecipient;
|
|
6226
|
+
constructor(config) {
|
|
6227
|
+
this.account = (0, import_accounts6.privateKeyToAccount)(config.privateKey);
|
|
6228
|
+
this.polygonRecipient = config.polygonRecipient;
|
|
6229
|
+
const baseTransport = (0, import_viem9.http)(config.baseRpcUrl || "https://mainnet.base.org", { timeout: 6e4 });
|
|
6230
|
+
const polygonTransport = (0, import_viem9.http)(config.polygonRpcUrl || "https://polygon-rpc.com", { timeout: 6e4 });
|
|
6231
|
+
this.basePublic = (0, import_viem9.createPublicClient)({ chain: import_chains6.base, transport: baseTransport });
|
|
6232
|
+
this.baseWallet = (0, import_viem9.createWalletClient)({ chain: import_chains6.base, transport: baseTransport, account: this.account });
|
|
6233
|
+
this.polygonPublic = (0, import_viem9.createPublicClient)({ chain: import_chains6.polygon, transport: polygonTransport });
|
|
6234
|
+
this.polygonWallet = (0, import_viem9.createWalletClient)({ chain: import_chains6.polygon, transport: polygonTransport, account: this.account });
|
|
6235
|
+
}
|
|
6236
|
+
// ============================================================
|
|
6237
|
+
// BALANCE QUERIES
|
|
6238
|
+
// ============================================================
|
|
6239
|
+
/** Get USDC balance on Base (6-decimal) */
|
|
6240
|
+
async getBaseUSDCBalance() {
|
|
6241
|
+
return this.basePublic.readContract({
|
|
6242
|
+
address: CONTRACTS.BASE_USDC,
|
|
6243
|
+
abi: ERC20_ABI2,
|
|
6244
|
+
functionName: "balanceOf",
|
|
6245
|
+
args: [this.account.address]
|
|
6246
|
+
});
|
|
6247
|
+
}
|
|
6248
|
+
/** Get USDC balance on Polygon (6-decimal) */
|
|
6249
|
+
async getPolygonUSDCBalance() {
|
|
6250
|
+
return this.polygonPublic.readContract({
|
|
6251
|
+
address: CONTRACTS.POLYGON_USDC,
|
|
6252
|
+
abi: ERC20_ABI2,
|
|
6253
|
+
functionName: "balanceOf",
|
|
6254
|
+
args: [this.account.address]
|
|
6255
|
+
});
|
|
6256
|
+
}
|
|
6257
|
+
/** Get MATIC balance on Polygon for gas (wei) */
|
|
6258
|
+
async getPolygonGasBalance() {
|
|
6259
|
+
return this.polygonPublic.getBalance({ address: this.account.address });
|
|
6260
|
+
}
|
|
6261
|
+
// ============================================================
|
|
6262
|
+
// DEPOSIT: Base → Polygon
|
|
6263
|
+
// ============================================================
|
|
6264
|
+
/**
|
|
6265
|
+
* Bridge USDC from Base to Polygon via CCTP V2.
|
|
6266
|
+
*
|
|
6267
|
+
* Steps:
|
|
6268
|
+
* 1. Approve USDC on Base for TokenMessengerV2
|
|
6269
|
+
* 2. Call depositForBurn (Base → Polygon)
|
|
6270
|
+
* 3. Wait for Circle attestation (~1-5 minutes)
|
|
6271
|
+
* 4. Call receiveMessage on Polygon
|
|
6272
|
+
*/
|
|
6273
|
+
async depositToPolygon(amount, onStep) {
|
|
6274
|
+
const startTime = Date.now();
|
|
6275
|
+
if (amount < MIN_BRIDGE_AMOUNT) {
|
|
6276
|
+
return { success: false, amount, error: "Amount below minimum (1 USDC)" };
|
|
6277
|
+
}
|
|
6278
|
+
try {
|
|
6279
|
+
onStep?.("approve_usdc");
|
|
6280
|
+
await this.approveIfNeeded(
|
|
6281
|
+
this.basePublic,
|
|
6282
|
+
this.baseWallet,
|
|
6283
|
+
CONTRACTS.BASE_USDC,
|
|
6284
|
+
CONTRACTS.BASE_TOKEN_MESSENGER_V2,
|
|
6285
|
+
amount
|
|
6286
|
+
);
|
|
6287
|
+
onStep?.("cctp_burn");
|
|
6288
|
+
const recipient = this.polygonRecipient ?? this.account.address;
|
|
6289
|
+
const mintRecipient = this.addressToBytes32(recipient);
|
|
6290
|
+
const burnHash = await this.baseWallet.writeContract({
|
|
6291
|
+
address: CONTRACTS.BASE_TOKEN_MESSENGER_V2,
|
|
6292
|
+
abi: TOKEN_MESSENGER_V2_ABI2,
|
|
6293
|
+
functionName: "depositForBurn",
|
|
6294
|
+
args: [amount, CCTP_DOMAIN_POLYGON, mintRecipient, CONTRACTS.BASE_USDC]
|
|
6295
|
+
});
|
|
6296
|
+
const burnReceipt = await this.basePublic.waitForTransactionReceipt({ hash: burnHash });
|
|
6297
|
+
const messageBytes = this.extractMessageFromReceipt(burnReceipt);
|
|
6298
|
+
if (!messageBytes) {
|
|
6299
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Failed to extract CCTP message from burn tx" };
|
|
6300
|
+
}
|
|
6301
|
+
onStep?.("wait_attestation");
|
|
6302
|
+
const attestation = await this.waitForAttestation(messageBytes);
|
|
6303
|
+
if (!attestation) {
|
|
6304
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Attestation timeout (15 min)" };
|
|
6305
|
+
}
|
|
6306
|
+
onStep?.("cctp_receive");
|
|
6307
|
+
const receiveHash = await this.polygonWallet.writeContract({
|
|
6308
|
+
address: CONTRACTS.POLYGON_MESSAGE_TRANSMITTER_V2,
|
|
6309
|
+
abi: MESSAGE_TRANSMITTER_V2_ABI2,
|
|
6310
|
+
functionName: "receiveMessage",
|
|
6311
|
+
args: [messageBytes, attestation]
|
|
6312
|
+
});
|
|
6313
|
+
await this.polygonPublic.waitForTransactionReceipt({ hash: receiveHash });
|
|
6314
|
+
console.log(`CCTP deposit complete: ${Number(amount) / 1e6} USDC Base \u2192 Polygon in ${((Date.now() - startTime) / 1e3).toFixed(0)}s`);
|
|
6315
|
+
return {
|
|
6316
|
+
success: true,
|
|
6317
|
+
amount,
|
|
6318
|
+
burnTxHash: burnHash,
|
|
6319
|
+
receiveTxHash: receiveHash,
|
|
6320
|
+
duration: Date.now() - startTime
|
|
6321
|
+
};
|
|
6322
|
+
} catch (error) {
|
|
6323
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6324
|
+
console.error("CCTP deposit to Polygon failed:", message);
|
|
6325
|
+
return { success: false, amount, error: message, duration: Date.now() - startTime };
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
// ============================================================
|
|
6329
|
+
// WITHDRAW: Polygon → Base
|
|
6330
|
+
// ============================================================
|
|
6331
|
+
/**
|
|
6332
|
+
* Bridge USDC from Polygon back to Base via CCTP V2.
|
|
6333
|
+
* Used for fee collection (bridge accumulated fees to Base treasury).
|
|
6334
|
+
*/
|
|
6335
|
+
async withdrawToBase(amount, onStep) {
|
|
6336
|
+
const startTime = Date.now();
|
|
6337
|
+
if (amount < MIN_BRIDGE_AMOUNT) {
|
|
6338
|
+
return { success: false, amount, error: "Amount below minimum (1 USDC)" };
|
|
6339
|
+
}
|
|
6340
|
+
try {
|
|
6341
|
+
onStep?.("approve_usdc");
|
|
6342
|
+
await this.approveIfNeeded(
|
|
6343
|
+
this.polygonPublic,
|
|
6344
|
+
this.polygonWallet,
|
|
6345
|
+
CONTRACTS.POLYGON_USDC,
|
|
6346
|
+
CONTRACTS.POLYGON_TOKEN_MESSENGER_V2,
|
|
6347
|
+
amount
|
|
6348
|
+
);
|
|
6349
|
+
onStep?.("cctp_burn");
|
|
6350
|
+
const mintRecipient = this.addressToBytes32(this.account.address);
|
|
6351
|
+
const burnHash = await this.polygonWallet.writeContract({
|
|
6352
|
+
address: CONTRACTS.POLYGON_TOKEN_MESSENGER_V2,
|
|
6353
|
+
abi: TOKEN_MESSENGER_V2_ABI2,
|
|
6354
|
+
functionName: "depositForBurn",
|
|
6355
|
+
args: [amount, CCTP_DOMAIN_BASE, mintRecipient, CONTRACTS.POLYGON_USDC]
|
|
6356
|
+
});
|
|
6357
|
+
const burnReceipt = await this.polygonPublic.waitForTransactionReceipt({ hash: burnHash });
|
|
6358
|
+
const messageBytes = this.extractMessageFromReceipt(burnReceipt);
|
|
6359
|
+
if (!messageBytes) {
|
|
6360
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Failed to extract CCTP message" };
|
|
6361
|
+
}
|
|
6362
|
+
onStep?.("wait_attestation");
|
|
6363
|
+
const attestation = await this.waitForAttestation(messageBytes);
|
|
6364
|
+
if (!attestation) {
|
|
6365
|
+
return { success: false, amount, burnTxHash: burnHash, error: "Attestation timeout" };
|
|
6366
|
+
}
|
|
6367
|
+
onStep?.("cctp_receive");
|
|
6368
|
+
const receiveHash = await this.baseWallet.writeContract({
|
|
6369
|
+
address: CONTRACTS.BASE_MESSAGE_TRANSMITTER_V2,
|
|
6370
|
+
abi: MESSAGE_TRANSMITTER_V2_ABI2,
|
|
6371
|
+
functionName: "receiveMessage",
|
|
6372
|
+
args: [messageBytes, attestation]
|
|
6373
|
+
});
|
|
6374
|
+
await this.basePublic.waitForTransactionReceipt({ hash: receiveHash });
|
|
6375
|
+
console.log(`CCTP withdrawal complete: ${Number(amount) / 1e6} USDC Polygon \u2192 Base in ${((Date.now() - startTime) / 1e3).toFixed(0)}s`);
|
|
6376
|
+
return {
|
|
6377
|
+
success: true,
|
|
6378
|
+
amount,
|
|
6379
|
+
burnTxHash: burnHash,
|
|
6380
|
+
receiveTxHash: receiveHash,
|
|
6381
|
+
duration: Date.now() - startTime
|
|
6382
|
+
};
|
|
6383
|
+
} catch (error) {
|
|
6384
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6385
|
+
console.error("CCTP withdrawal to Base failed:", message);
|
|
6386
|
+
return { success: false, amount, error: message, duration: Date.now() - startTime };
|
|
6387
|
+
}
|
|
6388
|
+
}
|
|
6389
|
+
// ============================================================
|
|
6390
|
+
// PRIVATE HELPERS
|
|
6391
|
+
// ============================================================
|
|
6392
|
+
/** Convert address to bytes32 (left-padded) for CCTP mintRecipient */
|
|
6393
|
+
addressToBytes32(address) {
|
|
6394
|
+
return `0x000000000000000000000000${address.slice(2)}`;
|
|
6395
|
+
}
|
|
6396
|
+
/** Approve USDC spend if needed (maxUint256 pattern) */
|
|
6397
|
+
async approveIfNeeded(publicClient, walletClient, token, spender, amount) {
|
|
6398
|
+
const allowance = await publicClient.readContract({
|
|
6399
|
+
address: token,
|
|
6400
|
+
abi: ERC20_ABI2,
|
|
6401
|
+
functionName: "allowance",
|
|
6402
|
+
args: [this.account.address, spender]
|
|
6403
|
+
});
|
|
6404
|
+
if (allowance < amount) {
|
|
6405
|
+
const maxUint256 = 2n ** 256n - 1n;
|
|
6406
|
+
const hash = await walletClient.writeContract({
|
|
6407
|
+
address: token,
|
|
6408
|
+
abi: ERC20_ABI2,
|
|
6409
|
+
functionName: "approve",
|
|
6410
|
+
args: [spender, maxUint256]
|
|
6411
|
+
});
|
|
6412
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
/** Extract MessageSent bytes from a CCTP burn transaction receipt */
|
|
6416
|
+
extractMessageFromReceipt(receipt) {
|
|
6417
|
+
for (const log of receipt.logs || []) {
|
|
6418
|
+
try {
|
|
6419
|
+
if (log.topics[0] === "0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036") {
|
|
6420
|
+
return log.data;
|
|
6421
|
+
}
|
|
6422
|
+
} catch {
|
|
6423
|
+
continue;
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
return null;
|
|
6427
|
+
}
|
|
6428
|
+
/** Wait for Circle attestation (polls Iris API) */
|
|
6429
|
+
async waitForAttestation(messageBytes, timeoutMs = 9e5) {
|
|
6430
|
+
const { keccak256: keccak2565 } = await import("viem");
|
|
6431
|
+
const messageHash = keccak2565(messageBytes);
|
|
6432
|
+
const start = Date.now();
|
|
6433
|
+
const pollInterval = 5e3;
|
|
6434
|
+
while (Date.now() - start < timeoutMs) {
|
|
6435
|
+
try {
|
|
6436
|
+
const resp = await fetch(`${IRIS_API_URL}/${messageHash}`);
|
|
6437
|
+
if (resp.ok) {
|
|
6438
|
+
const data = await resp.json();
|
|
6439
|
+
if (data.status === "complete" && data.attestation) {
|
|
6440
|
+
return data.attestation;
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
} catch {
|
|
6444
|
+
}
|
|
6445
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
6446
|
+
}
|
|
6447
|
+
return null;
|
|
6448
|
+
}
|
|
6449
|
+
};
|
|
6450
|
+
|
|
6451
|
+
// src/prediction/vault-manager.ts
|
|
6452
|
+
var import_viem10 = require("viem");
|
|
6453
|
+
var import_chains7 = require("viem/chains");
|
|
6454
|
+
var import_accounts7 = require("viem/accounts");
|
|
6455
|
+
var ERC20_ABI3 = (0, import_viem10.parseAbi)([
|
|
6456
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
6457
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
6458
|
+
"function allowance(address owner, address spender) external view returns (uint256)"
|
|
6459
|
+
]);
|
|
6460
|
+
var VAULT_FACTORY_ABI2 = (0, import_viem10.parseAbi)([
|
|
6461
|
+
"function createVault(uint256 agentId, address agentWallet, uint256 seedAmount, string name, string symbol, address feeRecipient, bool isVerified, bytes agentSig) external returns (address)",
|
|
6462
|
+
"function vaults(uint256 agentId) external view returns (address)",
|
|
6463
|
+
"function isFactoryVault(address vault) external view returns (bool)",
|
|
6464
|
+
"function MIN_SEED_AMOUNT() external view returns (uint256)"
|
|
6465
|
+
]);
|
|
6466
|
+
var VAULT_ABI2 = (0, import_viem10.parseAbi)([
|
|
6467
|
+
// ERC-4626
|
|
6468
|
+
"function deposit(uint256 assets, address receiver) external returns (uint256)",
|
|
6469
|
+
"function withdraw(uint256 assets, address receiver, address owner) external returns (uint256)",
|
|
6470
|
+
"function redeem(uint256 shares, address receiver, address owner) external returns (uint256)",
|
|
6471
|
+
"function redeemMax(address receiver, address owner) external returns (uint256)",
|
|
6472
|
+
"function totalAssets() external view returns (uint256)",
|
|
6473
|
+
"function totalSupply() external view returns (uint256)",
|
|
6474
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
6475
|
+
"function convertToAssets(uint256 shares) external view returns (uint256)",
|
|
6476
|
+
"function convertToShares(uint256 assets) external view returns (uint256)",
|
|
6477
|
+
// Vault-specific views
|
|
6478
|
+
"function sharePrice() external view returns (uint256)",
|
|
6479
|
+
"function availableTradingBalance() external view returns (uint256)",
|
|
6480
|
+
"function currentExposureBps() external view returns (uint256)",
|
|
6481
|
+
"function cachedCtfValue() external view returns (uint256)",
|
|
6482
|
+
"function lastCtfUpdate() external view returns (uint256)",
|
|
6483
|
+
"function depositCap() external view returns (uint256)",
|
|
6484
|
+
"function agentSeedAmount() external view returns (uint256)",
|
|
6485
|
+
"function circuitBreakerActive() external view returns (bool)",
|
|
6486
|
+
"function paused() external view returns (bool)",
|
|
6487
|
+
"function pendingTradingVolume() external view returns (uint256)",
|
|
6488
|
+
"function getTrackedTokenCount() external view returns (uint256)",
|
|
6489
|
+
"function agentWallet() external view returns (address)",
|
|
6490
|
+
"function agentId() external view returns (uint256)",
|
|
6491
|
+
"function isVerified() external view returns (bool)",
|
|
6492
|
+
"function maxExposureBps() external view returns (uint256)",
|
|
6493
|
+
// Keeper functions
|
|
6494
|
+
"function updatePositionValues(uint256[] tokenIds, uint256[] values) external",
|
|
6495
|
+
"function collectTradingFees(uint256 volume) external",
|
|
6496
|
+
// Emergency
|
|
6497
|
+
"function emergencyWithdraw() external returns (uint256)"
|
|
6498
|
+
]);
|
|
6499
|
+
var PredictionVaultManager = class {
|
|
6500
|
+
config;
|
|
6501
|
+
publicClient;
|
|
6502
|
+
walletClient;
|
|
6503
|
+
account;
|
|
6504
|
+
/** Resolved vault address (discovered or created) */
|
|
6505
|
+
vaultAddress = null;
|
|
6506
|
+
/** NAV update timer */
|
|
6507
|
+
navTimer = null;
|
|
6508
|
+
/** Fee collection timer */
|
|
6509
|
+
feeTimer = null;
|
|
6510
|
+
/** Last known accumulated trading volume (for delta tracking) */
|
|
6511
|
+
lastReportedVolume = 0n;
|
|
6512
|
+
constructor(privateKey, config) {
|
|
6513
|
+
this.config = config;
|
|
6514
|
+
this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
|
|
6515
|
+
const transport = (0, import_viem10.http)(config.polygonRpcUrl, { timeout: 6e4 });
|
|
6516
|
+
this.publicClient = (0, import_viem10.createPublicClient)({ chain: import_chains7.polygon, transport });
|
|
6517
|
+
this.walletClient = (0, import_viem10.createWalletClient)({
|
|
6518
|
+
chain: import_chains7.polygon,
|
|
6519
|
+
transport,
|
|
6520
|
+
account: this.account
|
|
6521
|
+
});
|
|
6522
|
+
if (config.vaultAddress) {
|
|
6523
|
+
this.vaultAddress = config.vaultAddress;
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
// ============================================================
|
|
6527
|
+
// INITIALIZATION
|
|
6528
|
+
// ============================================================
|
|
6529
|
+
/**
|
|
6530
|
+
* Initialize the vault manager — discover existing vault or prepare for creation.
|
|
6531
|
+
*/
|
|
6532
|
+
async initialize() {
|
|
6533
|
+
if (!this.vaultAddress) {
|
|
6534
|
+
try {
|
|
6535
|
+
const vaultAddr = await this.publicClient.readContract({
|
|
6536
|
+
address: this.config.factoryAddress,
|
|
6537
|
+
abi: VAULT_FACTORY_ABI2,
|
|
6538
|
+
functionName: "vaults",
|
|
6539
|
+
args: [this.config.agentId]
|
|
6540
|
+
});
|
|
6541
|
+
if (vaultAddr && vaultAddr !== "0x0000000000000000000000000000000000000000") {
|
|
6542
|
+
this.vaultAddress = vaultAddr;
|
|
6543
|
+
console.log(`Discovered existing prediction vault: ${this.vaultAddress}`);
|
|
6544
|
+
}
|
|
6545
|
+
} catch {
|
|
6546
|
+
console.log("No existing prediction vault found \u2014 create one via createVault()");
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
if (this.vaultAddress) {
|
|
6550
|
+
console.log(`Prediction vault manager initialized for vault: ${this.vaultAddress}`);
|
|
6551
|
+
}
|
|
6552
|
+
}
|
|
6553
|
+
/**
|
|
6554
|
+
* Get the vault address (null if no vault exists).
|
|
6555
|
+
*/
|
|
6556
|
+
getVaultAddress() {
|
|
6557
|
+
return this.vaultAddress;
|
|
6558
|
+
}
|
|
6559
|
+
/**
|
|
6560
|
+
* Whether a vault has been created/discovered.
|
|
6561
|
+
*/
|
|
6562
|
+
get hasVault() {
|
|
6563
|
+
return this.vaultAddress !== null;
|
|
6564
|
+
}
|
|
6565
|
+
// ============================================================
|
|
6566
|
+
// VAULT CREATION
|
|
6567
|
+
// ============================================================
|
|
6568
|
+
/**
|
|
6569
|
+
* Create a new prediction vault via PredictionVaultFactory.
|
|
6570
|
+
*
|
|
6571
|
+
* Deploys PredictionVault on Polygon with:
|
|
6572
|
+
* - Agent's EOA as the authorized signer for ERC-1271
|
|
6573
|
+
* - Seed USDC transferred from agent wallet to vault
|
|
6574
|
+
* - Deposit cap = seed × multiplier (10x unverified, 100x verified)
|
|
6575
|
+
*/
|
|
6576
|
+
async createVault(seedAmount, name, symbol) {
|
|
6577
|
+
if (this.vaultAddress) {
|
|
6578
|
+
return { success: false, error: `Vault already exists: ${this.vaultAddress}` };
|
|
6579
|
+
}
|
|
6580
|
+
try {
|
|
6581
|
+
const message = (0, import_viem10.keccak256)(
|
|
6582
|
+
(0, import_viem10.encodePacked)(
|
|
6583
|
+
["uint256", "address", "address"],
|
|
6584
|
+
[this.config.agentId, this.account.address, this.config.factoryAddress]
|
|
6585
|
+
)
|
|
6586
|
+
);
|
|
6587
|
+
const agentSig = await this.account.signMessage({ message: { raw: message } });
|
|
6588
|
+
await this.approveIfNeeded(
|
|
6589
|
+
POLYGON_USDC_ADDRESS,
|
|
6590
|
+
this.config.factoryAddress,
|
|
6591
|
+
seedAmount
|
|
6592
|
+
);
|
|
6593
|
+
const hash = await this.walletClient.writeContract({
|
|
6594
|
+
address: this.config.factoryAddress,
|
|
6595
|
+
abi: VAULT_FACTORY_ABI2,
|
|
6596
|
+
functionName: "createVault",
|
|
6597
|
+
args: [
|
|
6598
|
+
this.config.agentId,
|
|
6599
|
+
this.account.address,
|
|
6600
|
+
seedAmount,
|
|
6601
|
+
name,
|
|
6602
|
+
symbol,
|
|
6603
|
+
this.config.feeRecipient,
|
|
6604
|
+
this.config.isVerified ?? false,
|
|
6605
|
+
agentSig
|
|
6606
|
+
]
|
|
6607
|
+
});
|
|
6608
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6609
|
+
const vaultAddr = await this.publicClient.readContract({
|
|
6610
|
+
address: this.config.factoryAddress,
|
|
6611
|
+
abi: VAULT_FACTORY_ABI2,
|
|
6612
|
+
functionName: "vaults",
|
|
6613
|
+
args: [this.config.agentId]
|
|
6614
|
+
});
|
|
6615
|
+
this.vaultAddress = vaultAddr;
|
|
6616
|
+
console.log(`Prediction vault created: ${this.vaultAddress} (seed: ${(0, import_viem10.formatUnits)(seedAmount, 6)} USDC)`);
|
|
6617
|
+
return {
|
|
6618
|
+
success: true,
|
|
6619
|
+
vaultAddress: this.vaultAddress,
|
|
6620
|
+
txHash: hash
|
|
6621
|
+
};
|
|
6622
|
+
} catch (error) {
|
|
6623
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6624
|
+
console.error("Failed to create prediction vault:", message);
|
|
6625
|
+
return { success: false, error: message };
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6628
|
+
// ============================================================
|
|
6629
|
+
// DEPOSITS & WITHDRAWALS
|
|
6630
|
+
// ============================================================
|
|
6631
|
+
/**
|
|
6632
|
+
* Deposit USDC into the prediction vault.
|
|
6633
|
+
* Returns the number of shares received.
|
|
6634
|
+
*/
|
|
6635
|
+
async deposit(amount) {
|
|
6636
|
+
if (!this.vaultAddress) {
|
|
6637
|
+
return { success: false, error: "No vault exists" };
|
|
6638
|
+
}
|
|
6639
|
+
try {
|
|
6640
|
+
await this.approveIfNeeded(
|
|
6641
|
+
POLYGON_USDC_ADDRESS,
|
|
6642
|
+
this.vaultAddress,
|
|
6643
|
+
amount
|
|
6644
|
+
);
|
|
6645
|
+
const hash = await this.walletClient.writeContract({
|
|
6646
|
+
address: this.vaultAddress,
|
|
6647
|
+
abi: VAULT_ABI2,
|
|
6648
|
+
functionName: "deposit",
|
|
6649
|
+
args: [amount, this.account.address]
|
|
6650
|
+
});
|
|
6651
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6652
|
+
const shares = await this.publicClient.readContract({
|
|
6653
|
+
address: this.vaultAddress,
|
|
6654
|
+
abi: VAULT_ABI2,
|
|
6655
|
+
functionName: "balanceOf",
|
|
6656
|
+
args: [this.account.address]
|
|
6657
|
+
});
|
|
6658
|
+
console.log(`Deposited ${(0, import_viem10.formatUnits)(amount, 6)} USDC into prediction vault`);
|
|
6659
|
+
return { success: true, shares, txHash: hash };
|
|
6660
|
+
} catch (error) {
|
|
6661
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6662
|
+
console.error("Vault deposit failed:", message);
|
|
6663
|
+
return { success: false, error: message };
|
|
6664
|
+
}
|
|
6665
|
+
}
|
|
6666
|
+
/**
|
|
6667
|
+
* Withdraw USDC from the prediction vault.
|
|
6668
|
+
* Redeems all shares and returns the USDC received (net of fees).
|
|
6669
|
+
*/
|
|
6670
|
+
async withdrawAll() {
|
|
6671
|
+
if (!this.vaultAddress) {
|
|
6672
|
+
return { success: false, error: "No vault exists" };
|
|
6673
|
+
}
|
|
6674
|
+
try {
|
|
6675
|
+
const hash = await this.walletClient.writeContract({
|
|
6676
|
+
address: this.vaultAddress,
|
|
6677
|
+
abi: VAULT_ABI2,
|
|
6678
|
+
functionName: "redeemMax",
|
|
6679
|
+
args: [this.account.address, this.account.address]
|
|
6680
|
+
});
|
|
6681
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6682
|
+
const balance = await this.publicClient.readContract({
|
|
6683
|
+
address: POLYGON_USDC_ADDRESS,
|
|
6684
|
+
abi: ERC20_ABI3,
|
|
6685
|
+
functionName: "balanceOf",
|
|
6686
|
+
args: [this.account.address]
|
|
6687
|
+
});
|
|
6688
|
+
console.log("Withdrew all shares from prediction vault");
|
|
6689
|
+
return { success: true, assets: balance, txHash: hash };
|
|
6690
|
+
} catch (error) {
|
|
6691
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6692
|
+
console.error("Vault withdrawal failed:", message);
|
|
6693
|
+
return { success: false, error: message };
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
/**
|
|
6697
|
+
* Emergency withdraw with penalty.
|
|
6698
|
+
*/
|
|
6699
|
+
async emergencyWithdraw() {
|
|
6700
|
+
if (!this.vaultAddress) {
|
|
6701
|
+
return { success: false, error: "No vault exists" };
|
|
6702
|
+
}
|
|
6703
|
+
try {
|
|
6704
|
+
const hash = await this.walletClient.writeContract({
|
|
6705
|
+
address: this.vaultAddress,
|
|
6706
|
+
abi: VAULT_ABI2,
|
|
6707
|
+
functionName: "emergencyWithdraw",
|
|
6708
|
+
args: []
|
|
6709
|
+
});
|
|
6710
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6711
|
+
console.log("Emergency withdrew from prediction vault (penalty applied)");
|
|
6712
|
+
return { success: true, txHash: hash };
|
|
6713
|
+
} catch (error) {
|
|
6714
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6715
|
+
return { success: false, error: message };
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
// ============================================================
|
|
6719
|
+
// VAULT STATUS
|
|
6720
|
+
// ============================================================
|
|
6721
|
+
/**
|
|
6722
|
+
* Get comprehensive vault status.
|
|
6723
|
+
*/
|
|
6724
|
+
async getVaultStatus() {
|
|
6725
|
+
if (!this.vaultAddress) {
|
|
6726
|
+
return {
|
|
6727
|
+
vaultAddress: "0x0000000000000000000000000000000000000000",
|
|
6728
|
+
hasVault: false,
|
|
6729
|
+
totalAssets: 0n,
|
|
6730
|
+
totalSupply: 0n,
|
|
6731
|
+
sharePrice: 0,
|
|
6732
|
+
usdcBalance: 0n,
|
|
6733
|
+
cachedCtfValue: 0n,
|
|
6734
|
+
currentExposureBps: 0,
|
|
6735
|
+
depositCap: 0n,
|
|
6736
|
+
seedAmount: 0n,
|
|
6737
|
+
circuitBreakerActive: false,
|
|
6738
|
+
isPaused: false,
|
|
6739
|
+
lastCtfUpdate: 0,
|
|
6740
|
+
pendingTradingVolume: 0n,
|
|
6741
|
+
trackedTokenCount: 0
|
|
6742
|
+
};
|
|
6743
|
+
}
|
|
6744
|
+
try {
|
|
6745
|
+
const [
|
|
6746
|
+
totalAssets,
|
|
6747
|
+
totalSupply,
|
|
6748
|
+
sharePrice,
|
|
6749
|
+
usdcBalance,
|
|
6750
|
+
cachedCtfValue,
|
|
6751
|
+
currentExposureBps,
|
|
6752
|
+
depositCap,
|
|
6753
|
+
seedAmount,
|
|
6754
|
+
circuitBreakerActive,
|
|
6755
|
+
isPaused,
|
|
6756
|
+
lastCtfUpdate,
|
|
6757
|
+
pendingTradingVolume,
|
|
6758
|
+
trackedTokenCount
|
|
6759
|
+
] = await Promise.all([
|
|
6760
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "totalAssets" }),
|
|
6761
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "totalSupply" }),
|
|
6762
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "sharePrice" }),
|
|
6763
|
+
this.publicClient.readContract({ address: POLYGON_USDC_ADDRESS, abi: ERC20_ABI3, functionName: "balanceOf", args: [this.vaultAddress] }),
|
|
6764
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "cachedCtfValue" }),
|
|
6765
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "currentExposureBps" }),
|
|
6766
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "depositCap" }),
|
|
6767
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "agentSeedAmount" }),
|
|
6768
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "circuitBreakerActive" }),
|
|
6769
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "paused" }),
|
|
6770
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "lastCtfUpdate" }),
|
|
6771
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "pendingTradingVolume" }),
|
|
6772
|
+
this.publicClient.readContract({ address: this.vaultAddress, abi: VAULT_ABI2, functionName: "getTrackedTokenCount" })
|
|
6773
|
+
]);
|
|
6774
|
+
return {
|
|
6775
|
+
vaultAddress: this.vaultAddress,
|
|
6776
|
+
hasVault: true,
|
|
6777
|
+
totalAssets,
|
|
6778
|
+
totalSupply,
|
|
6779
|
+
sharePrice: Number(sharePrice) / 1e6,
|
|
6780
|
+
usdcBalance,
|
|
6781
|
+
cachedCtfValue,
|
|
6782
|
+
currentExposureBps: Number(currentExposureBps),
|
|
6783
|
+
depositCap,
|
|
6784
|
+
seedAmount,
|
|
6785
|
+
circuitBreakerActive,
|
|
6786
|
+
isPaused,
|
|
6787
|
+
lastCtfUpdate: Number(lastCtfUpdate),
|
|
6788
|
+
pendingTradingVolume,
|
|
6789
|
+
trackedTokenCount: Number(trackedTokenCount)
|
|
6790
|
+
};
|
|
6791
|
+
} catch (error) {
|
|
6792
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6793
|
+
console.error("Failed to read vault status:", message);
|
|
6794
|
+
return {
|
|
6795
|
+
vaultAddress: this.vaultAddress,
|
|
6796
|
+
hasVault: true,
|
|
6797
|
+
totalAssets: 0n,
|
|
6798
|
+
totalSupply: 0n,
|
|
6799
|
+
sharePrice: 0,
|
|
6800
|
+
usdcBalance: 0n,
|
|
6801
|
+
cachedCtfValue: 0n,
|
|
6802
|
+
currentExposureBps: 0,
|
|
6803
|
+
depositCap: 0n,
|
|
6804
|
+
seedAmount: 0n,
|
|
6805
|
+
circuitBreakerActive: false,
|
|
6806
|
+
isPaused: false,
|
|
6807
|
+
lastCtfUpdate: 0,
|
|
6808
|
+
pendingTradingVolume: 0n,
|
|
6809
|
+
trackedTokenCount: 0
|
|
6810
|
+
};
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
// ============================================================
|
|
6814
|
+
// KEEPER: NAV UPDATES
|
|
6815
|
+
// ============================================================
|
|
6816
|
+
/**
|
|
6817
|
+
* Update cached CTF position values (keeper function).
|
|
6818
|
+
* Fetches CLOB midpoint prices for all tracked tokens and updates the vault.
|
|
6819
|
+
*
|
|
6820
|
+
* @param tokenIds - CTF token IDs
|
|
6821
|
+
* @param values - USD values in 6-decimal (midpoint price × balance for each token)
|
|
6822
|
+
*/
|
|
6823
|
+
async updatePositionValues(tokenIds, values) {
|
|
6824
|
+
if (!this.vaultAddress) {
|
|
6825
|
+
return { success: false, error: "No vault exists" };
|
|
6826
|
+
}
|
|
6827
|
+
try {
|
|
6828
|
+
const hash = await this.walletClient.writeContract({
|
|
6829
|
+
address: this.vaultAddress,
|
|
6830
|
+
abi: VAULT_ABI2,
|
|
6831
|
+
functionName: "updatePositionValues",
|
|
6832
|
+
args: [tokenIds, values]
|
|
6833
|
+
});
|
|
6834
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6835
|
+
console.log(`Updated ${tokenIds.length} position values in prediction vault`);
|
|
6836
|
+
return { success: true, txHash: hash };
|
|
6837
|
+
} catch (error) {
|
|
6838
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6839
|
+
console.error("Failed to update position values:", message);
|
|
6840
|
+
return { success: false, error: message };
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
/**
|
|
6844
|
+
* Collect trading fees based on reported volume (keeper function).
|
|
6845
|
+
* Transfers 0.2% of volume as fees to PredictionFeeCollector.
|
|
6846
|
+
*
|
|
6847
|
+
* @param volume - Trading volume to report (6-decimal USDC)
|
|
6848
|
+
*/
|
|
6849
|
+
async collectTradingFees(volume) {
|
|
6850
|
+
if (!this.vaultAddress) {
|
|
6851
|
+
return { success: false, error: "No vault exists" };
|
|
6852
|
+
}
|
|
6853
|
+
try {
|
|
6854
|
+
const hash = await this.walletClient.writeContract({
|
|
6855
|
+
address: this.vaultAddress,
|
|
6856
|
+
abi: VAULT_ABI2,
|
|
6857
|
+
functionName: "collectTradingFees",
|
|
6858
|
+
args: [volume]
|
|
6859
|
+
});
|
|
6860
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6861
|
+
const feeAmount = volume * 20n / 10000n;
|
|
6862
|
+
console.log(`Collected trading fees: ${(0, import_viem10.formatUnits)(feeAmount, 6)} USDC from ${(0, import_viem10.formatUnits)(volume, 6)} USDC volume`);
|
|
6863
|
+
return { success: true, txHash: hash };
|
|
6864
|
+
} catch (error) {
|
|
6865
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6866
|
+
console.error("Failed to collect trading fees:", message);
|
|
6867
|
+
return { success: false, error: message };
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
// ============================================================
|
|
6871
|
+
// KEEPER LOOPS
|
|
6872
|
+
// ============================================================
|
|
6873
|
+
/**
|
|
6874
|
+
* Start the keeper loops (NAV updates + fee collection).
|
|
6875
|
+
* These run on timers and automatically update vault state.
|
|
6876
|
+
*
|
|
6877
|
+
* @param getPositionValues - Callback that returns current CTF position values
|
|
6878
|
+
* (token IDs and their midpoint-price USD values)
|
|
6879
|
+
* @param getAccumulatedVolume - Callback that returns accumulated trading volume
|
|
6880
|
+
* since last fee collection
|
|
6881
|
+
*/
|
|
6882
|
+
startKeeperLoops(getPositionValues, getAccumulatedVolume) {
|
|
6883
|
+
if (!this.vaultAddress) {
|
|
6884
|
+
console.log("Cannot start keeper loops \u2014 no vault exists");
|
|
6885
|
+
return;
|
|
6886
|
+
}
|
|
6887
|
+
const navInterval = this.config.navUpdateIntervalMs ?? 15 * 60 * 1e3;
|
|
6888
|
+
this.navTimer = setInterval(async () => {
|
|
6889
|
+
try {
|
|
6890
|
+
const { tokenIds, values } = await getPositionValues();
|
|
6891
|
+
if (tokenIds.length > 0) {
|
|
6892
|
+
await this.updatePositionValues(tokenIds, values);
|
|
6893
|
+
}
|
|
6894
|
+
} catch (error) {
|
|
6895
|
+
console.error("Keeper NAV update failed:", error instanceof Error ? error.message : error);
|
|
6896
|
+
}
|
|
6897
|
+
}, navInterval);
|
|
6898
|
+
const feeInterval = this.config.feeCollectionIntervalMs ?? 24 * 60 * 60 * 1e3;
|
|
6899
|
+
this.feeTimer = setInterval(async () => {
|
|
6900
|
+
try {
|
|
6901
|
+
const currentVolume = getAccumulatedVolume();
|
|
6902
|
+
const volumeDelta = currentVolume - this.lastReportedVolume;
|
|
6903
|
+
if (volumeDelta > 0n) {
|
|
6904
|
+
await this.collectTradingFees(volumeDelta);
|
|
6905
|
+
this.lastReportedVolume = currentVolume;
|
|
6906
|
+
}
|
|
6907
|
+
} catch (error) {
|
|
6908
|
+
console.error("Keeper fee collection failed:", error instanceof Error ? error.message : error);
|
|
6909
|
+
}
|
|
6910
|
+
}, feeInterval);
|
|
6911
|
+
console.log(`Keeper loops started (NAV: every ${navInterval / 6e4}min, fees: every ${feeInterval / 36e5}h)`);
|
|
6912
|
+
}
|
|
6913
|
+
/**
|
|
6914
|
+
* Stop keeper loops.
|
|
6915
|
+
*/
|
|
6916
|
+
stopKeeperLoops() {
|
|
6917
|
+
if (this.navTimer) {
|
|
6918
|
+
clearInterval(this.navTimer);
|
|
6919
|
+
this.navTimer = null;
|
|
6920
|
+
}
|
|
6921
|
+
if (this.feeTimer) {
|
|
6922
|
+
clearInterval(this.feeTimer);
|
|
6923
|
+
this.feeTimer = null;
|
|
6924
|
+
}
|
|
6925
|
+
console.log("Keeper loops stopped");
|
|
6926
|
+
}
|
|
6927
|
+
// ============================================================
|
|
6928
|
+
// CLEANUP
|
|
6929
|
+
// ============================================================
|
|
6930
|
+
/**
|
|
6931
|
+
* Stop all timers and clean up.
|
|
6932
|
+
*/
|
|
6933
|
+
stop() {
|
|
6934
|
+
this.stopKeeperLoops();
|
|
6935
|
+
}
|
|
6936
|
+
// ============================================================
|
|
6937
|
+
// PRIVATE HELPERS
|
|
6938
|
+
// ============================================================
|
|
6939
|
+
/** Approve USDC spend if needed (maxUint256 pattern) */
|
|
6940
|
+
async approveIfNeeded(token, spender, amount) {
|
|
6941
|
+
const allowance = await this.publicClient.readContract({
|
|
6942
|
+
address: token,
|
|
6943
|
+
abi: ERC20_ABI3,
|
|
6944
|
+
functionName: "allowance",
|
|
6945
|
+
args: [this.account.address, spender]
|
|
6946
|
+
});
|
|
6947
|
+
if (allowance < amount) {
|
|
6948
|
+
const maxUint256 = 2n ** 256n - 1n;
|
|
6949
|
+
const hash = await this.walletClient.writeContract({
|
|
6950
|
+
address: token,
|
|
6951
|
+
abi: ERC20_ABI3,
|
|
6952
|
+
functionName: "approve",
|
|
6953
|
+
args: [spender, maxUint256]
|
|
6954
|
+
});
|
|
6955
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
};
|
|
6959
|
+
|
|
6960
|
+
// src/runtime.ts
|
|
6961
|
+
var FUNDS_LOW_THRESHOLD = 5e-3;
|
|
6962
|
+
var FUNDS_CRITICAL_THRESHOLD = 1e-3;
|
|
6963
|
+
var AgentRuntime = class {
|
|
6964
|
+
config;
|
|
6965
|
+
client;
|
|
6966
|
+
llm;
|
|
6967
|
+
strategy;
|
|
6968
|
+
executor;
|
|
6969
|
+
riskManager;
|
|
6970
|
+
marketData;
|
|
6971
|
+
vaultManager;
|
|
6972
|
+
relay = null;
|
|
6973
|
+
isRunning = false;
|
|
6974
|
+
mode = "idle";
|
|
6975
|
+
configHash;
|
|
6976
|
+
pendingConfigHash = null;
|
|
6977
|
+
lastConfigCheckAt = 0;
|
|
6978
|
+
// Timestamp of last pending config RPC check
|
|
6979
|
+
cycleCount = 0;
|
|
6980
|
+
lastCycleAt = 0;
|
|
6981
|
+
lastPortfolioValue = 0;
|
|
6982
|
+
lastEthBalance = "0";
|
|
6983
|
+
lastPrices = {};
|
|
6984
|
+
processAlive = true;
|
|
6985
|
+
riskUniverse = 0;
|
|
6986
|
+
allowedTokens = /* @__PURE__ */ new Set();
|
|
6987
|
+
strategyContext;
|
|
6988
|
+
positionTracker;
|
|
6989
|
+
// Paper trading components (null when not in paper mode)
|
|
6990
|
+
paperExecutor = null;
|
|
6991
|
+
paperPortfolio = null;
|
|
6992
|
+
/** Whether agent was started directly in paper mode via CLI */
|
|
6993
|
+
startInPaperMode = false;
|
|
6994
|
+
// Perp trading components (null if perp not enabled)
|
|
6995
|
+
perpClient = null;
|
|
6996
|
+
perpSigner = null;
|
|
6997
|
+
perpOrders = null;
|
|
6998
|
+
perpPositions = null;
|
|
6999
|
+
perpWebSocket = null;
|
|
7000
|
+
perpRecorder = null;
|
|
7001
|
+
perpOnboarding = null;
|
|
7002
|
+
perpStrategy = null;
|
|
7003
|
+
// Two-layer perp control:
|
|
7004
|
+
// perpConnected = Hyperliquid infrastructure is initialized (WS, signer, recorder ready)
|
|
7005
|
+
// perpTradingActive = Dedicated perp trading cycle is mandated to run
|
|
7006
|
+
// When perpConnected && !perpTradingActive: agent's strategy can optionally return perp signals
|
|
7007
|
+
// When perpConnected && perpTradingActive: dedicated runPerpCycle() runs every interval
|
|
7008
|
+
perpConnected = false;
|
|
7009
|
+
perpTradingActive = false;
|
|
7010
|
+
// Cached perp account data for synchronous heartbeat inclusion (refreshed async)
|
|
7011
|
+
cachedPerpEquity = 0;
|
|
7012
|
+
cachedPerpUnrealizedPnl = 0;
|
|
7013
|
+
cachedPerpMarginUsed = 0;
|
|
7014
|
+
cachedPerpLeverage = 0;
|
|
7015
|
+
cachedPerpOpenPositions = 0;
|
|
7016
|
+
// Prediction market components (null if prediction not enabled)
|
|
7017
|
+
predictionClient = null;
|
|
7018
|
+
predictionOrders = null;
|
|
7019
|
+
predictionPositions = null;
|
|
7020
|
+
predictionRecorder = null;
|
|
7021
|
+
predictionFunding = null;
|
|
7022
|
+
predictionBrowser = null;
|
|
7023
|
+
predictionStrategy = null;
|
|
7024
|
+
// Two-layer prediction control (mirrors perp pattern):
|
|
7025
|
+
// predictionConnected = Polymarket infrastructure initialized (CLOB client, recorder ready)
|
|
7026
|
+
// predictionTradingActive = Dedicated prediction trading cycle is mandated to run
|
|
7027
|
+
predictionConnected = false;
|
|
7028
|
+
predictionTradingActive = false;
|
|
7029
|
+
// Cached prediction data for synchronous heartbeat inclusion
|
|
7030
|
+
cachedPredictionPolygonUSDC = 0;
|
|
7031
|
+
cachedPredictionBaseUSDC = 0;
|
|
7032
|
+
cachedPredictionExposure = 0;
|
|
7033
|
+
cachedPredictionOpenMarkets = 0;
|
|
7034
|
+
cachedPredictionOpenPositions = 0;
|
|
7035
|
+
cachedPredictionUnrealizedPnl = 0;
|
|
7036
|
+
// Prediction vault manager (null if vault mode not enabled)
|
|
7037
|
+
predictionVaultManager = null;
|
|
7038
|
+
// Cached vault status for synchronous heartbeat
|
|
7039
|
+
cachedPredictionVaultStatus = void 0;
|
|
7040
|
+
constructor(config, options) {
|
|
7041
|
+
this.config = config;
|
|
7042
|
+
this.startInPaperMode = options?.paperMode ?? false;
|
|
7043
|
+
this._paperInitialBalances = options?.paperBalances;
|
|
7044
|
+
}
|
|
7045
|
+
/** Initial balances override for paper mode (from CLI --initial-eth/--initial-usdc) */
|
|
7046
|
+
_paperInitialBalances;
|
|
7047
|
+
/**
|
|
7048
|
+
* Initialize the agent runtime
|
|
7049
|
+
*/
|
|
7050
|
+
async initialize() {
|
|
7051
|
+
console.log(`Initializing agent: ${this.config.name} (ID: ${this.config.agentId})`);
|
|
7052
|
+
this.client = new import_sdk.ExagentClient({
|
|
7053
|
+
privateKey: this.config.privateKey,
|
|
7054
|
+
network: this.config.network
|
|
7055
|
+
});
|
|
7056
|
+
console.log(`Wallet: ${this.client.address}`);
|
|
7057
|
+
const agent = await this.client.registry.getAgent(BigInt(this.config.agentId));
|
|
7058
|
+
if (!agent) {
|
|
7059
|
+
throw new Error(`Agent ID ${this.config.agentId} not found on-chain. Please register first.`);
|
|
7060
|
+
}
|
|
7061
|
+
console.log(`Agent verified: ${agent.name}`);
|
|
7062
|
+
await this.ensureWalletLinked();
|
|
7063
|
+
await this.loadTradingRestrictions();
|
|
7064
|
+
console.log(`Initializing LLM: ${this.config.llm.provider}`);
|
|
7065
|
+
this.llm = await createLLMAdapter(this.config.llm);
|
|
7066
|
+
const llmMeta = this.llm.getMetadata();
|
|
7067
|
+
console.log(`LLM ready: ${llmMeta.provider} (${llmMeta.model})`);
|
|
7068
|
+
await this.syncConfigHash();
|
|
7069
|
+
this.strategy = await loadStrategy();
|
|
7070
|
+
const store = new FileStore();
|
|
7071
|
+
this.strategyContext = {
|
|
7072
|
+
store,
|
|
7073
|
+
agentId: Number(this.config.agentId),
|
|
7074
|
+
walletAddress: this.client.address
|
|
7075
|
+
};
|
|
7076
|
+
this.positionTracker = new PositionTracker(store, { maxTradeHistory: 50 });
|
|
7077
|
+
this.executor = new TradeExecutor(this.client, this.config, () => this.getConfigHash());
|
|
7078
|
+
this.riskManager = new RiskManager(this.config.trading, this.riskUniverse);
|
|
7079
|
+
this.marketData = new MarketDataService(this.getRpcUrl(), store);
|
|
7080
|
+
setGlobalResolver(this.marketData.getResolver());
|
|
7081
|
+
const savedRisk = this.positionTracker.getRiskState();
|
|
7082
|
+
if (savedRisk.lastResetDate) {
|
|
7083
|
+
this.riskManager.restoreState(savedRisk);
|
|
7084
|
+
}
|
|
7085
|
+
await this.initializeVaultManager();
|
|
7086
|
+
await this.initializePerp();
|
|
7087
|
+
await this.initializePrediction();
|
|
7088
|
+
await this.initializeRelay();
|
|
7089
|
+
console.log("Agent initialized successfully");
|
|
7090
|
+
}
|
|
7091
|
+
/**
|
|
7092
|
+
* Initialize the relay client for command center connectivity
|
|
7093
|
+
*/
|
|
7094
|
+
async initializeRelay() {
|
|
7095
|
+
const relayConfig = this.config.relay;
|
|
7096
|
+
const relayEnabled = process.env.EXAGENT_RELAY_ENABLED !== "false";
|
|
7097
|
+
if (!relayConfig?.enabled || !relayEnabled) {
|
|
7098
|
+
console.log("Relay: Disabled");
|
|
7099
|
+
return;
|
|
7100
|
+
}
|
|
7101
|
+
const apiUrl = process.env.EXAGENT_API_URL || relayConfig.apiUrl;
|
|
7102
|
+
if (!apiUrl) {
|
|
7103
|
+
console.log("Relay: No API URL configured, skipping");
|
|
7104
|
+
return;
|
|
7105
|
+
}
|
|
7106
|
+
this.relay = new RelayClient({
|
|
7107
|
+
agentId: String(this.config.agentId),
|
|
7108
|
+
privateKey: this.config.privateKey,
|
|
7109
|
+
relay: {
|
|
7110
|
+
...relayConfig,
|
|
7111
|
+
apiUrl
|
|
7112
|
+
},
|
|
7113
|
+
onCommand: (cmd) => this.handleCommand(cmd)
|
|
7114
|
+
});
|
|
7115
|
+
try {
|
|
7116
|
+
await this.relay.connect();
|
|
7117
|
+
console.log("Relay: Connected to command center");
|
|
7118
|
+
this.sendRelayStatus();
|
|
7119
|
+
} catch (error) {
|
|
7120
|
+
console.warn(
|
|
7121
|
+
"Relay: Failed to connect (agent will work locally):",
|
|
7122
|
+
error instanceof Error ? error.message : error
|
|
7123
|
+
);
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
/**
|
|
7127
|
+
* Initialize the vault manager based on config
|
|
7128
|
+
*/
|
|
7129
|
+
async initializeVaultManager() {
|
|
7130
|
+
const vaultConfig = this.config.vault || { policy: "disabled", preferVaultTrading: false };
|
|
7131
|
+
this.vaultManager = new VaultManager({
|
|
7132
|
+
agentId: BigInt(this.config.agentId),
|
|
7133
|
+
agentName: this.config.name,
|
|
7134
|
+
network: this.config.network,
|
|
7135
|
+
walletKey: this.config.privateKey,
|
|
7136
|
+
vaultConfig
|
|
7137
|
+
});
|
|
7138
|
+
console.log(`Vault policy: ${vaultConfig.policy}`);
|
|
7139
|
+
const status = await this.vaultManager.getVaultStatus();
|
|
7140
|
+
if (status.hasVault) {
|
|
7141
|
+
console.log(`Vault exists: ${status.vaultAddress}`);
|
|
7142
|
+
console.log(`Vault TVL: ${Number(status.totalAssets) / 1e6} USDC`);
|
|
7143
|
+
} else {
|
|
7144
|
+
console.log("No vault exists for this agent");
|
|
7145
|
+
if (vaultConfig.policy === "manual") {
|
|
7146
|
+
console.log("Vault creation is manual \u2014 use the command center to create one");
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
}
|
|
7150
|
+
/**
|
|
7151
|
+
* Initialize Hyperliquid perp trading components.
|
|
7152
|
+
* Only initializes if perp is enabled in config AND risk universe >= 2.
|
|
7153
|
+
*/
|
|
7154
|
+
async initializePerp() {
|
|
7155
|
+
const perpConfig = this.config.perp;
|
|
7156
|
+
if (!perpConfig?.enabled) {
|
|
7157
|
+
console.log("Perp trading: Disabled");
|
|
7158
|
+
return;
|
|
7159
|
+
}
|
|
7160
|
+
if (this.riskUniverse < 2) {
|
|
7161
|
+
console.warn(`Perp trading: Blocked by risk universe ${this.riskUniverse} (need >= 2)`);
|
|
7162
|
+
return;
|
|
5168
7163
|
}
|
|
5169
7164
|
try {
|
|
5170
7165
|
const config = {
|
|
@@ -5179,10 +7174,10 @@ var AgentRuntime = class {
|
|
|
5179
7174
|
};
|
|
5180
7175
|
this.perpClient = new HyperliquidClient(config);
|
|
5181
7176
|
const perpKey = perpConfig.perpRelayerKey || this.config.privateKey;
|
|
5182
|
-
const account = (0,
|
|
5183
|
-
const walletClient = (0,
|
|
7177
|
+
const account = (0, import_accounts8.privateKeyToAccount)(perpKey);
|
|
7178
|
+
const walletClient = (0, import_viem11.createWalletClient)({
|
|
5184
7179
|
chain: { id: 42161, name: "Arbitrum", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://arb1.arbitrum.io/rpc"] } } },
|
|
5185
|
-
transport: (0,
|
|
7180
|
+
transport: (0, import_viem11.http)("https://arb1.arbitrum.io/rpc", { timeout: 6e4 }),
|
|
5186
7181
|
account
|
|
5187
7182
|
});
|
|
5188
7183
|
this.perpSigner = new HyperliquidSigner(walletClient);
|
|
@@ -5258,6 +7253,101 @@ var AgentRuntime = class {
|
|
|
5258
7253
|
console.warn("Perp trading will be disabled for this session");
|
|
5259
7254
|
}
|
|
5260
7255
|
}
|
|
7256
|
+
/**
|
|
7257
|
+
* Initialize Polymarket prediction market components.
|
|
7258
|
+
* Only initializes if prediction is enabled in config AND risk universe >= 2.
|
|
7259
|
+
*/
|
|
7260
|
+
async initializePrediction() {
|
|
7261
|
+
const predictionConfig = this.config.prediction;
|
|
7262
|
+
if (!predictionConfig?.enabled) {
|
|
7263
|
+
console.log("Prediction markets: Disabled");
|
|
7264
|
+
return;
|
|
7265
|
+
}
|
|
7266
|
+
if (this.riskUniverse < 2) {
|
|
7267
|
+
console.warn(`Prediction markets: Blocked by risk universe ${this.riskUniverse} (need >= 2)`);
|
|
7268
|
+
return;
|
|
7269
|
+
}
|
|
7270
|
+
try {
|
|
7271
|
+
const config = {
|
|
7272
|
+
enabled: true,
|
|
7273
|
+
clobApiUrl: predictionConfig.clobApiUrl || "https://clob.polymarket.com",
|
|
7274
|
+
gammaApiUrl: predictionConfig.gammaApiUrl || "https://gamma-api.polymarket.com",
|
|
7275
|
+
polygonRpcUrl: predictionConfig.polygonRpcUrl || "https://polygon-rpc.com",
|
|
7276
|
+
maxNotionalUSD: predictionConfig.maxNotionalUSD ?? 500,
|
|
7277
|
+
maxTotalExposureUSD: predictionConfig.maxTotalExposureUSD ?? 5e3,
|
|
7278
|
+
polygonAllocationUSD: predictionConfig.polygonAllocationUSD ?? 100,
|
|
7279
|
+
allowedCategories: predictionConfig.allowedCategories,
|
|
7280
|
+
feeBridgeThresholdUSD: predictionConfig.feeBridgeThresholdUSD ?? 10
|
|
7281
|
+
};
|
|
7282
|
+
const vaultConfig = predictionConfig.vault;
|
|
7283
|
+
let vaultOpts;
|
|
7284
|
+
if (vaultConfig?.enabled && vaultConfig.factoryAddress) {
|
|
7285
|
+
this.predictionVaultManager = new PredictionVaultManager(
|
|
7286
|
+
this.config.privateKey,
|
|
7287
|
+
{
|
|
7288
|
+
enabled: true,
|
|
7289
|
+
factoryAddress: vaultConfig.factoryAddress,
|
|
7290
|
+
feeCollectorAddress: vaultConfig.feeCollectorAddress || "0x0000000000000000000000000000000000000000",
|
|
7291
|
+
polygonRpcUrl: predictionConfig.polygonRpcUrl || "https://polygon-rpc.com",
|
|
7292
|
+
vaultAddress: vaultConfig.vaultAddress,
|
|
7293
|
+
agentId: BigInt(this.config.agentId),
|
|
7294
|
+
feeRecipient: vaultConfig.feeRecipient || this.client.address,
|
|
7295
|
+
isVerified: vaultConfig.isVerified ?? false
|
|
7296
|
+
}
|
|
7297
|
+
);
|
|
7298
|
+
await this.predictionVaultManager.initialize();
|
|
7299
|
+
if (this.predictionVaultManager.hasVault) {
|
|
7300
|
+
const vaultAddr = this.predictionVaultManager.getVaultAddress();
|
|
7301
|
+
vaultOpts = { vaultMode: true, vaultAddress: vaultAddr };
|
|
7302
|
+
console.log(` Vault mode: ENABLED \u2014 vault ${vaultAddr}`);
|
|
7303
|
+
} else {
|
|
7304
|
+
console.log(" Vault mode: configured but no vault exists \u2014 create one via command center");
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
this.predictionClient = new PolymarketClient(this.config.privateKey, config, vaultOpts);
|
|
7308
|
+
await this.predictionClient.initialize();
|
|
7309
|
+
this.predictionOrders = new PredictionOrderManager(this.predictionClient, config);
|
|
7310
|
+
this.predictionPositions = new PredictionPositionManager(this.predictionClient, config);
|
|
7311
|
+
this.predictionBrowser = new MarketBrowser(config);
|
|
7312
|
+
const recorderKey = predictionConfig.predictionRelayerKey || this.config.privateKey;
|
|
7313
|
+
this.predictionRecorder = new PredictionTradeRecorder({
|
|
7314
|
+
privateKey: recorderKey,
|
|
7315
|
+
rpcUrl: this.getRpcUrl(),
|
|
7316
|
+
agentId: BigInt(this.config.agentId),
|
|
7317
|
+
configHash: this.configHash
|
|
7318
|
+
});
|
|
7319
|
+
this.predictionFunding = new PredictionFunding({
|
|
7320
|
+
privateKey: this.config.privateKey,
|
|
7321
|
+
baseRpcUrl: this.getRpcUrl(),
|
|
7322
|
+
polygonRpcUrl: predictionConfig.polygonRpcUrl,
|
|
7323
|
+
...vaultOpts?.vaultMode && vaultOpts?.vaultAddress && {
|
|
7324
|
+
polygonRecipient: vaultOpts.vaultAddress
|
|
7325
|
+
}
|
|
7326
|
+
});
|
|
7327
|
+
this.predictionConnected = true;
|
|
7328
|
+
console.log(`Polymarket: Connected`);
|
|
7329
|
+
console.log(` CLOB: ${config.clobApiUrl}`);
|
|
7330
|
+
console.log(` Max notional: $${config.maxNotionalUSD}, Max exposure: $${config.maxTotalExposureUSD}`);
|
|
7331
|
+
if (config.allowedCategories?.length) {
|
|
7332
|
+
console.log(` Categories: ${config.allowedCategories.join(", ")}`);
|
|
7333
|
+
}
|
|
7334
|
+
try {
|
|
7335
|
+
const polygonBalance = await this.predictionFunding.getPolygonUSDCBalance();
|
|
7336
|
+
const polygonUSDC = Number(polygonBalance) / 1e6;
|
|
7337
|
+
this.cachedPredictionPolygonUSDC = polygonUSDC;
|
|
7338
|
+
console.log(` Polygon USDC: $${polygonUSDC.toFixed(2)}`);
|
|
7339
|
+
if (polygonUSDC < 1) {
|
|
7340
|
+
console.warn(" No USDC on Polygon \u2014 bridge required before trading");
|
|
7341
|
+
}
|
|
7342
|
+
} catch {
|
|
7343
|
+
console.warn(" Could not check Polygon USDC balance");
|
|
7344
|
+
}
|
|
7345
|
+
} catch (error) {
|
|
7346
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7347
|
+
console.error(`Prediction initialization failed: ${message}`);
|
|
7348
|
+
console.warn("Prediction market trading will be disabled for this session");
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
5261
7351
|
/**
|
|
5262
7352
|
* Ensure the current wallet is linked to the agent.
|
|
5263
7353
|
* If the trading wallet differs from the owner, enters a recovery loop
|
|
@@ -5422,9 +7512,9 @@ var AgentRuntime = class {
|
|
|
5422
7512
|
const message = error instanceof Error ? error.message : String(error);
|
|
5423
7513
|
if (message.includes("insufficient funds") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
|
|
5424
7514
|
const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
|
|
5425
|
-
const publicClientInstance = (0,
|
|
5426
|
-
chain:
|
|
5427
|
-
transport: (0,
|
|
7515
|
+
const publicClientInstance = (0, import_viem11.createPublicClient)({
|
|
7516
|
+
chain: import_chains8.base,
|
|
7517
|
+
transport: (0, import_viem11.http)(this.getRpcUrl(), { timeout: 6e4 })
|
|
5428
7518
|
});
|
|
5429
7519
|
console.log("");
|
|
5430
7520
|
console.log("=== ETH NEEDED FOR GAS ===");
|
|
@@ -5792,6 +7882,224 @@ var AgentRuntime = class {
|
|
|
5792
7882
|
}
|
|
5793
7883
|
break;
|
|
5794
7884
|
}
|
|
7885
|
+
case "enable_polymarket":
|
|
7886
|
+
if (this.predictionConnected) {
|
|
7887
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket already connected");
|
|
7888
|
+
break;
|
|
7889
|
+
}
|
|
7890
|
+
if (!this.config.prediction?.enabled) {
|
|
7891
|
+
this.relay?.sendCommandResult(cmd.id, false, "Prediction trading not configured in agent config");
|
|
7892
|
+
break;
|
|
7893
|
+
}
|
|
7894
|
+
if (this.riskUniverse < 2) {
|
|
7895
|
+
this.relay?.sendCommandResult(cmd.id, false, `Risk universe ${this.riskUniverse} too low (need >= 2)`);
|
|
7896
|
+
break;
|
|
7897
|
+
}
|
|
7898
|
+
try {
|
|
7899
|
+
await this.initializePrediction();
|
|
7900
|
+
if (this.predictionConnected) {
|
|
7901
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket connected");
|
|
7902
|
+
this.relay?.sendMessage(
|
|
7903
|
+
"system",
|
|
7904
|
+
"success",
|
|
7905
|
+
"Polymarket Enabled",
|
|
7906
|
+
'Polymarket infrastructure connected. Use "Start Prediction Trading" to begin a dedicated prediction cycle.'
|
|
7907
|
+
);
|
|
7908
|
+
} else {
|
|
7909
|
+
this.relay?.sendCommandResult(cmd.id, false, "Failed to connect to Polymarket");
|
|
7910
|
+
}
|
|
7911
|
+
} catch (error) {
|
|
7912
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
7913
|
+
this.relay?.sendCommandResult(cmd.id, false, `Polymarket init failed: ${msg}`);
|
|
7914
|
+
}
|
|
7915
|
+
this.sendRelayStatus();
|
|
7916
|
+
break;
|
|
7917
|
+
case "disable_polymarket":
|
|
7918
|
+
if (!this.predictionConnected) {
|
|
7919
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket already disconnected");
|
|
7920
|
+
break;
|
|
7921
|
+
}
|
|
7922
|
+
this.predictionTradingActive = false;
|
|
7923
|
+
this.predictionConnected = false;
|
|
7924
|
+
this.predictionClient = null;
|
|
7925
|
+
this.predictionOrders = null;
|
|
7926
|
+
this.predictionPositions = null;
|
|
7927
|
+
this.predictionRecorder = null;
|
|
7928
|
+
this.predictionFunding = null;
|
|
7929
|
+
this.predictionBrowser = null;
|
|
7930
|
+
console.log("Polymarket disabled via command center");
|
|
7931
|
+
this.relay?.sendCommandResult(cmd.id, true, "Polymarket disconnected");
|
|
7932
|
+
this.relay?.sendMessage(
|
|
7933
|
+
"system",
|
|
7934
|
+
"info",
|
|
7935
|
+
"Polymarket Disabled",
|
|
7936
|
+
"Polymarket infrastructure disconnected."
|
|
7937
|
+
);
|
|
7938
|
+
this.sendRelayStatus();
|
|
7939
|
+
break;
|
|
7940
|
+
case "start_prediction_trading":
|
|
7941
|
+
if (!this.predictionConnected) {
|
|
7942
|
+
this.relay?.sendCommandResult(cmd.id, false, "Polymarket not connected. Enable Polymarket first.");
|
|
7943
|
+
break;
|
|
7944
|
+
}
|
|
7945
|
+
if (this.predictionTradingActive) {
|
|
7946
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading already active");
|
|
7947
|
+
break;
|
|
7948
|
+
}
|
|
7949
|
+
this.predictionTradingActive = true;
|
|
7950
|
+
if (this.mode !== "trading") {
|
|
7951
|
+
this.mode = "trading";
|
|
7952
|
+
this.isRunning = true;
|
|
7953
|
+
}
|
|
7954
|
+
console.log("Prediction trading mandated via command center");
|
|
7955
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading cycle active");
|
|
7956
|
+
this.relay?.sendMessage(
|
|
7957
|
+
"system",
|
|
7958
|
+
"success",
|
|
7959
|
+
"Prediction Trading Active",
|
|
7960
|
+
"Dedicated prediction trading cycle is now running every interval."
|
|
7961
|
+
);
|
|
7962
|
+
this.sendRelayStatus();
|
|
7963
|
+
break;
|
|
7964
|
+
case "stop_prediction_trading":
|
|
7965
|
+
if (!this.predictionTradingActive) {
|
|
7966
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading already stopped");
|
|
7967
|
+
break;
|
|
7968
|
+
}
|
|
7969
|
+
this.predictionTradingActive = false;
|
|
7970
|
+
console.log("Prediction trading cycle stopped via command center");
|
|
7971
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction trading cycle stopped");
|
|
7972
|
+
this.relay?.sendMessage(
|
|
7973
|
+
"system",
|
|
7974
|
+
"info",
|
|
7975
|
+
"Prediction Trading Stopped",
|
|
7976
|
+
"Dedicated prediction cycle stopped. Polymarket remains connected."
|
|
7977
|
+
);
|
|
7978
|
+
this.sendRelayStatus();
|
|
7979
|
+
break;
|
|
7980
|
+
case "update_prediction_params": {
|
|
7981
|
+
const predParams = cmd.params || {};
|
|
7982
|
+
let predUpdated = false;
|
|
7983
|
+
if (this.config.prediction && predParams.maxNotionalUSD !== void 0) {
|
|
7984
|
+
const val = Number(predParams.maxNotionalUSD);
|
|
7985
|
+
if (val >= 1) {
|
|
7986
|
+
this.config.prediction.maxNotionalUSD = val;
|
|
7987
|
+
predUpdated = true;
|
|
7988
|
+
}
|
|
7989
|
+
}
|
|
7990
|
+
if (this.config.prediction && predParams.maxTotalExposureUSD !== void 0) {
|
|
7991
|
+
const val = Number(predParams.maxTotalExposureUSD);
|
|
7992
|
+
if (val >= 1) {
|
|
7993
|
+
this.config.prediction.maxTotalExposureUSD = val;
|
|
7994
|
+
predUpdated = true;
|
|
7995
|
+
}
|
|
7996
|
+
}
|
|
7997
|
+
if (predUpdated) {
|
|
7998
|
+
this.relay?.sendCommandResult(cmd.id, true, "Prediction parameters updated");
|
|
7999
|
+
this.relay?.sendMessage(
|
|
8000
|
+
"config_updated",
|
|
8001
|
+
"info",
|
|
8002
|
+
"Prediction Params Updated",
|
|
8003
|
+
`Max notional: $${this.config.prediction?.maxNotionalUSD}, Max exposure: $${this.config.prediction?.maxTotalExposureUSD}`
|
|
8004
|
+
);
|
|
8005
|
+
} else {
|
|
8006
|
+
this.relay?.sendCommandResult(cmd.id, false, "No valid prediction parameters provided");
|
|
8007
|
+
}
|
|
8008
|
+
break;
|
|
8009
|
+
}
|
|
8010
|
+
case "create_prediction_vault": {
|
|
8011
|
+
if (!this.predictionVaultManager) {
|
|
8012
|
+
this.relay?.sendCommandResult(cmd.id, false, "Vault mode not configured. Set prediction.vault config.");
|
|
8013
|
+
break;
|
|
8014
|
+
}
|
|
8015
|
+
if (this.predictionVaultManager.hasVault) {
|
|
8016
|
+
const addr = this.predictionVaultManager.getVaultAddress();
|
|
8017
|
+
this.relay?.sendCommandResult(cmd.id, true, `Vault already exists: ${addr}`);
|
|
8018
|
+
break;
|
|
8019
|
+
}
|
|
8020
|
+
try {
|
|
8021
|
+
const vaultParams = cmd.params || {};
|
|
8022
|
+
const seedAmount = BigInt(Number(vaultParams.seedAmount || 100) * 1e6);
|
|
8023
|
+
const name = vaultParams.name || `Prediction Fund ${this.config.agentId}`;
|
|
8024
|
+
const symbol = vaultParams.symbol || `pxAGT${this.config.agentId}`;
|
|
8025
|
+
const result = await this.predictionVaultManager.createVault(seedAmount, name, symbol);
|
|
8026
|
+
if (result.success) {
|
|
8027
|
+
this.relay?.sendCommandResult(cmd.id, true, `Vault created: ${result.vaultAddress}`);
|
|
8028
|
+
this.relay?.sendMessage(
|
|
8029
|
+
"prediction_vault_created",
|
|
8030
|
+
"success",
|
|
8031
|
+
"Prediction Vault Created",
|
|
8032
|
+
`Vault deployed at ${result.vaultAddress} with ${Number(seedAmount) / 1e6} USDC seed`,
|
|
8033
|
+
{ vaultAddress: result.vaultAddress, txHash: result.txHash }
|
|
8034
|
+
);
|
|
8035
|
+
} else {
|
|
8036
|
+
this.relay?.sendCommandResult(cmd.id, false, result.error || "Vault creation failed");
|
|
8037
|
+
}
|
|
8038
|
+
} catch (error) {
|
|
8039
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8040
|
+
this.relay?.sendCommandResult(cmd.id, false, `Vault creation failed: ${msg}`);
|
|
8041
|
+
}
|
|
8042
|
+
this.sendRelayStatus();
|
|
8043
|
+
break;
|
|
8044
|
+
}
|
|
8045
|
+
case "deposit_prediction_vault": {
|
|
8046
|
+
if (!this.predictionVaultManager?.hasVault) {
|
|
8047
|
+
this.relay?.sendCommandResult(cmd.id, false, "No prediction vault exists");
|
|
8048
|
+
break;
|
|
8049
|
+
}
|
|
8050
|
+
try {
|
|
8051
|
+
const amount = BigInt(Number(cmd.params?.amount || 0) * 1e6);
|
|
8052
|
+
if (amount <= 0n) {
|
|
8053
|
+
this.relay?.sendCommandResult(cmd.id, false, "Amount must be positive");
|
|
8054
|
+
break;
|
|
8055
|
+
}
|
|
8056
|
+
const result = await this.predictionVaultManager.deposit(amount);
|
|
8057
|
+
if (result.success) {
|
|
8058
|
+
this.relay?.sendCommandResult(cmd.id, true, `Deposited ${Number(amount) / 1e6} USDC`);
|
|
8059
|
+
this.relay?.sendMessage(
|
|
8060
|
+
"prediction_vault_deposit",
|
|
8061
|
+
"success",
|
|
8062
|
+
"Vault Deposit",
|
|
8063
|
+
`${Number(amount) / 1e6} USDC deposited into prediction vault`,
|
|
8064
|
+
{ amount: Number(amount) / 1e6, txHash: result.txHash }
|
|
8065
|
+
);
|
|
8066
|
+
} else {
|
|
8067
|
+
this.relay?.sendCommandResult(cmd.id, false, result.error || "Deposit failed");
|
|
8068
|
+
}
|
|
8069
|
+
} catch (error) {
|
|
8070
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8071
|
+
this.relay?.sendCommandResult(cmd.id, false, `Deposit failed: ${msg}`);
|
|
8072
|
+
}
|
|
8073
|
+
this.sendRelayStatus();
|
|
8074
|
+
break;
|
|
8075
|
+
}
|
|
8076
|
+
case "withdraw_prediction_vault": {
|
|
8077
|
+
if (!this.predictionVaultManager?.hasVault) {
|
|
8078
|
+
this.relay?.sendCommandResult(cmd.id, false, "No prediction vault exists");
|
|
8079
|
+
break;
|
|
8080
|
+
}
|
|
8081
|
+
try {
|
|
8082
|
+
const isEmergency = cmd.params?.emergency === true;
|
|
8083
|
+
const result = isEmergency ? await this.predictionVaultManager.emergencyWithdraw() : await this.predictionVaultManager.withdrawAll();
|
|
8084
|
+
if (result.success) {
|
|
8085
|
+
this.relay?.sendCommandResult(cmd.id, true, isEmergency ? "Emergency withdrawn (penalty applied)" : "Withdrawn all shares");
|
|
8086
|
+
this.relay?.sendMessage(
|
|
8087
|
+
"prediction_vault_withdraw",
|
|
8088
|
+
isEmergency ? "warning" : "success",
|
|
8089
|
+
isEmergency ? "Emergency Withdrawal" : "Vault Withdrawal",
|
|
8090
|
+
isEmergency ? "Emergency withdrawn with penalty" : "All shares redeemed from prediction vault",
|
|
8091
|
+
{ emergency: isEmergency, txHash: result.txHash }
|
|
8092
|
+
);
|
|
8093
|
+
} else {
|
|
8094
|
+
this.relay?.sendCommandResult(cmd.id, false, result.error || "Withdrawal failed");
|
|
8095
|
+
}
|
|
8096
|
+
} catch (error) {
|
|
8097
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8098
|
+
this.relay?.sendCommandResult(cmd.id, false, `Withdrawal failed: ${msg}`);
|
|
8099
|
+
}
|
|
8100
|
+
this.sendRelayStatus();
|
|
8101
|
+
break;
|
|
8102
|
+
}
|
|
5795
8103
|
case "start_paper_trading":
|
|
5796
8104
|
if (this.mode === "paper") {
|
|
5797
8105
|
this.relay?.sendCommandResult(cmd.id, true, "Already paper trading");
|
|
@@ -5919,7 +8227,7 @@ var AgentRuntime = class {
|
|
|
5919
8227
|
try {
|
|
5920
8228
|
const onChain = await this.client.registry.getConfigHash(BigInt(this.config.agentId));
|
|
5921
8229
|
if (onChain === this.pendingConfigHash) {
|
|
5922
|
-
this.
|
|
8230
|
+
this.updateConfigHashEverywhere(this.pendingConfigHash);
|
|
5923
8231
|
this.pendingConfigHash = null;
|
|
5924
8232
|
console.log("Config verified on-chain! Your agent will now appear on the leaderboard.");
|
|
5925
8233
|
this.relay?.sendMessage(
|
|
@@ -5932,6 +8240,34 @@ var AgentRuntime = class {
|
|
|
5932
8240
|
} catch {
|
|
5933
8241
|
}
|
|
5934
8242
|
}
|
|
8243
|
+
/**
|
|
8244
|
+
* Periodically sync config hash from on-chain to detect external changes.
|
|
8245
|
+
* This catches cases where the config was changed via the website or another
|
|
8246
|
+
* session while this agent is running. Without this, all trades and recordings
|
|
8247
|
+
* fail with ConfigMismatch until the agent is restarted.
|
|
8248
|
+
*
|
|
8249
|
+
* Called from sendRelayStatus at most every 5 minutes (timestamp-throttled).
|
|
8250
|
+
*/
|
|
8251
|
+
lastConfigSyncAt = 0;
|
|
8252
|
+
async syncConfigHashFromChain() {
|
|
8253
|
+
try {
|
|
8254
|
+
const onChain = await this.client.registry.getConfigHash(BigInt(this.config.agentId));
|
|
8255
|
+
if (onChain && onChain !== this.configHash && onChain !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
8256
|
+
console.log(`Config hash changed on-chain (external update detected): ${this.configHash} \u2192 ${onChain}`);
|
|
8257
|
+
this.updateConfigHashEverywhere(onChain);
|
|
8258
|
+
}
|
|
8259
|
+
} catch {
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
/**
|
|
8263
|
+
* Propagate a config hash update to runtime + all consumers (recorders).
|
|
8264
|
+
* Ensures executor, perp recorder, and prediction recorder all stay in sync.
|
|
8265
|
+
*/
|
|
8266
|
+
updateConfigHashEverywhere(newHash) {
|
|
8267
|
+
this.configHash = newHash;
|
|
8268
|
+
this.perpRecorder?.updateConfigHash(newHash);
|
|
8269
|
+
this.predictionRecorder?.updateConfigHash(newHash);
|
|
8270
|
+
}
|
|
5935
8271
|
/**
|
|
5936
8272
|
* Send current status to the relay
|
|
5937
8273
|
*/
|
|
@@ -5942,6 +8278,11 @@ var AgentRuntime = class {
|
|
|
5942
8278
|
this.lastConfigCheckAt = Date.now();
|
|
5943
8279
|
this.checkPendingConfigApproval();
|
|
5944
8280
|
}
|
|
8281
|
+
const CONFIG_SYNC_INTERVAL_MS = 3e5;
|
|
8282
|
+
if (!this.pendingConfigHash && Date.now() - this.lastConfigSyncAt >= CONFIG_SYNC_INTERVAL_MS) {
|
|
8283
|
+
this.lastConfigSyncAt = Date.now();
|
|
8284
|
+
this.syncConfigHashFromChain();
|
|
8285
|
+
}
|
|
5945
8286
|
const vaultConfig = this.config.vault || { policy: "disabled" };
|
|
5946
8287
|
const status = {
|
|
5947
8288
|
mode: this.mode,
|
|
@@ -5979,6 +8320,19 @@ var AgentRuntime = class {
|
|
|
5979
8320
|
effectiveLeverage: this.cachedPerpLeverage,
|
|
5980
8321
|
pendingRecords: this.perpRecorder?.pendingRetries ?? 0
|
|
5981
8322
|
} : void 0,
|
|
8323
|
+
prediction: this.predictionConnected ? {
|
|
8324
|
+
enabled: true,
|
|
8325
|
+
trading: this.predictionTradingActive,
|
|
8326
|
+
polygonUSDC: this.cachedPredictionPolygonUSDC,
|
|
8327
|
+
baseUSDC: this.cachedPredictionBaseUSDC,
|
|
8328
|
+
totalExposure: this.cachedPredictionExposure,
|
|
8329
|
+
openMarkets: this.cachedPredictionOpenMarkets,
|
|
8330
|
+
openPositions: this.cachedPredictionOpenPositions,
|
|
8331
|
+
unrealizedPnl: this.cachedPredictionUnrealizedPnl,
|
|
8332
|
+
pendingFees: this.predictionRecorder?.pendingFeesUSD ?? 0,
|
|
8333
|
+
pendingRecords: this.predictionRecorder?.pendingRetries ?? 0,
|
|
8334
|
+
vault: this.cachedPredictionVaultStatus
|
|
8335
|
+
} : void 0,
|
|
5982
8336
|
positions: this.positionTracker ? this.positionTracker.getPositionSummary(this.lastPrices) : void 0,
|
|
5983
8337
|
paper: this.mode === "paper" && this.paperPortfolio ? (() => {
|
|
5984
8338
|
const summary = this.paperPortfolio.getSummary(this.lastPrices);
|
|
@@ -6021,6 +8375,39 @@ var AgentRuntime = class {
|
|
|
6021
8375
|
}).catch(() => {
|
|
6022
8376
|
});
|
|
6023
8377
|
}
|
|
8378
|
+
if (this.predictionConnected && this.predictionPositions && this.predictionFunding) {
|
|
8379
|
+
this.predictionPositions.getAccountSummary().then((summary) => {
|
|
8380
|
+
this.cachedPredictionExposure = summary.totalExposure;
|
|
8381
|
+
this.cachedPredictionOpenMarkets = summary.openMarketCount;
|
|
8382
|
+
this.cachedPredictionOpenPositions = summary.openPositionCount;
|
|
8383
|
+
this.cachedPredictionUnrealizedPnl = summary.totalUnrealizedPnl;
|
|
8384
|
+
}).catch(() => {
|
|
8385
|
+
});
|
|
8386
|
+
this.predictionFunding.getPolygonUSDCBalance().then((bal) => {
|
|
8387
|
+
this.cachedPredictionPolygonUSDC = Number(bal) / 1e6;
|
|
8388
|
+
}).catch(() => {
|
|
8389
|
+
});
|
|
8390
|
+
this.predictionFunding.getBaseUSDCBalance().then((bal) => {
|
|
8391
|
+
this.cachedPredictionBaseUSDC = Number(bal) / 1e6;
|
|
8392
|
+
}).catch(() => {
|
|
8393
|
+
});
|
|
8394
|
+
if (this.predictionVaultManager?.hasVault) {
|
|
8395
|
+
this.predictionVaultManager.getVaultStatus().then((vs) => {
|
|
8396
|
+
if (vs.hasVault) {
|
|
8397
|
+
this.cachedPredictionVaultStatus = {
|
|
8398
|
+
address: vs.vaultAddress,
|
|
8399
|
+
totalAssets: Number(vs.totalAssets) / 1e6,
|
|
8400
|
+
sharePrice: vs.sharePrice,
|
|
8401
|
+
cachedCtfValue: Number(vs.cachedCtfValue) / 1e6,
|
|
8402
|
+
currentExposureBps: vs.currentExposureBps,
|
|
8403
|
+
circuitBreakerActive: vs.circuitBreakerActive,
|
|
8404
|
+
isPaused: vs.isPaused
|
|
8405
|
+
};
|
|
8406
|
+
}
|
|
8407
|
+
}).catch(() => {
|
|
8408
|
+
});
|
|
8409
|
+
}
|
|
8410
|
+
}
|
|
6024
8411
|
}
|
|
6025
8412
|
/**
|
|
6026
8413
|
* Run a single trading cycle
|
|
@@ -6218,6 +8605,15 @@ var AgentRuntime = class {
|
|
|
6218
8605
|
this.relay?.sendMessage("system", "error", "Perp Cycle Error", message);
|
|
6219
8606
|
}
|
|
6220
8607
|
}
|
|
8608
|
+
if (!isPaper && this.predictionConnected && this.predictionTradingActive) {
|
|
8609
|
+
try {
|
|
8610
|
+
await this.runPredictionCycle();
|
|
8611
|
+
} catch (error) {
|
|
8612
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8613
|
+
console.error("Error in prediction cycle:", message);
|
|
8614
|
+
this.relay?.sendMessage("system", "error", "Prediction Cycle Error", message);
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
6221
8617
|
this.sendRelayStatus();
|
|
6222
8618
|
}
|
|
6223
8619
|
/**
|
|
@@ -6278,6 +8674,101 @@ var AgentRuntime = class {
|
|
|
6278
8674
|
}
|
|
6279
8675
|
}
|
|
6280
8676
|
}
|
|
8677
|
+
/**
|
|
8678
|
+
* Run a single prediction market trading cycle.
|
|
8679
|
+
* Fetches active markets, positions, calls prediction strategy, executes signals.
|
|
8680
|
+
* Fills are recorded on Base via Router.recordPerpTrade() with POLY: prefix.
|
|
8681
|
+
*/
|
|
8682
|
+
async runPredictionCycle() {
|
|
8683
|
+
if (!this.predictionClient || !this.predictionOrders || !this.predictionPositions || !this.predictionRecorder || !this.predictionBrowser || !this.predictionConnected) return;
|
|
8684
|
+
const predictionConfig = this.config.prediction;
|
|
8685
|
+
if (!predictionConfig?.enabled) return;
|
|
8686
|
+
console.log(" [POLY] Running prediction cycle...");
|
|
8687
|
+
try {
|
|
8688
|
+
const newFills = await this.predictionOrders.pollNewFills();
|
|
8689
|
+
if (newFills.length > 0) {
|
|
8690
|
+
console.log(` [POLY] ${newFills.length} new fill(s) detected`);
|
|
8691
|
+
for (const fill of newFills) {
|
|
8692
|
+
this.predictionPositions.processFill(fill);
|
|
8693
|
+
const result = await this.predictionRecorder.recordFill(fill);
|
|
8694
|
+
if (result.success) {
|
|
8695
|
+
this.relay?.sendMessage(
|
|
8696
|
+
"prediction_fill",
|
|
8697
|
+
"success",
|
|
8698
|
+
"Prediction Fill",
|
|
8699
|
+
`${fill.side} ${fill.size} @ $${parseFloat(fill.price).toFixed(2)} \u2014 market ${fill.marketConditionId.slice(0, 12)}...`,
|
|
8700
|
+
{ tradeId: fill.tradeId, side: fill.side, size: fill.size, price: fill.price, txHash: result.txHash }
|
|
8701
|
+
);
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
} catch (error) {
|
|
8706
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8707
|
+
console.warn(" [POLY] Fill polling failed:", message);
|
|
8708
|
+
}
|
|
8709
|
+
const account = await this.predictionPositions.getAccountSummary();
|
|
8710
|
+
const positions = await this.predictionPositions.getPositions();
|
|
8711
|
+
console.log(` [POLY] Polygon USDC: $${account.polygonUSDC.toFixed(2)}, Positions: ${positions.length}, Exposure: $${account.totalExposure.toFixed(2)}`);
|
|
8712
|
+
if (!this.predictionStrategy) {
|
|
8713
|
+
return;
|
|
8714
|
+
}
|
|
8715
|
+
let markets;
|
|
8716
|
+
try {
|
|
8717
|
+
const category = predictionConfig.allowedCategories?.[0];
|
|
8718
|
+
markets = await this.predictionBrowser.getActiveMarkets({
|
|
8719
|
+
limit: 50,
|
|
8720
|
+
category
|
|
8721
|
+
});
|
|
8722
|
+
} catch (error) {
|
|
8723
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8724
|
+
console.error(" [POLY] Market fetch error:", message);
|
|
8725
|
+
return;
|
|
8726
|
+
}
|
|
8727
|
+
let signals;
|
|
8728
|
+
try {
|
|
8729
|
+
signals = await this.predictionStrategy(
|
|
8730
|
+
markets,
|
|
8731
|
+
positions,
|
|
8732
|
+
account,
|
|
8733
|
+
this.llm,
|
|
8734
|
+
this.config
|
|
8735
|
+
);
|
|
8736
|
+
} catch (error) {
|
|
8737
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8738
|
+
console.error(" [POLY] Strategy error:", message);
|
|
8739
|
+
return;
|
|
8740
|
+
}
|
|
8741
|
+
console.log(` [POLY] Strategy generated ${signals.length} signals`);
|
|
8742
|
+
for (const signal of signals) {
|
|
8743
|
+
if (signal.action === "hold") continue;
|
|
8744
|
+
const result = await this.predictionOrders.executeSignal(signal);
|
|
8745
|
+
if (result.success) {
|
|
8746
|
+
console.log(` [POLY] Order placed: ${signal.action} "${signal.marketQuestion.slice(0, 50)}..." \u2014 ${result.status}`);
|
|
8747
|
+
} else {
|
|
8748
|
+
console.warn(` [POLY] Order failed: ${signal.action} \u2014 ${result.error}`);
|
|
8749
|
+
}
|
|
8750
|
+
}
|
|
8751
|
+
const pendingFees = this.predictionRecorder.pendingFeesUSD;
|
|
8752
|
+
const threshold = predictionConfig.feeBridgeThresholdUSD ?? 10;
|
|
8753
|
+
if (pendingFees >= threshold && this.predictionFunding) {
|
|
8754
|
+
console.log(` [POLY] Bridging $${pendingFees.toFixed(2)} in accumulated fees to Base...`);
|
|
8755
|
+
const feeAmount = BigInt(Math.floor(pendingFees * 1e6));
|
|
8756
|
+
try {
|
|
8757
|
+
const bridgeResult = await this.predictionFunding.withdrawToBase(feeAmount, (step) => {
|
|
8758
|
+
console.log(` [POLY] Fee bridge: ${step}`);
|
|
8759
|
+
});
|
|
8760
|
+
if (bridgeResult.success) {
|
|
8761
|
+
this.predictionRecorder.resetAccumulatedFees();
|
|
8762
|
+
console.log(` [POLY] Fee bridge complete: $${pendingFees.toFixed(2)} \u2192 Base`);
|
|
8763
|
+
} else {
|
|
8764
|
+
console.warn(` [POLY] Fee bridge failed: ${bridgeResult.error}`);
|
|
8765
|
+
}
|
|
8766
|
+
} catch (error) {
|
|
8767
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8768
|
+
console.warn(` [POLY] Fee bridge error: ${message}`);
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
6281
8772
|
/**
|
|
6282
8773
|
* Check if ETH balance is below threshold and notify.
|
|
6283
8774
|
* Returns true if trading should continue, false if ETH is critically low.
|
|
@@ -6329,6 +8820,14 @@ var AgentRuntime = class {
|
|
|
6329
8820
|
if (this.perpRecorder) {
|
|
6330
8821
|
this.perpRecorder.stop();
|
|
6331
8822
|
}
|
|
8823
|
+
this.predictionConnected = false;
|
|
8824
|
+
this.predictionTradingActive = false;
|
|
8825
|
+
if (this.predictionVaultManager) {
|
|
8826
|
+
this.predictionVaultManager.stop();
|
|
8827
|
+
}
|
|
8828
|
+
if (this.predictionRecorder) {
|
|
8829
|
+
this.predictionRecorder.stop();
|
|
8830
|
+
}
|
|
6332
8831
|
if (this.relay) {
|
|
6333
8832
|
this.relay.disconnect();
|
|
6334
8833
|
}
|
|
@@ -6346,8 +8845,8 @@ var AgentRuntime = class {
|
|
|
6346
8845
|
* for Frontier agents trading outside the whitelist) stay visible.
|
|
6347
8846
|
*/
|
|
6348
8847
|
getTokensToTrack() {
|
|
6349
|
-
const
|
|
6350
|
-
const baseSet = new Set(
|
|
8848
|
+
const base8 = this.config.allowedTokens || this.getDefaultTokens();
|
|
8849
|
+
const baseSet = new Set(base8.map((t) => t.toLowerCase()));
|
|
6351
8850
|
const trackedPositions = this.positionTracker.getPositions();
|
|
6352
8851
|
const extras = [];
|
|
6353
8852
|
for (const pos of trackedPositions) {
|
|
@@ -6370,7 +8869,7 @@ var AgentRuntime = class {
|
|
|
6370
8869
|
console.log(`Auto-tracking ${extras.length} additional token(s) from position history`);
|
|
6371
8870
|
}
|
|
6372
8871
|
const resolver = this.marketData.getResolver();
|
|
6373
|
-
const allTokens = [...
|
|
8872
|
+
const allTokens = [...base8, ...extras];
|
|
6374
8873
|
const filtered = allTokens.filter((t) => !resolver.isUnresolvable(t.toLowerCase()));
|
|
6375
8874
|
const dropped = allTokens.length - filtered.length;
|
|
6376
8875
|
if (dropped > 0) {
|
|
@@ -6707,7 +9206,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
6707
9206
|
}
|
|
6708
9207
|
|
|
6709
9208
|
// src/index.ts
|
|
6710
|
-
var AGENT_VERSION = "0.1.
|
|
9209
|
+
var AGENT_VERSION = "0.1.41";
|
|
6711
9210
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6712
9211
|
0 && (module.exports = {
|
|
6713
9212
|
AGENT_VERSION,
|
|
@@ -6725,6 +9224,8 @@ var AGENT_VERSION = "0.1.39";
|
|
|
6725
9224
|
HyperliquidWebSocket,
|
|
6726
9225
|
LLMConfigSchema,
|
|
6727
9226
|
LLMProviderSchema,
|
|
9227
|
+
MARKET_CATEGORIES,
|
|
9228
|
+
MarketBrowser,
|
|
6728
9229
|
MarketDataService,
|
|
6729
9230
|
MistralAdapter,
|
|
6730
9231
|
OllamaAdapter,
|
|
@@ -6733,8 +9234,14 @@ var AGENT_VERSION = "0.1.39";
|
|
|
6733
9234
|
PerpConfigSchema,
|
|
6734
9235
|
PerpOnboarding,
|
|
6735
9236
|
PerpTradeRecorder,
|
|
9237
|
+
PolymarketClient,
|
|
6736
9238
|
PositionManager,
|
|
6737
9239
|
PositionTracker,
|
|
9240
|
+
PredictionConfigSchema,
|
|
9241
|
+
PredictionFunding,
|
|
9242
|
+
PredictionOrderManager,
|
|
9243
|
+
PredictionPositionManager,
|
|
9244
|
+
PredictionTradeRecorder,
|
|
6738
9245
|
RelayClient,
|
|
6739
9246
|
RelayConfigSchema,
|
|
6740
9247
|
RiskManager,
|
|
@@ -6746,9 +9253,12 @@ var AGENT_VERSION = "0.1.39";
|
|
|
6746
9253
|
VaultConfigSchema,
|
|
6747
9254
|
VaultManager,
|
|
6748
9255
|
VaultPolicySchema,
|
|
9256
|
+
calculatePredictionFee,
|
|
6749
9257
|
createLLMAdapter,
|
|
6750
9258
|
createSampleConfig,
|
|
9259
|
+
decodePredictionInstrument,
|
|
6751
9260
|
decryptEnvFile,
|
|
9261
|
+
encodePredictionInstrument,
|
|
6752
9262
|
encryptEnvFile,
|
|
6753
9263
|
fillHashToBytes32,
|
|
6754
9264
|
fillOidToBytes32,
|
|
@@ -6758,6 +9268,8 @@ var AGENT_VERSION = "0.1.39";
|
|
|
6758
9268
|
loadConfig,
|
|
6759
9269
|
loadSecureEnv,
|
|
6760
9270
|
loadStrategy,
|
|
9271
|
+
orderIdToBytes32,
|
|
9272
|
+
tradeIdToBytes32,
|
|
6761
9273
|
validateConfig,
|
|
6762
9274
|
validateStrategy
|
|
6763
9275
|
});
|