@exagent/agent 0.1.40 → 0.1.42
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-AEASQNGT.mjs +9475 -0
- package/dist/chunk-CQBWIY4B.mjs +9699 -0
- package/dist/chunk-MEYC4ASJ.mjs +9597 -0
- package/dist/chunk-OYQO7EUW.mjs +9712 -0
- package/dist/chunk-PUQW5VB2.mjs +9699 -0
- package/dist/chunk-R7LBXQH2.mjs +9712 -0
- package/dist/chunk-TI3C2W62.mjs +9585 -0
- package/dist/chunk-XGSGGIVT.mjs +9699 -0
- package/dist/cli.js +370 -52
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +119 -57
- package/dist/index.d.ts +119 -57
- package/dist/index.js +370 -52
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -3435,16 +3435,20 @@ var TOKEN_SYMBOLS = {
|
|
|
3435
3435
|
var KEY_POSITIONS = "__positions";
|
|
3436
3436
|
var KEY_TRADE_HISTORY = "__trade_history";
|
|
3437
3437
|
var KEY_RISK_STATE = "__risk_state";
|
|
3438
|
+
var KEY_SELL_FAILURES = "__sell_failures";
|
|
3439
|
+
var STUCK_FAILURE_THRESHOLD = 3;
|
|
3438
3440
|
var PositionTracker = class {
|
|
3439
3441
|
store;
|
|
3440
3442
|
positions;
|
|
3441
3443
|
tradeHistory;
|
|
3444
|
+
sellFailures;
|
|
3442
3445
|
maxTradeHistory;
|
|
3443
3446
|
constructor(store, options) {
|
|
3444
3447
|
this.store = store;
|
|
3445
3448
|
this.maxTradeHistory = options?.maxTradeHistory ?? 50;
|
|
3446
3449
|
this.positions = store.get(KEY_POSITIONS) || {};
|
|
3447
3450
|
this.tradeHistory = store.get(KEY_TRADE_HISTORY) || [];
|
|
3451
|
+
this.sellFailures = store.get(KEY_SELL_FAILURES) || {};
|
|
3448
3452
|
const posCount = Object.keys(this.positions).length;
|
|
3449
3453
|
if (posCount > 0 || this.tradeHistory.length > 0) {
|
|
3450
3454
|
console.log(`Position tracker loaded: ${posCount} positions, ${this.tradeHistory.length} trade records`);
|
|
@@ -3480,10 +3484,10 @@ var PositionTracker = class {
|
|
|
3480
3484
|
const realizedPnL = this.handleSell(tokenIn.toLowerCase(), tradeValueUSD);
|
|
3481
3485
|
record.realizedPnL = realizedPnL;
|
|
3482
3486
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
+
this.tradeHistory.unshift(record);
|
|
3488
|
+
if (this.tradeHistory.length > this.maxTradeHistory) {
|
|
3489
|
+
this.tradeHistory = this.tradeHistory.slice(0, this.maxTradeHistory);
|
|
3490
|
+
}
|
|
3487
3491
|
}
|
|
3488
3492
|
this.persist();
|
|
3489
3493
|
}
|
|
@@ -3544,26 +3548,58 @@ var PositionTracker = class {
|
|
|
3544
3548
|
// ============================================================
|
|
3545
3549
|
/**
|
|
3546
3550
|
* Sync tracked positions with on-chain balances.
|
|
3547
|
-
* Updates currentAmount, detects new tokens (airdrops),
|
|
3551
|
+
* Updates currentAmount, detects new tokens (airdrops), and handles position exits.
|
|
3552
|
+
*
|
|
3553
|
+
* CRITICAL: When a tracked position's balance decreases without a corresponding
|
|
3554
|
+
* sell trade, we infer a manual exit and record it with realized PnL calculated
|
|
3555
|
+
* at current market price. This prevents PnL manipulation via off-router sells.
|
|
3556
|
+
*
|
|
3557
|
+
* Returns array of inferred exits for the runtime to report to Command Center.
|
|
3548
3558
|
*/
|
|
3549
3559
|
syncBalances(balances, prices) {
|
|
3550
3560
|
let changed = false;
|
|
3561
|
+
const inferredExits = [];
|
|
3551
3562
|
for (const [address, balance] of Object.entries(balances)) {
|
|
3552
3563
|
const token = address.toLowerCase();
|
|
3553
3564
|
if (BASE_ASSETS.has(token)) continue;
|
|
3554
3565
|
const decimals = getTokenDecimals(token);
|
|
3555
3566
|
const amount = Number(balance) / Math.pow(10, decimals);
|
|
3567
|
+
const position = this.positions[token];
|
|
3556
3568
|
if (amount > 0) {
|
|
3557
|
-
if (
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3569
|
+
if (position) {
|
|
3570
|
+
const previousAmount = position.currentAmount;
|
|
3571
|
+
if (amount < previousAmount && position.totalAmountAcquired > 0) {
|
|
3572
|
+
const exitedAmount = previousAmount - amount;
|
|
3573
|
+
const currentPrice = prices[token] || position.averageEntryPrice || 0;
|
|
3574
|
+
const exitValueUSD = exitedAmount * currentPrice;
|
|
3575
|
+
const costBasisOfExited = exitedAmount * position.averageEntryPrice;
|
|
3576
|
+
const realizedPnL = exitValueUSD - costBasisOfExited;
|
|
3577
|
+
const symbol = position.symbol || TOKEN_SYMBOLS[token] || getTokenSymbol(token) || token.slice(0, 10);
|
|
3578
|
+
this.recordInferredExit(token, exitedAmount, exitValueUSD, realizedPnL, symbol);
|
|
3579
|
+
inferredExits.push({
|
|
3580
|
+
token,
|
|
3581
|
+
symbol,
|
|
3582
|
+
amountExited: exitedAmount,
|
|
3583
|
+
exitValueUSD,
|
|
3584
|
+
realizedPnL,
|
|
3585
|
+
costBasis: costBasisOfExited
|
|
3586
|
+
});
|
|
3587
|
+
const exitRatio = exitedAmount / position.totalAmountAcquired;
|
|
3588
|
+
position.totalCostBasis = Math.max(0, position.totalCostBasis * (1 - exitRatio));
|
|
3589
|
+
position.totalAmountAcquired = Math.max(0, position.totalAmountAcquired - exitedAmount);
|
|
3590
|
+
console.log(
|
|
3591
|
+
`Position tracker: inferred exit of ${exitedAmount.toFixed(4)} ${symbol} ($${exitValueUSD.toFixed(2)} value, PnL: $${realizedPnL >= 0 ? "+" : ""}${realizedPnL.toFixed(2)})`
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
if (position.currentAmount !== amount) {
|
|
3595
|
+
position.currentAmount = amount;
|
|
3596
|
+
position.lastUpdateTimestamp = Date.now();
|
|
3561
3597
|
changed = true;
|
|
3562
3598
|
}
|
|
3563
|
-
if (!
|
|
3599
|
+
if (!position.symbol) {
|
|
3564
3600
|
const resolved = TOKEN_SYMBOLS[token] || getTokenSymbol(token);
|
|
3565
3601
|
if (resolved) {
|
|
3566
|
-
|
|
3602
|
+
position.symbol = resolved;
|
|
3567
3603
|
changed = true;
|
|
3568
3604
|
}
|
|
3569
3605
|
}
|
|
@@ -3586,14 +3622,60 @@ var PositionTracker = class {
|
|
|
3586
3622
|
}
|
|
3587
3623
|
changed = true;
|
|
3588
3624
|
}
|
|
3589
|
-
} else if (
|
|
3625
|
+
} else if (position) {
|
|
3626
|
+
if (position.totalAmountAcquired > 0 && position.currentAmount > 0) {
|
|
3627
|
+
const currentPrice = prices[token] || position.averageEntryPrice || 0;
|
|
3628
|
+
const exitValueUSD = position.currentAmount * currentPrice;
|
|
3629
|
+
const costBasisOfExited = position.currentAmount * position.averageEntryPrice;
|
|
3630
|
+
const realizedPnL = exitValueUSD - costBasisOfExited;
|
|
3631
|
+
const symbol = position.symbol || TOKEN_SYMBOLS[token] || getTokenSymbol(token) || token.slice(0, 10);
|
|
3632
|
+
this.recordInferredExit(token, position.currentAmount, exitValueUSD, realizedPnL, symbol);
|
|
3633
|
+
inferredExits.push({
|
|
3634
|
+
token,
|
|
3635
|
+
symbol,
|
|
3636
|
+
amountExited: position.currentAmount,
|
|
3637
|
+
exitValueUSD,
|
|
3638
|
+
realizedPnL,
|
|
3639
|
+
costBasis: costBasisOfExited
|
|
3640
|
+
});
|
|
3641
|
+
console.log(
|
|
3642
|
+
`Position tracker: inferred FULL exit of ${position.currentAmount.toFixed(4)} ${symbol} ($${exitValueUSD.toFixed(2)} value, PnL: $${realizedPnL >= 0 ? "+" : ""}${realizedPnL.toFixed(2)})`
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3590
3645
|
delete this.positions[token];
|
|
3646
|
+
if (this.sellFailures[token]) {
|
|
3647
|
+
delete this.sellFailures[token];
|
|
3648
|
+
}
|
|
3591
3649
|
changed = true;
|
|
3592
3650
|
}
|
|
3593
3651
|
}
|
|
3594
|
-
if (changed) {
|
|
3652
|
+
if (changed || inferredExits.length > 0) {
|
|
3595
3653
|
this.persist();
|
|
3596
3654
|
}
|
|
3655
|
+
return inferredExits;
|
|
3656
|
+
}
|
|
3657
|
+
/**
|
|
3658
|
+
* Record an inferred exit in trade history.
|
|
3659
|
+
* These are exits detected from balance changes, not from tracked sell executions.
|
|
3660
|
+
*/
|
|
3661
|
+
recordInferredExit(token, amount, valueUSD, realizedPnL, symbol) {
|
|
3662
|
+
const record = {
|
|
3663
|
+
timestamp: Date.now(),
|
|
3664
|
+
action: "sell",
|
|
3665
|
+
tokenIn: token,
|
|
3666
|
+
tokenOut: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
3667
|
+
// USDC (assumed exit to stablecoin)
|
|
3668
|
+
amountIn: amount.toString(),
|
|
3669
|
+
priceUSD: valueUSD,
|
|
3670
|
+
reasoning: `Inferred exit: balance decreased without tracked sell. Exited ${amount.toFixed(4)} ${symbol} at ~$${(valueUSD / amount).toFixed(4)}/token.`,
|
|
3671
|
+
realizedPnL,
|
|
3672
|
+
success: true,
|
|
3673
|
+
inferred: true
|
|
3674
|
+
};
|
|
3675
|
+
this.tradeHistory.unshift(record);
|
|
3676
|
+
if (this.tradeHistory.length > this.maxTradeHistory) {
|
|
3677
|
+
this.tradeHistory = this.tradeHistory.slice(0, this.maxTradeHistory);
|
|
3678
|
+
}
|
|
3597
3679
|
}
|
|
3598
3680
|
// ============================================================
|
|
3599
3681
|
// QUERY METHODS (for strategies)
|
|
@@ -3634,6 +3716,86 @@ var PositionTracker = class {
|
|
|
3634
3716
|
return Object.values(pnl).reduce((sum, v) => sum + v, 0);
|
|
3635
3717
|
}
|
|
3636
3718
|
// ============================================================
|
|
3719
|
+
// STUCK POSITION DETECTION
|
|
3720
|
+
// ============================================================
|
|
3721
|
+
/**
|
|
3722
|
+
* Record a sell failure for a token. Called when a sell attempt fails
|
|
3723
|
+
* due to liquidity issues, route simulation failure, or excessive price impact.
|
|
3724
|
+
*/
|
|
3725
|
+
recordSellFailure(token, reason) {
|
|
3726
|
+
const key = token.toLowerCase();
|
|
3727
|
+
const existing = this.sellFailures[key];
|
|
3728
|
+
if (existing) {
|
|
3729
|
+
existing.consecutiveFailures += 1;
|
|
3730
|
+
existing.lastFailureReason = reason;
|
|
3731
|
+
existing.lastFailureTimestamp = Date.now();
|
|
3732
|
+
} else {
|
|
3733
|
+
this.sellFailures[key] = {
|
|
3734
|
+
consecutiveFailures: 1,
|
|
3735
|
+
lastFailureReason: reason,
|
|
3736
|
+
lastFailureTimestamp: Date.now()
|
|
3737
|
+
};
|
|
3738
|
+
}
|
|
3739
|
+
this.store.set(KEY_SELL_FAILURES, this.sellFailures);
|
|
3740
|
+
}
|
|
3741
|
+
/**
|
|
3742
|
+
* Clear sell failure tracking for a token (called after successful sell).
|
|
3743
|
+
*/
|
|
3744
|
+
clearSellFailure(token) {
|
|
3745
|
+
const key = token.toLowerCase();
|
|
3746
|
+
if (this.sellFailures[key]) {
|
|
3747
|
+
delete this.sellFailures[key];
|
|
3748
|
+
this.store.set(KEY_SELL_FAILURES, this.sellFailures);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
/**
|
|
3752
|
+
* Detect positions that are "stuck" — we've tried and failed to sell them
|
|
3753
|
+
* multiple times, likely due to insufficient liquidity.
|
|
3754
|
+
*
|
|
3755
|
+
* Returns positions that have failed to sell >= STUCK_FAILURE_THRESHOLD times.
|
|
3756
|
+
*/
|
|
3757
|
+
getStuckPositions(prices) {
|
|
3758
|
+
const stuck = [];
|
|
3759
|
+
const now = Date.now();
|
|
3760
|
+
for (const [token, failure] of Object.entries(this.sellFailures)) {
|
|
3761
|
+
if (failure.consecutiveFailures < STUCK_FAILURE_THRESHOLD) continue;
|
|
3762
|
+
const position = this.positions[token];
|
|
3763
|
+
if (!position || position.currentAmount <= 0) continue;
|
|
3764
|
+
const price = prices[token] || position.averageEntryPrice || 0;
|
|
3765
|
+
const estimatedValueUSD = position.currentAmount * price;
|
|
3766
|
+
stuck.push({
|
|
3767
|
+
token,
|
|
3768
|
+
symbol: position.symbol || TOKEN_SYMBOLS[token] || token.slice(0, 10),
|
|
3769
|
+
consecutiveFailures: failure.consecutiveFailures,
|
|
3770
|
+
lastFailureReason: failure.lastFailureReason,
|
|
3771
|
+
holdingDurationMs: now - position.entryTimestamp,
|
|
3772
|
+
estimatedValueUSD
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
return stuck;
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* Check if a specific position is stuck.
|
|
3779
|
+
*/
|
|
3780
|
+
isPositionStuck(token) {
|
|
3781
|
+
const failure = this.sellFailures[token.toLowerCase()];
|
|
3782
|
+
return !!failure && failure.consecutiveFailures >= STUCK_FAILURE_THRESHOLD;
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Check if we should alert about a stuck position (rate-limited to once per 24h).
|
|
3786
|
+
* Returns true if we should alert, and marks the alert as sent.
|
|
3787
|
+
*/
|
|
3788
|
+
shouldAlertStuckPosition(token) {
|
|
3789
|
+
const alertKey = `__stuck_alert_${token.toLowerCase()}`;
|
|
3790
|
+
const lastAlert = this.store.get(alertKey) || 0;
|
|
3791
|
+
const hoursSinceAlert = (Date.now() - lastAlert) / (1e3 * 60 * 60);
|
|
3792
|
+
if (hoursSinceAlert >= 24) {
|
|
3793
|
+
this.store.set(alertKey, Date.now());
|
|
3794
|
+
return true;
|
|
3795
|
+
}
|
|
3796
|
+
return false;
|
|
3797
|
+
}
|
|
3798
|
+
// ============================================================
|
|
3637
3799
|
// RISK STATE PERSISTENCE
|
|
3638
3800
|
// ============================================================
|
|
3639
3801
|
/** Load persisted risk state */
|
|
@@ -4132,7 +4294,9 @@ function validateConfig(config) {
|
|
|
4132
4294
|
throw new Error("Endpoint required for custom LLM provider");
|
|
4133
4295
|
}
|
|
4134
4296
|
if (!config.agentId || Number(config.agentId) <= 0) {
|
|
4135
|
-
throw new Error(
|
|
4297
|
+
throw new Error(
|
|
4298
|
+
'Valid agent ID required in agent-config.json.\n\n Register your agent at https://exagent.io first:\n 1. Connect your wallet\n 2. Click "Launch Agent" to register\n 3. Copy your agent ID into agent-config.json as "agentId"\n'
|
|
4299
|
+
);
|
|
4136
4300
|
}
|
|
4137
4301
|
}
|
|
4138
4302
|
var DEFAULT_RPC_URL = "https://mainnet.base.org";
|
|
@@ -4163,13 +4327,6 @@ var VAULT_FACTORY_ABI = [
|
|
|
4163
4327
|
outputs: [{ type: "address" }],
|
|
4164
4328
|
stateMutability: "view"
|
|
4165
4329
|
},
|
|
4166
|
-
{
|
|
4167
|
-
type: "function",
|
|
4168
|
-
name: "canCreateVault",
|
|
4169
|
-
inputs: [{ name: "creator", type: "address" }],
|
|
4170
|
-
outputs: [{ name: "canCreate", type: "bool" }, { name: "reason", type: "string" }],
|
|
4171
|
-
stateMutability: "view"
|
|
4172
|
-
},
|
|
4173
4330
|
{
|
|
4174
4331
|
type: "function",
|
|
4175
4332
|
name: "createVault",
|
|
@@ -4186,10 +4343,17 @@ var VAULT_FACTORY_ABI = [
|
|
|
4186
4343
|
},
|
|
4187
4344
|
{
|
|
4188
4345
|
type: "function",
|
|
4189
|
-
name: "
|
|
4346
|
+
name: "MIN_SEED_AMOUNT",
|
|
4190
4347
|
inputs: [],
|
|
4191
4348
|
outputs: [{ type: "uint256" }],
|
|
4192
4349
|
stateMutability: "view"
|
|
4350
|
+
},
|
|
4351
|
+
{
|
|
4352
|
+
type: "function",
|
|
4353
|
+
name: "allowedAssets",
|
|
4354
|
+
inputs: [{ name: "asset", type: "address" }],
|
|
4355
|
+
outputs: [{ type: "bool" }],
|
|
4356
|
+
stateMutability: "view"
|
|
4193
4357
|
}
|
|
4194
4358
|
];
|
|
4195
4359
|
var VAULT_ABI = [
|
|
@@ -4287,23 +4451,34 @@ var VaultManager = class {
|
|
|
4287
4451
|
} catch {
|
|
4288
4452
|
}
|
|
4289
4453
|
}
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4454
|
+
let canCreate = true;
|
|
4455
|
+
let cannotCreateReason = null;
|
|
4456
|
+
if (hasVault) {
|
|
4457
|
+
canCreate = false;
|
|
4458
|
+
cannotCreateReason = "Vault already exists for this agent";
|
|
4459
|
+
} else {
|
|
4460
|
+
try {
|
|
4461
|
+
const isAssetAllowed = await this.publicClient.readContract({
|
|
4462
|
+
address: this.addresses.vaultFactory,
|
|
4463
|
+
abi: VAULT_FACTORY_ABI,
|
|
4464
|
+
functionName: "allowedAssets",
|
|
4465
|
+
args: [this.addresses.usdc]
|
|
4466
|
+
});
|
|
4467
|
+
if (!isAssetAllowed) {
|
|
4468
|
+
canCreate = false;
|
|
4469
|
+
cannotCreateReason = "USDC is not an allowed asset on the vault factory";
|
|
4470
|
+
}
|
|
4471
|
+
} catch {
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4299
4474
|
return {
|
|
4300
4475
|
hasVault,
|
|
4301
4476
|
vaultAddress,
|
|
4302
4477
|
totalAssets,
|
|
4303
|
-
canCreateVault:
|
|
4304
|
-
cannotCreateReason
|
|
4305
|
-
requirementsMet:
|
|
4306
|
-
requirements
|
|
4478
|
+
canCreateVault: canCreate,
|
|
4479
|
+
cannotCreateReason,
|
|
4480
|
+
requirementsMet: canCreate,
|
|
4481
|
+
requirements: { isBypassed: true }
|
|
4307
4482
|
};
|
|
4308
4483
|
}
|
|
4309
4484
|
/**
|
|
@@ -4351,11 +4526,67 @@ var VaultManager = class {
|
|
|
4351
4526
|
if (existingVault) {
|
|
4352
4527
|
return { success: false, error: "Vault already exists", vaultAddress: existingVault };
|
|
4353
4528
|
}
|
|
4354
|
-
const status = await this.getVaultStatus();
|
|
4355
|
-
if (!status.canCreateVault) {
|
|
4356
|
-
return { success: false, error: status.cannotCreateReason || "Requirements not met" };
|
|
4357
|
-
}
|
|
4358
4529
|
const seed = seedAmount || BigInt(1e8);
|
|
4530
|
+
let usdcBalance;
|
|
4531
|
+
try {
|
|
4532
|
+
usdcBalance = await this.publicClient.readContract({
|
|
4533
|
+
address: this.addresses.usdc,
|
|
4534
|
+
abi: import_viem3.erc20Abi,
|
|
4535
|
+
functionName: "balanceOf",
|
|
4536
|
+
args: [this.account.address]
|
|
4537
|
+
});
|
|
4538
|
+
} catch {
|
|
4539
|
+
return { success: false, error: "Failed to read USDC balance" };
|
|
4540
|
+
}
|
|
4541
|
+
if (usdcBalance < seed) {
|
|
4542
|
+
const needed = Number(seed) / 1e6;
|
|
4543
|
+
const have = Number(usdcBalance) / 1e6;
|
|
4544
|
+
return {
|
|
4545
|
+
success: false,
|
|
4546
|
+
error: `Insufficient USDC: need ${needed} USDC seed but wallet has ${have} USDC. Fund ${this.account.address} with at least ${needed} USDC and retry.`
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
try {
|
|
4550
|
+
const isAllowed = await this.publicClient.readContract({
|
|
4551
|
+
address: this.addresses.vaultFactory,
|
|
4552
|
+
abi: VAULT_FACTORY_ABI,
|
|
4553
|
+
functionName: "allowedAssets",
|
|
4554
|
+
args: [this.addresses.usdc]
|
|
4555
|
+
});
|
|
4556
|
+
if (!isAllowed) {
|
|
4557
|
+
return { success: false, error: "USDC is not whitelisted on the vault factory. Contact protocol admin." };
|
|
4558
|
+
}
|
|
4559
|
+
} catch {
|
|
4560
|
+
}
|
|
4561
|
+
try {
|
|
4562
|
+
const currentAllowance = await this.publicClient.readContract({
|
|
4563
|
+
address: this.addresses.usdc,
|
|
4564
|
+
abi: import_viem3.erc20Abi,
|
|
4565
|
+
functionName: "allowance",
|
|
4566
|
+
args: [this.account.address, this.addresses.vaultFactory]
|
|
4567
|
+
});
|
|
4568
|
+
if (currentAllowance < seed) {
|
|
4569
|
+
console.log(`Approving ${Number(seed) / 1e6} USDC to VaultFactory...`);
|
|
4570
|
+
const approveHash = await this.walletClient.writeContract({
|
|
4571
|
+
address: this.addresses.usdc,
|
|
4572
|
+
abi: import_viem3.erc20Abi,
|
|
4573
|
+
functionName: "approve",
|
|
4574
|
+
args: [this.addresses.vaultFactory, seed],
|
|
4575
|
+
chain: import_chains2.base,
|
|
4576
|
+
account: this.account
|
|
4577
|
+
});
|
|
4578
|
+
const approveReceipt = await this.publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
4579
|
+
if (approveReceipt.status !== "success") {
|
|
4580
|
+
return { success: false, error: "USDC approval transaction failed" };
|
|
4581
|
+
}
|
|
4582
|
+
console.log("USDC approved successfully");
|
|
4583
|
+
}
|
|
4584
|
+
} catch (error) {
|
|
4585
|
+
return {
|
|
4586
|
+
success: false,
|
|
4587
|
+
error: `USDC approval failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4359
4590
|
const vaultName = this.config.vaultConfig.defaultName || `${this.config.agentName} Trading Vault`;
|
|
4360
4591
|
const vaultSymbol = this.config.vaultConfig.defaultSymbol || `ex${this.config.agentName.replace(/[^a-zA-Z]/g, "").slice(0, 4).toUpperCase()}`;
|
|
4361
4592
|
const feeRecipient = this.config.vaultConfig.feeRecipient || this.account.address;
|
|
@@ -4377,8 +4608,9 @@ var VaultManager = class {
|
|
|
4377
4608
|
});
|
|
4378
4609
|
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
|
|
4379
4610
|
if (receipt.status !== "success") {
|
|
4380
|
-
return { success: false, error: "
|
|
4611
|
+
return { success: false, error: "createVault transaction reverted on-chain", txHash: hash };
|
|
4381
4612
|
}
|
|
4613
|
+
this.lastVaultCheck = 0;
|
|
4382
4614
|
const vaultAddress = await this.getVaultAddress();
|
|
4383
4615
|
this.cachedVaultAddress = vaultAddress;
|
|
4384
4616
|
return {
|
|
@@ -4387,10 +4619,23 @@ var VaultManager = class {
|
|
|
4387
4619
|
txHash: hash
|
|
4388
4620
|
};
|
|
4389
4621
|
} catch (error) {
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
}
|
|
4622
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
4623
|
+
if (msg.includes("NotAgentOwner")) {
|
|
4624
|
+
return { success: false, error: "This wallet is not the owner of the agent. Only the agent owner can create a fund." };
|
|
4625
|
+
}
|
|
4626
|
+
if (msg.includes("InsufficientStake")) {
|
|
4627
|
+
return { success: false, error: "Staking requirement not met. This should not happen with StakingStub \u2014 contact protocol admin." };
|
|
4628
|
+
}
|
|
4629
|
+
if (msg.includes("FrontierCannotCreateVault")) {
|
|
4630
|
+
return { success: false, error: "Frontier-risk agents cannot create funds. Change the agent risk universe first." };
|
|
4631
|
+
}
|
|
4632
|
+
if (msg.includes("VaultAlreadyExists")) {
|
|
4633
|
+
return { success: false, error: "A fund already exists for this agent and asset." };
|
|
4634
|
+
}
|
|
4635
|
+
if (msg.includes("InsufficientSeed")) {
|
|
4636
|
+
return { success: false, error: `Seed amount too low. Minimum is 100 USDC.` };
|
|
4637
|
+
}
|
|
4638
|
+
return { success: false, error: msg };
|
|
4394
4639
|
}
|
|
4395
4640
|
}
|
|
4396
4641
|
/**
|
|
@@ -5310,7 +5555,7 @@ var HyperliquidWebSocket = class {
|
|
|
5310
5555
|
var import_viem5 = require("viem");
|
|
5311
5556
|
var import_chains3 = require("viem/chains");
|
|
5312
5557
|
var import_accounts2 = require("viem/accounts");
|
|
5313
|
-
var ROUTER_ADDRESS = "
|
|
5558
|
+
var ROUTER_ADDRESS = "0x20feB3054750970773Fe7370c391732EE0559743";
|
|
5314
5559
|
var ROUTER_ABI = [
|
|
5315
5560
|
{
|
|
5316
5561
|
type: "function",
|
|
@@ -6370,7 +6615,7 @@ var PredictionPositionManager = class {
|
|
|
6370
6615
|
var import_viem8 = require("viem");
|
|
6371
6616
|
var import_chains5 = require("viem/chains");
|
|
6372
6617
|
var import_accounts4 = require("viem/accounts");
|
|
6373
|
-
var ROUTER_ADDRESS2 = "
|
|
6618
|
+
var ROUTER_ADDRESS2 = "0x20feB3054750970773Fe7370c391732EE0559743";
|
|
6374
6619
|
var ROUTER_ABI2 = [
|
|
6375
6620
|
{
|
|
6376
6621
|
type: "function",
|
|
@@ -7680,7 +7925,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
7680
7925
|
}
|
|
7681
7926
|
|
|
7682
7927
|
// src/index.ts
|
|
7683
|
-
var AGENT_VERSION = "0.1.
|
|
7928
|
+
var AGENT_VERSION = "0.1.42";
|
|
7684
7929
|
|
|
7685
7930
|
// src/relay.ts
|
|
7686
7931
|
var RelayClient = class {
|
|
@@ -8048,7 +8293,18 @@ var AgentRuntime = class {
|
|
|
8048
8293
|
console.log(`Wallet: ${this.client.address}`);
|
|
8049
8294
|
const agent = await this.client.registry.getAgent(BigInt(this.config.agentId));
|
|
8050
8295
|
if (!agent) {
|
|
8051
|
-
throw new Error(
|
|
8296
|
+
throw new Error(
|
|
8297
|
+
`Agent ID ${this.config.agentId} not found on-chain.
|
|
8298
|
+
|
|
8299
|
+
Register your agent at https://exagent.io first:
|
|
8300
|
+
1. Connect your wallet at https://exagent.io
|
|
8301
|
+
2. Click "Launch Agent" to register (sets name, risk universe, limits)
|
|
8302
|
+
3. Copy your agent ID into agent-config.json
|
|
8303
|
+
4. Run your agent again
|
|
8304
|
+
|
|
8305
|
+
The website handles the full registration flow including risk parameters.
|
|
8306
|
+
Your agent ID will appear on your agent's profile page after registration.`
|
|
8307
|
+
);
|
|
8052
8308
|
}
|
|
8053
8309
|
console.log(`Agent verified: ${agent.name}`);
|
|
8054
8310
|
await this.ensureWalletLinked();
|
|
@@ -8572,7 +8828,7 @@ var AgentRuntime = class {
|
|
|
8572
8828
|
try {
|
|
8573
8829
|
const tokens = this.getTokensToTrack();
|
|
8574
8830
|
const initData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
8575
|
-
this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
8831
|
+
void this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
8576
8832
|
this.lastPortfolioValue = initData.portfolioValue;
|
|
8577
8833
|
this.lastPrices = initData.prices;
|
|
8578
8834
|
const nativeEthBal = initData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
@@ -9424,7 +9680,31 @@ var AgentRuntime = class {
|
|
|
9424
9680
|
this.lastPrices = marketData.prices;
|
|
9425
9681
|
const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
9426
9682
|
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
9427
|
-
this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
9683
|
+
const inferredExits = this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
9684
|
+
for (const exit of inferredExits) {
|
|
9685
|
+
const pnlSign = exit.realizedPnL >= 0 ? "+" : "";
|
|
9686
|
+
this.relay?.sendMessage(
|
|
9687
|
+
"position_inferred_exit",
|
|
9688
|
+
exit.realizedPnL >= 0 ? "info" : "warning",
|
|
9689
|
+
`Inferred Exit: ${exit.symbol}`,
|
|
9690
|
+
`Detected manual sell of ${exit.amountExited.toFixed(4)} ${exit.symbol} outside the ExagentRouter.
|
|
9691
|
+
|
|
9692
|
+
Exit value: $${exit.exitValueUSD.toFixed(2)}
|
|
9693
|
+
Cost basis: $${exit.costBasis.toFixed(2)}
|
|
9694
|
+
Realized PnL: ${pnlSign}$${exit.realizedPnL.toFixed(2)}
|
|
9695
|
+
|
|
9696
|
+
This exit has been recorded in your trade history and counts toward your total PnL.`,
|
|
9697
|
+
{
|
|
9698
|
+
token: exit.token,
|
|
9699
|
+
symbol: exit.symbol,
|
|
9700
|
+
amountExited: exit.amountExited,
|
|
9701
|
+
exitValueUSD: exit.exitValueUSD,
|
|
9702
|
+
realizedPnL: exit.realizedPnL,
|
|
9703
|
+
costBasis: exit.costBasis,
|
|
9704
|
+
inferred: true
|
|
9705
|
+
}
|
|
9706
|
+
);
|
|
9707
|
+
}
|
|
9428
9708
|
if (!isPaper) {
|
|
9429
9709
|
const fundsOk = this.checkFundsLow(marketData);
|
|
9430
9710
|
if (!fundsOk) {
|
|
@@ -9517,7 +9797,7 @@ var AgentRuntime = class {
|
|
|
9517
9797
|
console.log(` [PAPER] Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
9518
9798
|
}
|
|
9519
9799
|
this.paperPortfolio.recordEquityPoint(marketData.prices);
|
|
9520
|
-
this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
9800
|
+
void this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
9521
9801
|
this.lastPortfolioValue = postValue;
|
|
9522
9802
|
this.paperPortfolio.save();
|
|
9523
9803
|
} else {
|
|
@@ -9548,6 +9828,9 @@ var AgentRuntime = class {
|
|
|
9548
9828
|
tokenOut: result.signal.tokenOut
|
|
9549
9829
|
}
|
|
9550
9830
|
);
|
|
9831
|
+
if (result.signal.action === "sell") {
|
|
9832
|
+
this.positionTracker.clearSellFailure(result.signal.tokenIn);
|
|
9833
|
+
}
|
|
9551
9834
|
} else {
|
|
9552
9835
|
console.warn(`Trade failed: ${result.error}`);
|
|
9553
9836
|
this.relay?.sendMessage(
|
|
@@ -9557,6 +9840,41 @@ var AgentRuntime = class {
|
|
|
9557
9840
|
result.error || "Unknown error",
|
|
9558
9841
|
{ action: result.signal.action }
|
|
9559
9842
|
);
|
|
9843
|
+
if (result.signal.action === "sell") {
|
|
9844
|
+
const failedToken = result.signal.tokenIn.toLowerCase();
|
|
9845
|
+
this.positionTracker.recordSellFailure(failedToken, result.error || "Unknown error");
|
|
9846
|
+
}
|
|
9847
|
+
}
|
|
9848
|
+
}
|
|
9849
|
+
const stuckPositions = this.positionTracker.getStuckPositions(marketData.prices);
|
|
9850
|
+
for (const stuck of stuckPositions) {
|
|
9851
|
+
if (this.positionTracker.shouldAlertStuckPosition(stuck.token)) {
|
|
9852
|
+
const holdingDays = Math.floor(stuck.holdingDurationMs / (1e3 * 60 * 60 * 24));
|
|
9853
|
+
this.relay?.sendMessage(
|
|
9854
|
+
"position_stuck",
|
|
9855
|
+
"warning",
|
|
9856
|
+
`Position Stuck: ${stuck.symbol}`,
|
|
9857
|
+
`Unable to sell ${stuck.symbol} after ${stuck.consecutiveFailures} attempts. This position has been held for ${holdingDays} days with ~$${stuck.estimatedValueUSD.toFixed(2)} value.
|
|
9858
|
+
|
|
9859
|
+
Last error: ${stuck.lastFailureReason}
|
|
9860
|
+
|
|
9861
|
+
MANUAL EXIT REQUIRED:
|
|
9862
|
+
1. Export your wallet key: npx @exagent/agent export-key
|
|
9863
|
+
2. Import into MetaMask or Rabby
|
|
9864
|
+
3. Go to Uniswap/Aerodrome and swap ${stuck.symbol} \u2192 ETH directly
|
|
9865
|
+
4. Use high slippage (10-20%) for illiquid tokens
|
|
9866
|
+
|
|
9867
|
+
Note: Manual sells won't be tracked in your agent's PnL. The position will be removed from tracking once your balance is synced.
|
|
9868
|
+
|
|
9869
|
+
See: https://exagent.io/docs/guides/manual-exit`,
|
|
9870
|
+
{
|
|
9871
|
+
token: stuck.token,
|
|
9872
|
+
symbol: stuck.symbol,
|
|
9873
|
+
consecutiveFailures: stuck.consecutiveFailures,
|
|
9874
|
+
estimatedValueUSD: stuck.estimatedValueUSD,
|
|
9875
|
+
holdingDurationMs: stuck.holdingDurationMs
|
|
9876
|
+
}
|
|
9877
|
+
);
|
|
9560
9878
|
}
|
|
9561
9879
|
}
|
|
9562
9880
|
for (const result of results) {
|
|
@@ -9582,7 +9900,7 @@ var AgentRuntime = class {
|
|
|
9582
9900
|
if (marketPnL !== 0) {
|
|
9583
9901
|
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
9584
9902
|
}
|
|
9585
|
-
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
9903
|
+
void this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
9586
9904
|
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
9587
9905
|
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
9588
9906
|
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|