@owney/sdk 0.7.25-beta.7 → 0.7.25-beta.8
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/index.cjs +316 -138
- package/dist/index.d.cts +161 -143
- package/dist/index.d.ts +161 -143
- package/dist/index.js +316 -138
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2905,10 +2905,11 @@ var YieldseekerApiClient = class {
|
|
|
2905
2905
|
const payload = await response.json().catch(() => null);
|
|
2906
2906
|
if (!response.ok) {
|
|
2907
2907
|
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2908
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
2908
2909
|
throw new YieldseekerApiError(
|
|
2909
2910
|
response.status,
|
|
2910
2911
|
error.code,
|
|
2911
|
-
error.fields
|
|
2912
|
+
retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
|
|
2912
2913
|
);
|
|
2913
2914
|
}
|
|
2914
2915
|
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
@@ -3039,13 +3040,14 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
3039
3040
|
// differ from the underlying asset. Yieldseeker already converts it to
|
|
3040
3041
|
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
3041
3042
|
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
3042
|
-
// share quantity
|
|
3043
|
+
// share quantity separate from the withdrawable underlying asset amount.
|
|
3043
3044
|
amount: decimal(
|
|
3044
3045
|
value.assetsBase,
|
|
3045
3046
|
baseAssetDecimals,
|
|
3046
3047
|
"yield positions"
|
|
3047
3048
|
),
|
|
3048
3049
|
amountRaw: String(value.assetsRaw),
|
|
3050
|
+
withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
|
|
3049
3051
|
apy: percent(option.riskAdjustedApy),
|
|
3050
3052
|
tvl: Number(option.totalDepositsUsd),
|
|
3051
3053
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -3374,8 +3376,12 @@ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
|
3374
3376
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3375
3377
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3376
3378
|
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3377
|
-
var YIELDSEEKER_AGENT_LIST_CACHE_MS =
|
|
3378
|
-
var YIELDSEEKER_PORTFOLIO_CACHE_MS =
|
|
3379
|
+
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
3380
|
+
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
3381
|
+
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
3382
|
+
var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
3383
|
+
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
3384
|
+
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
3379
3385
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3380
3386
|
function generateYieldseekerUsername() {
|
|
3381
3387
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
@@ -3445,6 +3451,15 @@ var YieldseekerAgent = class {
|
|
|
3445
3451
|
readCache = /* @__PURE__ */ new Map();
|
|
3446
3452
|
pendingReads = /* @__PURE__ */ new Map();
|
|
3447
3453
|
readGeneration = 0;
|
|
3454
|
+
portfolioVersions = /* @__PURE__ */ new Map();
|
|
3455
|
+
snapshotFailures = /* @__PURE__ */ new Map();
|
|
3456
|
+
standardReadFailures = /* @__PURE__ */ new Map();
|
|
3457
|
+
reconcileUntil = /* @__PURE__ */ new Map();
|
|
3458
|
+
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
3459
|
+
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
3460
|
+
// settlement; keep slow reconciliation after the fast window expires.
|
|
3461
|
+
snapshotMovements = /* @__PURE__ */ new Map();
|
|
3462
|
+
movementEpochs = /* @__PURE__ */ new Map();
|
|
3448
3463
|
yieldOptions = /* @__PURE__ */ new Map();
|
|
3449
3464
|
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3450
3465
|
constructor(owneyApiKey, options = {}) {
|
|
@@ -3475,6 +3490,13 @@ var YieldseekerAgent = class {
|
|
|
3475
3490
|
this.pendingWalletContexts.clear();
|
|
3476
3491
|
this.readCache.clear();
|
|
3477
3492
|
this.pendingReads.clear();
|
|
3493
|
+
this.portfolioVersions.clear();
|
|
3494
|
+
this.snapshotFailures.clear();
|
|
3495
|
+
this.standardReadFailures.clear();
|
|
3496
|
+
this.reconcileUntil.clear();
|
|
3497
|
+
this.activityRefreshUntil.clear();
|
|
3498
|
+
this.snapshotMovements.clear();
|
|
3499
|
+
this.movementEpochs.clear();
|
|
3478
3500
|
}
|
|
3479
3501
|
async activateAgent(state, chainId, asset) {
|
|
3480
3502
|
this.assertChain(chainId);
|
|
@@ -3494,41 +3516,37 @@ var YieldseekerAgent = class {
|
|
|
3494
3516
|
);
|
|
3495
3517
|
}
|
|
3496
3518
|
const context = await this.ensureAgent(state, chainId, asset);
|
|
3519
|
+
const generation = this.readGeneration;
|
|
3520
|
+
const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
|
|
3497
3521
|
let txHash;
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
context.wallet.walletAddress,
|
|
3508
|
-
chainId,
|
|
3509
|
-
amount
|
|
3510
|
-
);
|
|
3511
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3512
|
-
} else {
|
|
3513
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3514
|
-
from: (0, import_viem7.getAddress)(state.walletAddress),
|
|
3515
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3516
|
-
data: (0, import_viem7.encodeFunctionData)({
|
|
3517
|
-
abi: import_viem7.erc20Abi,
|
|
3518
|
-
functionName: "transfer",
|
|
3519
|
-
args: [(0, import_viem7.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
3520
|
-
}),
|
|
3521
|
-
value: "0",
|
|
3522
|
-
chainId
|
|
3523
|
-
});
|
|
3524
|
-
}
|
|
3525
|
-
} finally {
|
|
3526
|
-
await this.refreshSnapshotAfterMovement(
|
|
3527
|
-
state,
|
|
3522
|
+
if (depositCallback) {
|
|
3523
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3524
|
+
agentId: "yieldseeker",
|
|
3525
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3526
|
+
userId: context.user.userId,
|
|
3527
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3528
|
+
});
|
|
3529
|
+
txHash = await depositCallback(
|
|
3530
|
+
context.wallet.walletAddress,
|
|
3528
3531
|
chainId,
|
|
3529
|
-
|
|
3530
|
-
"deposit"
|
|
3532
|
+
amount
|
|
3531
3533
|
);
|
|
3534
|
+
await this.waitForReceipt(state, chainId, txHash);
|
|
3535
|
+
} else {
|
|
3536
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3537
|
+
from: (0, import_viem7.getAddress)(state.walletAddress),
|
|
3538
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3539
|
+
data: (0, import_viem7.encodeFunctionData)({
|
|
3540
|
+
abi: import_viem7.erc20Abi,
|
|
3541
|
+
functionName: "transfer",
|
|
3542
|
+
args: [(0, import_viem7.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
3543
|
+
}),
|
|
3544
|
+
value: "0",
|
|
3545
|
+
chainId
|
|
3546
|
+
});
|
|
3547
|
+
}
|
|
3548
|
+
if (this.readGeneration === generation) {
|
|
3549
|
+
await this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3532
3550
|
}
|
|
3533
3551
|
return {
|
|
3534
3552
|
txHash,
|
|
@@ -3556,6 +3574,9 @@ var YieldseekerAgent = class {
|
|
|
3556
3574
|
this.id
|
|
3557
3575
|
);
|
|
3558
3576
|
}
|
|
3577
|
+
const generation = this.readGeneration;
|
|
3578
|
+
let confirmedMovement = false;
|
|
3579
|
+
let settlement = null;
|
|
3559
3580
|
try {
|
|
3560
3581
|
const portfolio = await this.loadPortfolioContext(
|
|
3561
3582
|
state,
|
|
@@ -3573,6 +3594,7 @@ var YieldseekerAgent = class {
|
|
|
3573
3594
|
);
|
|
3574
3595
|
const totalAvailable = idle + deployed;
|
|
3575
3596
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3597
|
+
const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
|
|
3576
3598
|
if (requested > totalAvailable) {
|
|
3577
3599
|
throw new OwneyError(
|
|
3578
3600
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -3608,6 +3630,7 @@ var YieldseekerAgent = class {
|
|
|
3608
3630
|
throw this.invalidResponse("position withdrawal");
|
|
3609
3631
|
}
|
|
3610
3632
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3633
|
+
confirmedMovement = true;
|
|
3611
3634
|
remaining -= assetsRaw;
|
|
3612
3635
|
}
|
|
3613
3636
|
if (remaining > 0n) {
|
|
@@ -3632,18 +3655,23 @@ var YieldseekerAgent = class {
|
|
|
3632
3655
|
value: "0",
|
|
3633
3656
|
chainId
|
|
3634
3657
|
});
|
|
3658
|
+
confirmedMovement = true;
|
|
3659
|
+
settlement = plannedMovement;
|
|
3635
3660
|
return {
|
|
3636
3661
|
txHash,
|
|
3637
3662
|
type: amount === void 0 ? "full" : "partial",
|
|
3638
3663
|
amount: requested.toString()
|
|
3639
3664
|
};
|
|
3640
3665
|
} finally {
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3666
|
+
if (confirmedMovement && this.readGeneration === generation) {
|
|
3667
|
+
await this.refreshSnapshotAfterMovement(
|
|
3668
|
+
state,
|
|
3669
|
+
chainId,
|
|
3670
|
+
context,
|
|
3671
|
+
"withdrawal",
|
|
3672
|
+
settlement
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3647
3675
|
}
|
|
3648
3676
|
}
|
|
3649
3677
|
async getBalances(state, chainId) {
|
|
@@ -3737,6 +3765,16 @@ var YieldseekerAgent = class {
|
|
|
3737
3765
|
contextKey(state, chainId, asset) {
|
|
3738
3766
|
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3739
3767
|
}
|
|
3768
|
+
assertSession(generation, chainId, asset) {
|
|
3769
|
+
if (this.readGeneration !== generation) {
|
|
3770
|
+
throw new OwneyError(
|
|
3771
|
+
"NOT_CONNECTED",
|
|
3772
|
+
"Wallet session changed during the Yieldseeker request.",
|
|
3773
|
+
{ rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
|
|
3774
|
+
this.id
|
|
3775
|
+
);
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3740
3778
|
cachedRead(key2, ttlMs, read2) {
|
|
3741
3779
|
const cached = this.readCache.get(key2);
|
|
3742
3780
|
if (cached && cached.expiresAt > Date.now()) {
|
|
@@ -3746,7 +3784,7 @@ var YieldseekerAgent = class {
|
|
|
3746
3784
|
if (pending) return pending;
|
|
3747
3785
|
const generation = this.readGeneration;
|
|
3748
3786
|
const request = Promise.resolve().then(read2).then((value) => {
|
|
3749
|
-
if (this.readGeneration === generation) {
|
|
3787
|
+
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
3750
3788
|
this.readCache.set(key2, {
|
|
3751
3789
|
expiresAt: Date.now() + ttlMs,
|
|
3752
3790
|
value
|
|
@@ -3794,6 +3832,7 @@ var YieldseekerAgent = class {
|
|
|
3794
3832
|
chainId,
|
|
3795
3833
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3796
3834
|
).then((walletResponse) => {
|
|
3835
|
+
this.assertSession(generation, chainId, asset);
|
|
3797
3836
|
if (!walletResponse?.agentWallet || !(0, import_viem7.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
3798
3837
|
throw this.invalidResponse("agent wallet");
|
|
3799
3838
|
}
|
|
@@ -3803,9 +3842,7 @@ var YieldseekerAgent = class {
|
|
|
3803
3842
|
wallet: walletResponse.agentWallet,
|
|
3804
3843
|
asset
|
|
3805
3844
|
};
|
|
3806
|
-
|
|
3807
|
-
this.agentContexts.set(key2, context);
|
|
3808
|
-
}
|
|
3845
|
+
this.agentContexts.set(key2, context);
|
|
3809
3846
|
return context;
|
|
3810
3847
|
});
|
|
3811
3848
|
this.pendingWalletContexts.set(key2, request);
|
|
@@ -3827,6 +3864,7 @@ var YieldseekerAgent = class {
|
|
|
3827
3864
|
return persisted;
|
|
3828
3865
|
}
|
|
3829
3866
|
const walletAddress = (0, import_viem7.getAddress)(state.walletAddress);
|
|
3867
|
+
const generation = this.readGeneration;
|
|
3830
3868
|
let user = null;
|
|
3831
3869
|
try {
|
|
3832
3870
|
const login = await this.providerRequest(
|
|
@@ -3873,6 +3911,7 @@ var YieldseekerAgent = class {
|
|
|
3873
3911
|
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3874
3912
|
throw this.invalidResponse("wallet identity");
|
|
3875
3913
|
}
|
|
3914
|
+
this.assertSession(generation, chainId);
|
|
3876
3915
|
const resolved = { userId: user.userId };
|
|
3877
3916
|
this.users.set(key2, resolved);
|
|
3878
3917
|
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
@@ -3888,10 +3927,13 @@ var YieldseekerAgent = class {
|
|
|
3888
3927
|
if (cached) return cached;
|
|
3889
3928
|
const pending = this.pendingAgents.get(key2);
|
|
3890
3929
|
if (pending) return pending;
|
|
3930
|
+
const generation = this.readGeneration;
|
|
3891
3931
|
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3892
3932
|
async (context) => {
|
|
3933
|
+
this.assertSession(generation, chainId, asset);
|
|
3893
3934
|
if (!context) throw this.invalidResponse("agent creation");
|
|
3894
3935
|
await this.deployAgent(state, chainId, context);
|
|
3936
|
+
this.assertSession(generation, chainId, asset);
|
|
3895
3937
|
this.agentContexts.set(key2, context);
|
|
3896
3938
|
return context;
|
|
3897
3939
|
}
|
|
@@ -3900,20 +3942,25 @@ var YieldseekerAgent = class {
|
|
|
3900
3942
|
try {
|
|
3901
3943
|
return await request;
|
|
3902
3944
|
} finally {
|
|
3903
|
-
this.pendingAgents.delete(key2);
|
|
3945
|
+
if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
|
|
3904
3946
|
}
|
|
3905
3947
|
}
|
|
3906
3948
|
async findAgent(state, chainId, asset) {
|
|
3907
3949
|
const key2 = this.contextKey(state, chainId, asset);
|
|
3908
3950
|
const cached = this.agentContexts.get(key2);
|
|
3909
3951
|
if (cached) return cached;
|
|
3952
|
+
const generation = this.readGeneration;
|
|
3910
3953
|
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3954
|
+
this.assertSession(generation, chainId, asset);
|
|
3911
3955
|
if (context) this.agentContexts.set(key2, context);
|
|
3912
3956
|
return context;
|
|
3913
3957
|
}
|
|
3914
3958
|
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3959
|
+
const generation = this.readGeneration;
|
|
3915
3960
|
const user = await this.resolveUser(state, chainId);
|
|
3961
|
+
this.assertSession(generation, chainId, asset);
|
|
3916
3962
|
const agents = await this.listAgents(state, chainId, user);
|
|
3963
|
+
this.assertSession(generation, chainId, asset);
|
|
3917
3964
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3918
3965
|
let agent = agents.find(
|
|
3919
3966
|
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
@@ -3935,6 +3982,7 @@ var YieldseekerAgent = class {
|
|
|
3935
3982
|
}
|
|
3936
3983
|
}
|
|
3937
3984
|
);
|
|
3985
|
+
this.assertSession(generation, chainId, asset);
|
|
3938
3986
|
agent = created?.agent;
|
|
3939
3987
|
if (agent) {
|
|
3940
3988
|
this.readCache.set(this.agentListKey(state, chainId), {
|
|
@@ -3950,8 +3998,11 @@ var YieldseekerAgent = class {
|
|
|
3950
3998
|
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3951
3999
|
}
|
|
3952
4000
|
async loadPortfolio(state, chainId, options) {
|
|
4001
|
+
const generation = this.readGeneration;
|
|
3953
4002
|
const user = await this.resolveUser(state, chainId);
|
|
4003
|
+
this.assertSession(generation, chainId);
|
|
3954
4004
|
const agents = await this.listAgents(state, chainId, user);
|
|
4005
|
+
this.assertSession(generation, chainId);
|
|
3955
4006
|
const contexts = [];
|
|
3956
4007
|
for (const agent of agents) {
|
|
3957
4008
|
const asset = this.assetForAgent(agent);
|
|
@@ -3962,81 +4013,187 @@ var YieldseekerAgent = class {
|
|
|
3962
4013
|
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
3963
4014
|
}
|
|
3964
4015
|
const resolvedContexts = await Promise.all(contexts);
|
|
4016
|
+
this.assertSession(generation, chainId);
|
|
3965
4017
|
return Promise.all(
|
|
3966
4018
|
resolvedContexts.map(
|
|
3967
4019
|
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3968
4020
|
)
|
|
3969
4021
|
);
|
|
3970
4022
|
}
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4023
|
+
portfolioVersion(contextKey) {
|
|
4024
|
+
return this.portfolioVersions.get(contextKey) ?? 0;
|
|
4025
|
+
}
|
|
4026
|
+
portfolioCacheMs(contextKey) {
|
|
4027
|
+
return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4028
|
+
}
|
|
4029
|
+
snapshotMovement(state, chainId, context, delta) {
|
|
4030
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
4031
|
+
const previous = this.snapshotMovements.get(key2);
|
|
4032
|
+
if (this.snapshotMovements.has(key2)) {
|
|
4033
|
+
if (!previous || previous.delta > 0n !== delta > 0n) return null;
|
|
4034
|
+
return {
|
|
4035
|
+
...previous,
|
|
4036
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4037
|
+
delta: previous.delta + delta,
|
|
4038
|
+
expectedBalance: previous.expectedBalance + delta,
|
|
4039
|
+
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4042
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4043
|
+
if (!cached || cached.expiresAt <= Date.now()) return null;
|
|
4044
|
+
const snapshot = cached.value.agentSnapshot;
|
|
4045
|
+
if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
|
|
4046
|
+
if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
|
|
4047
|
+
return {
|
|
4048
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4049
|
+
delta,
|
|
4050
|
+
decimals: snapshot.baseAssetDecimals,
|
|
4051
|
+
expectedBalance: BigInt(snapshot.totalValueBase) + delta,
|
|
4052
|
+
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
advancePortfolioVersion(contextKey) {
|
|
4056
|
+
const version = this.portfolioVersion(contextKey) + 1;
|
|
4057
|
+
this.portfolioVersions.set(contextKey, version);
|
|
4058
|
+
this.snapshotFailures.delete(contextKey);
|
|
4059
|
+
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4060
|
+
const key2 = `${kind}:${contextKey}`;
|
|
4061
|
+
this.readCache.delete(key2);
|
|
4062
|
+
this.pendingReads.delete(key2);
|
|
4063
|
+
}
|
|
4064
|
+
return version;
|
|
4065
|
+
}
|
|
4066
|
+
requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
|
|
4067
|
+
return this.walletRequest(
|
|
4068
|
+
state,
|
|
4069
|
+
chainId,
|
|
4070
|
+
`${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
|
|
4071
|
+
).then((response) => {
|
|
4072
|
+
if (!response?.agentSnapshot) {
|
|
4073
|
+
throw this.invalidResponse("agent snapshot");
|
|
4074
|
+
}
|
|
4075
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4076
|
+
this.snapshotFailures.delete(contextKey);
|
|
4077
|
+
const movement = this.snapshotMovements.get(contextKey);
|
|
4078
|
+
const snapshot = response.agentSnapshot;
|
|
4079
|
+
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4080
|
+
const balance = BigInt(snapshot.totalValueBase);
|
|
4081
|
+
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4082
|
+
const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
|
|
4083
|
+
if (reconciled) {
|
|
4084
|
+
this.snapshotMovements.delete(contextKey);
|
|
4085
|
+
this.reconcileUntil.delete(contextKey);
|
|
4003
4086
|
}
|
|
4004
|
-
return response;
|
|
4005
4087
|
}
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4088
|
+
}
|
|
4089
|
+
return response;
|
|
4090
|
+
}).catch((error) => {
|
|
4091
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4092
|
+
this.snapshotFailures.set(contextKey, {
|
|
4093
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
|
|
4094
|
+
error
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
throw error;
|
|
4098
|
+
});
|
|
4099
|
+
}
|
|
4100
|
+
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
4101
|
+
const key2 = `snapshot:${contextKey}`;
|
|
4102
|
+
const cached = this.readCache.get(key2);
|
|
4103
|
+
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
4104
|
+
const failure = this.snapshotFailures.get(contextKey);
|
|
4105
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4106
|
+
this.advancePortfolioVersion(contextKey);
|
|
4107
|
+
}
|
|
4108
|
+
const version = this.portfolioVersion(contextKey);
|
|
4109
|
+
const generation = this.readGeneration;
|
|
4110
|
+
return {
|
|
4111
|
+
version,
|
|
4112
|
+
read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
|
|
4113
|
+
// Only event-driven reconciliation forces refreshes. Routine reads
|
|
4114
|
+
// remain non-forced; the cache and shared cooldown still bound calls.
|
|
4115
|
+
this.requestPortfolioSnapshot(
|
|
4020
4116
|
state,
|
|
4021
4117
|
chainId,
|
|
4022
|
-
|
|
4118
|
+
context,
|
|
4119
|
+
contextKey,
|
|
4120
|
+
version,
|
|
4121
|
+
generation,
|
|
4122
|
+
this.snapshotMovements.has(contextKey)
|
|
4023
4123
|
)
|
|
4024
|
-
)
|
|
4025
|
-
]);
|
|
4026
|
-
if (!snapshot?.agentSnapshot) {
|
|
4027
|
-
throw this.invalidResponse("agent snapshot");
|
|
4028
|
-
}
|
|
4029
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
4030
|
-
throw this.invalidResponse("yield positions");
|
|
4031
|
-
}
|
|
4032
|
-
return {
|
|
4033
|
-
...context,
|
|
4034
|
-
snapshot: snapshot.agentSnapshot,
|
|
4035
|
-
positions: positions.yieldPositions,
|
|
4036
|
-
...historic?.position ? { historic: historic.position } : {},
|
|
4037
|
-
...actions?.actions ? { actions: actions.actions } : {}
|
|
4124
|
+
))
|
|
4038
4125
|
};
|
|
4039
4126
|
}
|
|
4127
|
+
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
4128
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4129
|
+
const generation = this.readGeneration;
|
|
4130
|
+
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4131
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4132
|
+
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4133
|
+
try {
|
|
4134
|
+
const snapshot = await read2;
|
|
4135
|
+
assertSession();
|
|
4136
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4137
|
+
const [positions, historic, actions] = await Promise.all([
|
|
4138
|
+
this.cachedRead(
|
|
4139
|
+
`positions:${contextKey}`,
|
|
4140
|
+
this.portfolioCacheMs(contextKey),
|
|
4141
|
+
async () => {
|
|
4142
|
+
const response = await this.walletRequest(
|
|
4143
|
+
state,
|
|
4144
|
+
chainId,
|
|
4145
|
+
this.agentPath(context, "yield-positions")
|
|
4146
|
+
);
|
|
4147
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
4148
|
+
throw this.invalidResponse("yield positions");
|
|
4149
|
+
}
|
|
4150
|
+
return response;
|
|
4151
|
+
}
|
|
4152
|
+
),
|
|
4153
|
+
options.historic ? this.cachedRead(
|
|
4154
|
+
`historic:${contextKey}`,
|
|
4155
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4156
|
+
() => this.walletRequest(
|
|
4157
|
+
state,
|
|
4158
|
+
chainId,
|
|
4159
|
+
this.agentPath(context, "wallet/historic-position")
|
|
4160
|
+
)
|
|
4161
|
+
) : Promise.resolve(void 0),
|
|
4162
|
+
options.actions ? this.cachedRead(
|
|
4163
|
+
`actions:${contextKey}`,
|
|
4164
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4165
|
+
() => this.walletRequest(
|
|
4166
|
+
state,
|
|
4167
|
+
chainId,
|
|
4168
|
+
this.agentPath(context, "actions")
|
|
4169
|
+
)
|
|
4170
|
+
) : Promise.resolve(void 0)
|
|
4171
|
+
]);
|
|
4172
|
+
assertSession();
|
|
4173
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4174
|
+
if (!Array.isArray(positions?.yieldPositions)) {
|
|
4175
|
+
throw this.invalidResponse("yield positions");
|
|
4176
|
+
}
|
|
4177
|
+
return {
|
|
4178
|
+
...context,
|
|
4179
|
+
snapshot: snapshot.agentSnapshot,
|
|
4180
|
+
positions: positions.yieldPositions,
|
|
4181
|
+
...historic?.position ? { historic: historic.position } : {},
|
|
4182
|
+
...actions?.actions ? { actions: actions.actions } : {}
|
|
4183
|
+
};
|
|
4184
|
+
} catch (error) {
|
|
4185
|
+
assertSession();
|
|
4186
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4187
|
+
throw error;
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
throw new OwneyError(
|
|
4191
|
+
"AGENT_API_ERROR",
|
|
4192
|
+
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4193
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4194
|
+
this.id
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4040
4197
|
async deployAgent(state, chainId, context) {
|
|
4041
4198
|
if (context.wallet.initializedDate != null) return;
|
|
4042
4199
|
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
@@ -4053,42 +4210,52 @@ var YieldseekerAgent = class {
|
|
|
4053
4210
|
}
|
|
4054
4211
|
context.wallet = deployed.agentWallet;
|
|
4055
4212
|
}
|
|
4056
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4213
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4057
4214
|
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4215
|
+
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4216
|
+
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4217
|
+
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4218
|
+
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4219
|
+
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4220
|
+
const version = this.advancePortfolioVersion(contextKey);
|
|
4221
|
+
const generation = this.readGeneration;
|
|
4064
4222
|
try {
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
shouldForceRefresh: true
|
|
4070
|
-
})}`
|
|
4223
|
+
await this.cachedRead(
|
|
4224
|
+
`snapshot:${contextKey}`,
|
|
4225
|
+
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4226
|
+
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
4071
4227
|
);
|
|
4072
|
-
if (!response?.agentSnapshot) {
|
|
4073
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
4074
|
-
}
|
|
4075
4228
|
} catch (error) {
|
|
4076
4229
|
console.warn(
|
|
4077
4230
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
4078
4231
|
error
|
|
4079
4232
|
);
|
|
4080
|
-
} finally {
|
|
4081
|
-
invalidatePortfolio();
|
|
4082
4233
|
}
|
|
4083
4234
|
}
|
|
4084
4235
|
agentPath(context, suffix) {
|
|
4085
4236
|
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
4086
4237
|
}
|
|
4087
4238
|
async walletRequest(state, chainId, path, options = {}) {
|
|
4239
|
+
const read2 = (options.method ?? "GET") === "GET";
|
|
4240
|
+
const userKey = this.userKey(state, chainId);
|
|
4241
|
+
if (read2) {
|
|
4242
|
+
const failure = this.standardReadFailures.get(userKey);
|
|
4243
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4244
|
+
if (failure) this.standardReadFailures.delete(userKey);
|
|
4245
|
+
}
|
|
4246
|
+
const generation = this.readGeneration;
|
|
4088
4247
|
try {
|
|
4089
4248
|
return await this.providerRequest(state, chainId, path, options);
|
|
4090
4249
|
} catch (error) {
|
|
4091
|
-
|
|
4250
|
+
const mapped = this.mapApiError(error);
|
|
4251
|
+
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4252
|
+
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4253
|
+
this.standardReadFailures.set(userKey, {
|
|
4254
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4255
|
+
error: mapped
|
|
4256
|
+
});
|
|
4257
|
+
}
|
|
4258
|
+
throw mapped;
|
|
4092
4259
|
}
|
|
4093
4260
|
}
|
|
4094
4261
|
async providerRequest(state, chainId, path, options = {}) {
|
|
@@ -4385,16 +4552,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4385
4552
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
4386
4553
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4387
4554
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4388
|
-
|
|
4389
|
-
try {
|
|
4390
|
-
balance += BigInt(position2.amountRaw);
|
|
4391
|
-
continue;
|
|
4392
|
-
} catch {
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4395
|
-
balance += (0, import_viem8.parseUnits)(position2.amount, decimals);
|
|
4555
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem8.parseUnits)(position2.amount, decimals);
|
|
4396
4556
|
}
|
|
4397
4557
|
}
|
|
4558
|
+
const assetTotal = agentBalance?.assetBalances?.find(
|
|
4559
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4560
|
+
);
|
|
4561
|
+
if (assetTotal) {
|
|
4562
|
+
const total = (0, import_viem8.parseUnits)(assetTotal.amount, decimals);
|
|
4563
|
+
if (total < balance) balance = total > 0n ? total : 0n;
|
|
4564
|
+
}
|
|
4398
4565
|
return { agent, balance };
|
|
4399
4566
|
});
|
|
4400
4567
|
}
|
|
@@ -6015,7 +6182,14 @@ var OwneySDK = class {
|
|
|
6015
6182
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
6016
6183
|
*/
|
|
6017
6184
|
async withdraw(options) {
|
|
6018
|
-
const { asset, amount, agentId } = options;
|
|
6185
|
+
const { asset, amount, agentId, onAgentResult } = options;
|
|
6186
|
+
const notifyAgentResult = (id, result) => {
|
|
6187
|
+
try {
|
|
6188
|
+
onAgentResult?.(id, result);
|
|
6189
|
+
} catch (error) {
|
|
6190
|
+
console.warn("Withdrawal result observer failed:", error);
|
|
6191
|
+
}
|
|
6192
|
+
};
|
|
6019
6193
|
const state = this.requireState();
|
|
6020
6194
|
const chainId = this.requireChainId();
|
|
6021
6195
|
const token = asset;
|
|
@@ -6031,12 +6205,14 @@ var OwneySDK = class {
|
|
|
6031
6205
|
if (agentId) {
|
|
6032
6206
|
const agent = this.getAgent(agentId);
|
|
6033
6207
|
this.validateAssetSupport(agent, chainId, asset);
|
|
6034
|
-
|
|
6208
|
+
const result = await withFailureReporting(
|
|
6035
6209
|
this.apiKey,
|
|
6036
6210
|
agent.id,
|
|
6037
6211
|
() => agent.withdraw(state, chainId, token, amount),
|
|
6038
6212
|
this.routingApiBaseUrl
|
|
6039
6213
|
);
|
|
6214
|
+
notifyAgentResult(agent.id, result);
|
|
6215
|
+
return result;
|
|
6040
6216
|
}
|
|
6041
6217
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6042
6218
|
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
@@ -6046,6 +6222,7 @@ var OwneySDK = class {
|
|
|
6046
6222
|
for (const agent of withdrawalAgents) {
|
|
6047
6223
|
try {
|
|
6048
6224
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
6225
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
6049
6226
|
} catch (err) {
|
|
6050
6227
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6051
6228
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
@@ -6143,6 +6320,7 @@ var OwneySDK = class {
|
|
|
6143
6320
|
token,
|
|
6144
6321
|
p.planned.toString()
|
|
6145
6322
|
);
|
|
6323
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
6146
6324
|
} catch (err) {
|
|
6147
6325
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6148
6326
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|