@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.js
CHANGED
|
@@ -2887,10 +2887,11 @@ var YieldseekerApiClient = class {
|
|
|
2887
2887
|
const payload = await response.json().catch(() => null);
|
|
2888
2888
|
if (!response.ok) {
|
|
2889
2889
|
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2890
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
2890
2891
|
throw new YieldseekerApiError(
|
|
2891
2892
|
response.status,
|
|
2892
2893
|
error.code,
|
|
2893
|
-
error.fields
|
|
2894
|
+
retryAfter ? { ...error.fields, headers: { "Retry-After": retryAfter } } : error.fields
|
|
2894
2895
|
);
|
|
2895
2896
|
}
|
|
2896
2897
|
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
@@ -3021,13 +3022,14 @@ function position(value, asset, baseAssetDecimals) {
|
|
|
3021
3022
|
// differ from the underlying asset. Yieldseeker already converts it to
|
|
3022
3023
|
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
3023
3024
|
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
3024
|
-
// share quantity
|
|
3025
|
+
// share quantity separate from the withdrawable underlying asset amount.
|
|
3025
3026
|
amount: decimal(
|
|
3026
3027
|
value.assetsBase,
|
|
3027
3028
|
baseAssetDecimals,
|
|
3028
3029
|
"yield positions"
|
|
3029
3030
|
),
|
|
3030
3031
|
amountRaw: String(value.assetsRaw),
|
|
3032
|
+
withdrawableAmountRaw: String(value.withdrawableAssetsRaw),
|
|
3031
3033
|
apy: percent(option.riskAdjustedApy),
|
|
3032
3034
|
tvl: Number(option.totalDepositsUsd),
|
|
3033
3035
|
liquidity: Number(option.withdrawableDepositsUsd)
|
|
@@ -3356,8 +3358,12 @@ var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
|
3356
3358
|
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3357
3359
|
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3358
3360
|
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3359
|
-
var YIELDSEEKER_AGENT_LIST_CACHE_MS =
|
|
3360
|
-
var YIELDSEEKER_PORTFOLIO_CACHE_MS =
|
|
3361
|
+
var YIELDSEEKER_AGENT_LIST_CACHE_MS = 12e4;
|
|
3362
|
+
var YIELDSEEKER_PORTFOLIO_CACHE_MS = 12e4;
|
|
3363
|
+
var YIELDSEEKER_SETTLEMENT_CACHE_MS = 5e3;
|
|
3364
|
+
var YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS = 15e3;
|
|
3365
|
+
var YIELDSEEKER_READ_FAILURE_COOLDOWN_MS = 6e4;
|
|
3366
|
+
var YIELDSEEKER_SETTLEMENT_WINDOW_MS = 5 * 6e4;
|
|
3361
3367
|
var YIELDSEEKER_ACTIVITY_CACHE_MS = 6e4;
|
|
3362
3368
|
function generateYieldseekerUsername() {
|
|
3363
3369
|
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
@@ -3427,6 +3433,15 @@ var YieldseekerAgent = class {
|
|
|
3427
3433
|
readCache = /* @__PURE__ */ new Map();
|
|
3428
3434
|
pendingReads = /* @__PURE__ */ new Map();
|
|
3429
3435
|
readGeneration = 0;
|
|
3436
|
+
portfolioVersions = /* @__PURE__ */ new Map();
|
|
3437
|
+
snapshotFailures = /* @__PURE__ */ new Map();
|
|
3438
|
+
standardReadFailures = /* @__PURE__ */ new Map();
|
|
3439
|
+
reconcileUntil = /* @__PURE__ */ new Map();
|
|
3440
|
+
activityRefreshUntil = /* @__PURE__ */ new Map();
|
|
3441
|
+
// null means a confirmed movement has no reliable baseline. Do not guess
|
|
3442
|
+
// settlement; keep slow reconciliation after the fast window expires.
|
|
3443
|
+
snapshotMovements = /* @__PURE__ */ new Map();
|
|
3444
|
+
movementEpochs = /* @__PURE__ */ new Map();
|
|
3430
3445
|
yieldOptions = /* @__PURE__ */ new Map();
|
|
3431
3446
|
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3432
3447
|
constructor(owneyApiKey, options = {}) {
|
|
@@ -3457,6 +3472,13 @@ var YieldseekerAgent = class {
|
|
|
3457
3472
|
this.pendingWalletContexts.clear();
|
|
3458
3473
|
this.readCache.clear();
|
|
3459
3474
|
this.pendingReads.clear();
|
|
3475
|
+
this.portfolioVersions.clear();
|
|
3476
|
+
this.snapshotFailures.clear();
|
|
3477
|
+
this.standardReadFailures.clear();
|
|
3478
|
+
this.reconcileUntil.clear();
|
|
3479
|
+
this.activityRefreshUntil.clear();
|
|
3480
|
+
this.snapshotMovements.clear();
|
|
3481
|
+
this.movementEpochs.clear();
|
|
3460
3482
|
}
|
|
3461
3483
|
async activateAgent(state, chainId, asset) {
|
|
3462
3484
|
this.assertChain(chainId);
|
|
@@ -3476,41 +3498,37 @@ var YieldseekerAgent = class {
|
|
|
3476
3498
|
);
|
|
3477
3499
|
}
|
|
3478
3500
|
const context = await this.ensureAgent(state, chainId, asset);
|
|
3501
|
+
const generation = this.readGeneration;
|
|
3502
|
+
const movement = this.snapshotMovement(state, chainId, context, BigInt(amount));
|
|
3479
3503
|
let txHash;
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
context.wallet.walletAddress,
|
|
3490
|
-
chainId,
|
|
3491
|
-
amount
|
|
3492
|
-
);
|
|
3493
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3494
|
-
} else {
|
|
3495
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3496
|
-
from: getAddress2(state.walletAddress),
|
|
3497
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3498
|
-
data: encodeFunctionData({
|
|
3499
|
-
abi: erc20Abi,
|
|
3500
|
-
functionName: "transfer",
|
|
3501
|
-
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3502
|
-
}),
|
|
3503
|
-
value: "0",
|
|
3504
|
-
chainId
|
|
3505
|
-
});
|
|
3506
|
-
}
|
|
3507
|
-
} finally {
|
|
3508
|
-
await this.refreshSnapshotAfterMovement(
|
|
3509
|
-
state,
|
|
3504
|
+
if (depositCallback) {
|
|
3505
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3506
|
+
agentId: "yieldseeker",
|
|
3507
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3508
|
+
userId: context.user.userId,
|
|
3509
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3510
|
+
});
|
|
3511
|
+
txHash = await depositCallback(
|
|
3512
|
+
context.wallet.walletAddress,
|
|
3510
3513
|
chainId,
|
|
3511
|
-
|
|
3512
|
-
"deposit"
|
|
3514
|
+
amount
|
|
3513
3515
|
);
|
|
3516
|
+
await this.waitForReceipt(state, chainId, txHash);
|
|
3517
|
+
} else {
|
|
3518
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3519
|
+
from: getAddress2(state.walletAddress),
|
|
3520
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3521
|
+
data: encodeFunctionData({
|
|
3522
|
+
abi: erc20Abi,
|
|
3523
|
+
functionName: "transfer",
|
|
3524
|
+
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3525
|
+
}),
|
|
3526
|
+
value: "0",
|
|
3527
|
+
chainId
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3530
|
+
if (this.readGeneration === generation) {
|
|
3531
|
+
await this.refreshSnapshotAfterMovement(state, chainId, context, "deposit", movement);
|
|
3514
3532
|
}
|
|
3515
3533
|
return {
|
|
3516
3534
|
txHash,
|
|
@@ -3538,6 +3556,9 @@ var YieldseekerAgent = class {
|
|
|
3538
3556
|
this.id
|
|
3539
3557
|
);
|
|
3540
3558
|
}
|
|
3559
|
+
const generation = this.readGeneration;
|
|
3560
|
+
let confirmedMovement = false;
|
|
3561
|
+
let settlement = null;
|
|
3541
3562
|
try {
|
|
3542
3563
|
const portfolio = await this.loadPortfolioContext(
|
|
3543
3564
|
state,
|
|
@@ -3555,6 +3576,7 @@ var YieldseekerAgent = class {
|
|
|
3555
3576
|
);
|
|
3556
3577
|
const totalAvailable = idle + deployed;
|
|
3557
3578
|
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3579
|
+
const plannedMovement = this.snapshotMovement(state, chainId, context, -requested);
|
|
3558
3580
|
if (requested > totalAvailable) {
|
|
3559
3581
|
throw new OwneyError(
|
|
3560
3582
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
@@ -3590,6 +3612,7 @@ var YieldseekerAgent = class {
|
|
|
3590
3612
|
throw this.invalidResponse("position withdrawal");
|
|
3591
3613
|
}
|
|
3592
3614
|
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3615
|
+
confirmedMovement = true;
|
|
3593
3616
|
remaining -= assetsRaw;
|
|
3594
3617
|
}
|
|
3595
3618
|
if (remaining > 0n) {
|
|
@@ -3614,18 +3637,23 @@ var YieldseekerAgent = class {
|
|
|
3614
3637
|
value: "0",
|
|
3615
3638
|
chainId
|
|
3616
3639
|
});
|
|
3640
|
+
confirmedMovement = true;
|
|
3641
|
+
settlement = plannedMovement;
|
|
3617
3642
|
return {
|
|
3618
3643
|
txHash,
|
|
3619
3644
|
type: amount === void 0 ? "full" : "partial",
|
|
3620
3645
|
amount: requested.toString()
|
|
3621
3646
|
};
|
|
3622
3647
|
} finally {
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3648
|
+
if (confirmedMovement && this.readGeneration === generation) {
|
|
3649
|
+
await this.refreshSnapshotAfterMovement(
|
|
3650
|
+
state,
|
|
3651
|
+
chainId,
|
|
3652
|
+
context,
|
|
3653
|
+
"withdrawal",
|
|
3654
|
+
settlement
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3629
3657
|
}
|
|
3630
3658
|
}
|
|
3631
3659
|
async getBalances(state, chainId) {
|
|
@@ -3719,6 +3747,16 @@ var YieldseekerAgent = class {
|
|
|
3719
3747
|
contextKey(state, chainId, asset) {
|
|
3720
3748
|
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3721
3749
|
}
|
|
3750
|
+
assertSession(generation, chainId, asset) {
|
|
3751
|
+
if (this.readGeneration !== generation) {
|
|
3752
|
+
throw new OwneyError(
|
|
3753
|
+
"NOT_CONNECTED",
|
|
3754
|
+
"Wallet session changed during the Yieldseeker request.",
|
|
3755
|
+
{ rpcSource: "agent-api", chainId, ...asset ? { asset } : {} },
|
|
3756
|
+
this.id
|
|
3757
|
+
);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3722
3760
|
cachedRead(key2, ttlMs, read2) {
|
|
3723
3761
|
const cached = this.readCache.get(key2);
|
|
3724
3762
|
if (cached && cached.expiresAt > Date.now()) {
|
|
@@ -3728,7 +3766,7 @@ var YieldseekerAgent = class {
|
|
|
3728
3766
|
if (pending) return pending;
|
|
3729
3767
|
const generation = this.readGeneration;
|
|
3730
3768
|
const request = Promise.resolve().then(read2).then((value) => {
|
|
3731
|
-
if (this.readGeneration === generation) {
|
|
3769
|
+
if (this.readGeneration === generation && this.pendingReads.get(key2) === request) {
|
|
3732
3770
|
this.readCache.set(key2, {
|
|
3733
3771
|
expiresAt: Date.now() + ttlMs,
|
|
3734
3772
|
value
|
|
@@ -3776,6 +3814,7 @@ var YieldseekerAgent = class {
|
|
|
3776
3814
|
chainId,
|
|
3777
3815
|
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3778
3816
|
).then((walletResponse) => {
|
|
3817
|
+
this.assertSession(generation, chainId, asset);
|
|
3779
3818
|
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3780
3819
|
throw this.invalidResponse("agent wallet");
|
|
3781
3820
|
}
|
|
@@ -3785,9 +3824,7 @@ var YieldseekerAgent = class {
|
|
|
3785
3824
|
wallet: walletResponse.agentWallet,
|
|
3786
3825
|
asset
|
|
3787
3826
|
};
|
|
3788
|
-
|
|
3789
|
-
this.agentContexts.set(key2, context);
|
|
3790
|
-
}
|
|
3827
|
+
this.agentContexts.set(key2, context);
|
|
3791
3828
|
return context;
|
|
3792
3829
|
});
|
|
3793
3830
|
this.pendingWalletContexts.set(key2, request);
|
|
@@ -3809,6 +3846,7 @@ var YieldseekerAgent = class {
|
|
|
3809
3846
|
return persisted;
|
|
3810
3847
|
}
|
|
3811
3848
|
const walletAddress = getAddress2(state.walletAddress);
|
|
3849
|
+
const generation = this.readGeneration;
|
|
3812
3850
|
let user = null;
|
|
3813
3851
|
try {
|
|
3814
3852
|
const login = await this.providerRequest(
|
|
@@ -3855,6 +3893,7 @@ var YieldseekerAgent = class {
|
|
|
3855
3893
|
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3856
3894
|
throw this.invalidResponse("wallet identity");
|
|
3857
3895
|
}
|
|
3896
|
+
this.assertSession(generation, chainId);
|
|
3858
3897
|
const resolved = { userId: user.userId };
|
|
3859
3898
|
this.users.set(key2, resolved);
|
|
3860
3899
|
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
@@ -3870,10 +3909,13 @@ var YieldseekerAgent = class {
|
|
|
3870
3909
|
if (cached) return cached;
|
|
3871
3910
|
const pending = this.pendingAgents.get(key2);
|
|
3872
3911
|
if (pending) return pending;
|
|
3912
|
+
const generation = this.readGeneration;
|
|
3873
3913
|
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3874
3914
|
async (context) => {
|
|
3915
|
+
this.assertSession(generation, chainId, asset);
|
|
3875
3916
|
if (!context) throw this.invalidResponse("agent creation");
|
|
3876
3917
|
await this.deployAgent(state, chainId, context);
|
|
3918
|
+
this.assertSession(generation, chainId, asset);
|
|
3877
3919
|
this.agentContexts.set(key2, context);
|
|
3878
3920
|
return context;
|
|
3879
3921
|
}
|
|
@@ -3882,20 +3924,25 @@ var YieldseekerAgent = class {
|
|
|
3882
3924
|
try {
|
|
3883
3925
|
return await request;
|
|
3884
3926
|
} finally {
|
|
3885
|
-
this.pendingAgents.delete(key2);
|
|
3927
|
+
if (this.pendingAgents.get(key2) === request) this.pendingAgents.delete(key2);
|
|
3886
3928
|
}
|
|
3887
3929
|
}
|
|
3888
3930
|
async findAgent(state, chainId, asset) {
|
|
3889
3931
|
const key2 = this.contextKey(state, chainId, asset);
|
|
3890
3932
|
const cached = this.agentContexts.get(key2);
|
|
3891
3933
|
if (cached) return cached;
|
|
3934
|
+
const generation = this.readGeneration;
|
|
3892
3935
|
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3936
|
+
this.assertSession(generation, chainId, asset);
|
|
3893
3937
|
if (context) this.agentContexts.set(key2, context);
|
|
3894
3938
|
return context;
|
|
3895
3939
|
}
|
|
3896
3940
|
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3941
|
+
const generation = this.readGeneration;
|
|
3897
3942
|
const user = await this.resolveUser(state, chainId);
|
|
3943
|
+
this.assertSession(generation, chainId, asset);
|
|
3898
3944
|
const agents = await this.listAgents(state, chainId, user);
|
|
3945
|
+
this.assertSession(generation, chainId, asset);
|
|
3899
3946
|
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3900
3947
|
let agent = agents.find(
|
|
3901
3948
|
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
@@ -3917,6 +3964,7 @@ var YieldseekerAgent = class {
|
|
|
3917
3964
|
}
|
|
3918
3965
|
}
|
|
3919
3966
|
);
|
|
3967
|
+
this.assertSession(generation, chainId, asset);
|
|
3920
3968
|
agent = created?.agent;
|
|
3921
3969
|
if (agent) {
|
|
3922
3970
|
this.readCache.set(this.agentListKey(state, chainId), {
|
|
@@ -3932,8 +3980,11 @@ var YieldseekerAgent = class {
|
|
|
3932
3980
|
return this.contextForAgent(state, chainId, user, agent, asset);
|
|
3933
3981
|
}
|
|
3934
3982
|
async loadPortfolio(state, chainId, options) {
|
|
3983
|
+
const generation = this.readGeneration;
|
|
3935
3984
|
const user = await this.resolveUser(state, chainId);
|
|
3985
|
+
this.assertSession(generation, chainId);
|
|
3936
3986
|
const agents = await this.listAgents(state, chainId, user);
|
|
3987
|
+
this.assertSession(generation, chainId);
|
|
3937
3988
|
const contexts = [];
|
|
3938
3989
|
for (const agent of agents) {
|
|
3939
3990
|
const asset = this.assetForAgent(agent);
|
|
@@ -3944,81 +3995,187 @@ var YieldseekerAgent = class {
|
|
|
3944
3995
|
contexts.push(this.contextForAgent(state, chainId, user, agent, asset));
|
|
3945
3996
|
}
|
|
3946
3997
|
const resolvedContexts = await Promise.all(contexts);
|
|
3998
|
+
this.assertSession(generation, chainId);
|
|
3947
3999
|
return Promise.all(
|
|
3948
4000
|
resolvedContexts.map(
|
|
3949
4001
|
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3950
4002
|
)
|
|
3951
4003
|
);
|
|
3952
4004
|
}
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4005
|
+
portfolioVersion(contextKey) {
|
|
4006
|
+
return this.portfolioVersions.get(contextKey) ?? 0;
|
|
4007
|
+
}
|
|
4008
|
+
portfolioCacheMs(contextKey) {
|
|
4009
|
+
return (this.reconcileUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_CACHE_MS : YIELDSEEKER_PORTFOLIO_CACHE_MS;
|
|
4010
|
+
}
|
|
4011
|
+
snapshotMovement(state, chainId, context, delta) {
|
|
4012
|
+
const key2 = this.contextKey(state, chainId, context.asset);
|
|
4013
|
+
const previous = this.snapshotMovements.get(key2);
|
|
4014
|
+
if (this.snapshotMovements.has(key2)) {
|
|
4015
|
+
if (!previous || previous.delta > 0n !== delta > 0n) return null;
|
|
4016
|
+
return {
|
|
4017
|
+
...previous,
|
|
4018
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4019
|
+
delta: previous.delta + delta,
|
|
4020
|
+
expectedBalance: previous.expectedBalance + delta,
|
|
4021
|
+
expectedNetDeposits: previous.expectedNetDeposits + delta
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
4024
|
+
const cached = this.readCache.get(`snapshot:${key2}`);
|
|
4025
|
+
if (!cached || cached.expiresAt <= Date.now()) return null;
|
|
4026
|
+
const snapshot = cached.value.agentSnapshot;
|
|
4027
|
+
if (snapshot.baseAssetDecimals !== YIELDSEEKER_ASSET_METADATA[context.asset].decimals) return null;
|
|
4028
|
+
if (!/^-?\d+$/.test(snapshot.netDepositsBase) || !/^\d+$/.test(snapshot.totalValueBase)) return null;
|
|
4029
|
+
return {
|
|
4030
|
+
epoch: this.movementEpochs.get(key2) ?? 0,
|
|
4031
|
+
delta,
|
|
4032
|
+
decimals: snapshot.baseAssetDecimals,
|
|
4033
|
+
expectedBalance: BigInt(snapshot.totalValueBase) + delta,
|
|
4034
|
+
expectedNetDeposits: BigInt(snapshot.netDepositsBase) + delta
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
advancePortfolioVersion(contextKey) {
|
|
4038
|
+
const version = this.portfolioVersion(contextKey) + 1;
|
|
4039
|
+
this.portfolioVersions.set(contextKey, version);
|
|
4040
|
+
this.snapshotFailures.delete(contextKey);
|
|
4041
|
+
for (const kind of ["snapshot", "positions", "historic", "actions"]) {
|
|
4042
|
+
const key2 = `${kind}:${contextKey}`;
|
|
4043
|
+
this.readCache.delete(key2);
|
|
4044
|
+
this.pendingReads.delete(key2);
|
|
4045
|
+
}
|
|
4046
|
+
return version;
|
|
4047
|
+
}
|
|
4048
|
+
requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, forceRefresh = false) {
|
|
4049
|
+
return this.walletRequest(
|
|
4050
|
+
state,
|
|
4051
|
+
chainId,
|
|
4052
|
+
`${this.agentPath(context, "snapshot")}${query(forceRefresh ? { shouldForceRefresh: true } : { shouldOnlyUseRecentValue: true, shouldAllowStaleOnError: true })}`
|
|
4053
|
+
).then((response) => {
|
|
4054
|
+
if (!response?.agentSnapshot) {
|
|
4055
|
+
throw this.invalidResponse("agent snapshot");
|
|
4056
|
+
}
|
|
4057
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4058
|
+
this.snapshotFailures.delete(contextKey);
|
|
4059
|
+
const movement = this.snapshotMovements.get(contextKey);
|
|
4060
|
+
const snapshot = response.agentSnapshot;
|
|
4061
|
+
if (movement && snapshot.baseAssetDecimals === movement.decimals && /^-?\d+$/.test(snapshot.netDepositsBase) && /^\d+$/.test(snapshot.totalValueBase)) {
|
|
4062
|
+
const balance = BigInt(snapshot.totalValueBase);
|
|
4063
|
+
const netDeposits = BigInt(snapshot.netDepositsBase);
|
|
4064
|
+
const reconciled = movement.delta > 0n ? balance >= movement.expectedBalance && netDeposits >= movement.expectedNetDeposits : balance <= movement.expectedBalance && netDeposits <= movement.expectedNetDeposits;
|
|
4065
|
+
if (reconciled) {
|
|
4066
|
+
this.snapshotMovements.delete(contextKey);
|
|
4067
|
+
this.reconcileUntil.delete(contextKey);
|
|
3985
4068
|
}
|
|
3986
|
-
return response;
|
|
3987
4069
|
}
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4070
|
+
}
|
|
4071
|
+
return response;
|
|
4072
|
+
}).catch((error) => {
|
|
4073
|
+
if (this.readGeneration === generation && this.portfolioVersion(contextKey) === version) {
|
|
4074
|
+
this.snapshotFailures.set(contextKey, {
|
|
4075
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, rateLimitDelay(error) ?? 0),
|
|
4076
|
+
error
|
|
4077
|
+
});
|
|
4078
|
+
}
|
|
4079
|
+
throw error;
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
portfolioSnapshot(state, chainId, context, contextKey) {
|
|
4083
|
+
const key2 = `snapshot:${contextKey}`;
|
|
4084
|
+
const cached = this.readCache.get(key2);
|
|
4085
|
+
if ((!cached || cached.expiresAt <= Date.now()) && !this.pendingReads.has(key2)) {
|
|
4086
|
+
const failure = this.snapshotFailures.get(contextKey);
|
|
4087
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4088
|
+
this.advancePortfolioVersion(contextKey);
|
|
4089
|
+
}
|
|
4090
|
+
const version = this.portfolioVersion(contextKey);
|
|
4091
|
+
const generation = this.readGeneration;
|
|
4092
|
+
return {
|
|
4093
|
+
version,
|
|
4094
|
+
read: this.cachedRead(key2, this.portfolioCacheMs(contextKey), () => (
|
|
4095
|
+
// Only event-driven reconciliation forces refreshes. Routine reads
|
|
4096
|
+
// remain non-forced; the cache and shared cooldown still bound calls.
|
|
4097
|
+
this.requestPortfolioSnapshot(
|
|
4002
4098
|
state,
|
|
4003
4099
|
chainId,
|
|
4004
|
-
|
|
4100
|
+
context,
|
|
4101
|
+
contextKey,
|
|
4102
|
+
version,
|
|
4103
|
+
generation,
|
|
4104
|
+
this.snapshotMovements.has(contextKey)
|
|
4005
4105
|
)
|
|
4006
|
-
)
|
|
4007
|
-
]);
|
|
4008
|
-
if (!snapshot?.agentSnapshot) {
|
|
4009
|
-
throw this.invalidResponse("agent snapshot");
|
|
4010
|
-
}
|
|
4011
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
4012
|
-
throw this.invalidResponse("yield positions");
|
|
4013
|
-
}
|
|
4014
|
-
return {
|
|
4015
|
-
...context,
|
|
4016
|
-
snapshot: snapshot.agentSnapshot,
|
|
4017
|
-
positions: positions.yieldPositions,
|
|
4018
|
-
...historic?.position ? { historic: historic.position } : {},
|
|
4019
|
-
...actions?.actions ? { actions: actions.actions } : {}
|
|
4106
|
+
))
|
|
4020
4107
|
};
|
|
4021
4108
|
}
|
|
4109
|
+
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
4110
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4111
|
+
const generation = this.readGeneration;
|
|
4112
|
+
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4113
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4114
|
+
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4115
|
+
try {
|
|
4116
|
+
const snapshot = await read2;
|
|
4117
|
+
assertSession();
|
|
4118
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4119
|
+
const [positions, historic, actions] = await Promise.all([
|
|
4120
|
+
this.cachedRead(
|
|
4121
|
+
`positions:${contextKey}`,
|
|
4122
|
+
this.portfolioCacheMs(contextKey),
|
|
4123
|
+
async () => {
|
|
4124
|
+
const response = await this.walletRequest(
|
|
4125
|
+
state,
|
|
4126
|
+
chainId,
|
|
4127
|
+
this.agentPath(context, "yield-positions")
|
|
4128
|
+
);
|
|
4129
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
4130
|
+
throw this.invalidResponse("yield positions");
|
|
4131
|
+
}
|
|
4132
|
+
return response;
|
|
4133
|
+
}
|
|
4134
|
+
),
|
|
4135
|
+
options.historic ? this.cachedRead(
|
|
4136
|
+
`historic:${contextKey}`,
|
|
4137
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4138
|
+
() => this.walletRequest(
|
|
4139
|
+
state,
|
|
4140
|
+
chainId,
|
|
4141
|
+
this.agentPath(context, "wallet/historic-position")
|
|
4142
|
+
)
|
|
4143
|
+
) : Promise.resolve(void 0),
|
|
4144
|
+
options.actions ? this.cachedRead(
|
|
4145
|
+
`actions:${contextKey}`,
|
|
4146
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4147
|
+
() => this.walletRequest(
|
|
4148
|
+
state,
|
|
4149
|
+
chainId,
|
|
4150
|
+
this.agentPath(context, "actions")
|
|
4151
|
+
)
|
|
4152
|
+
) : Promise.resolve(void 0)
|
|
4153
|
+
]);
|
|
4154
|
+
assertSession();
|
|
4155
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4156
|
+
if (!Array.isArray(positions?.yieldPositions)) {
|
|
4157
|
+
throw this.invalidResponse("yield positions");
|
|
4158
|
+
}
|
|
4159
|
+
return {
|
|
4160
|
+
...context,
|
|
4161
|
+
snapshot: snapshot.agentSnapshot,
|
|
4162
|
+
positions: positions.yieldPositions,
|
|
4163
|
+
...historic?.position ? { historic: historic.position } : {},
|
|
4164
|
+
...actions?.actions ? { actions: actions.actions } : {}
|
|
4165
|
+
};
|
|
4166
|
+
} catch (error) {
|
|
4167
|
+
assertSession();
|
|
4168
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4169
|
+
throw error;
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
throw new OwneyError(
|
|
4173
|
+
"AGENT_API_ERROR",
|
|
4174
|
+
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4175
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4176
|
+
this.id
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
4022
4179
|
async deployAgent(state, chainId, context) {
|
|
4023
4180
|
if (context.wallet.initializedDate != null) return;
|
|
4024
4181
|
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
@@ -4035,42 +4192,52 @@ var YieldseekerAgent = class {
|
|
|
4035
4192
|
}
|
|
4036
4193
|
context.wallet = deployed.agentWallet;
|
|
4037
4194
|
}
|
|
4038
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4195
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4039
4196
|
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4197
|
+
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4198
|
+
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4199
|
+
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4200
|
+
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4201
|
+
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4202
|
+
const version = this.advancePortfolioVersion(contextKey);
|
|
4203
|
+
const generation = this.readGeneration;
|
|
4046
4204
|
try {
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
shouldForceRefresh: true
|
|
4052
|
-
})}`
|
|
4205
|
+
await this.cachedRead(
|
|
4206
|
+
`snapshot:${contextKey}`,
|
|
4207
|
+
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4208
|
+
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
4053
4209
|
);
|
|
4054
|
-
if (!response?.agentSnapshot) {
|
|
4055
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
4056
|
-
}
|
|
4057
4210
|
} catch (error) {
|
|
4058
4211
|
console.warn(
|
|
4059
4212
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
4060
4213
|
error
|
|
4061
4214
|
);
|
|
4062
|
-
} finally {
|
|
4063
|
-
invalidatePortfolio();
|
|
4064
4215
|
}
|
|
4065
4216
|
}
|
|
4066
4217
|
agentPath(context, suffix) {
|
|
4067
4218
|
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
4068
4219
|
}
|
|
4069
4220
|
async walletRequest(state, chainId, path, options = {}) {
|
|
4221
|
+
const read2 = (options.method ?? "GET") === "GET";
|
|
4222
|
+
const userKey = this.userKey(state, chainId);
|
|
4223
|
+
if (read2) {
|
|
4224
|
+
const failure = this.standardReadFailures.get(userKey);
|
|
4225
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4226
|
+
if (failure) this.standardReadFailures.delete(userKey);
|
|
4227
|
+
}
|
|
4228
|
+
const generation = this.readGeneration;
|
|
4070
4229
|
try {
|
|
4071
4230
|
return await this.providerRequest(state, chainId, path, options);
|
|
4072
4231
|
} catch (error) {
|
|
4073
|
-
|
|
4232
|
+
const mapped = this.mapApiError(error);
|
|
4233
|
+
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4234
|
+
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4235
|
+
this.standardReadFailures.set(userKey, {
|
|
4236
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4237
|
+
error: mapped
|
|
4238
|
+
});
|
|
4239
|
+
}
|
|
4240
|
+
throw mapped;
|
|
4074
4241
|
}
|
|
4075
4242
|
}
|
|
4076
4243
|
async providerRequest(state, chainId, path, options = {}) {
|
|
@@ -4367,16 +4534,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4367
4534
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
4368
4535
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4369
4536
|
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4370
|
-
|
|
4371
|
-
try {
|
|
4372
|
-
balance += BigInt(position2.amountRaw);
|
|
4373
|
-
continue;
|
|
4374
|
-
} catch {
|
|
4375
|
-
}
|
|
4376
|
-
}
|
|
4377
|
-
balance += parseUnits(position2.amount, decimals);
|
|
4537
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : parseUnits(position2.amount, decimals);
|
|
4378
4538
|
}
|
|
4379
4539
|
}
|
|
4540
|
+
const assetTotal = agentBalance?.assetBalances?.find(
|
|
4541
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4542
|
+
);
|
|
4543
|
+
if (assetTotal) {
|
|
4544
|
+
const total = parseUnits(assetTotal.amount, decimals);
|
|
4545
|
+
if (total < balance) balance = total > 0n ? total : 0n;
|
|
4546
|
+
}
|
|
4380
4547
|
return { agent, balance };
|
|
4381
4548
|
});
|
|
4382
4549
|
}
|
|
@@ -6005,7 +6172,14 @@ var OwneySDK = class {
|
|
|
6005
6172
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
6006
6173
|
*/
|
|
6007
6174
|
async withdraw(options) {
|
|
6008
|
-
const { asset, amount, agentId } = options;
|
|
6175
|
+
const { asset, amount, agentId, onAgentResult } = options;
|
|
6176
|
+
const notifyAgentResult = (id, result) => {
|
|
6177
|
+
try {
|
|
6178
|
+
onAgentResult?.(id, result);
|
|
6179
|
+
} catch (error) {
|
|
6180
|
+
console.warn("Withdrawal result observer failed:", error);
|
|
6181
|
+
}
|
|
6182
|
+
};
|
|
6009
6183
|
const state = this.requireState();
|
|
6010
6184
|
const chainId = this.requireChainId();
|
|
6011
6185
|
const token = asset;
|
|
@@ -6021,12 +6195,14 @@ var OwneySDK = class {
|
|
|
6021
6195
|
if (agentId) {
|
|
6022
6196
|
const agent = this.getAgent(agentId);
|
|
6023
6197
|
this.validateAssetSupport(agent, chainId, asset);
|
|
6024
|
-
|
|
6198
|
+
const result = await withFailureReporting(
|
|
6025
6199
|
this.apiKey,
|
|
6026
6200
|
agent.id,
|
|
6027
6201
|
() => agent.withdraw(state, chainId, token, amount),
|
|
6028
6202
|
this.routingApiBaseUrl
|
|
6029
6203
|
);
|
|
6204
|
+
notifyAgentResult(agent.id, result);
|
|
6205
|
+
return result;
|
|
6030
6206
|
}
|
|
6031
6207
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6032
6208
|
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
@@ -6036,6 +6212,7 @@ var OwneySDK = class {
|
|
|
6036
6212
|
for (const agent of withdrawalAgents) {
|
|
6037
6213
|
try {
|
|
6038
6214
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
6215
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
6039
6216
|
} catch (err) {
|
|
6040
6217
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6041
6218
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
@@ -6133,6 +6310,7 @@ var OwneySDK = class {
|
|
|
6133
6310
|
token,
|
|
6134
6311
|
p.planned.toString()
|
|
6135
6312
|
);
|
|
6313
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
6136
6314
|
} catch (err) {
|
|
6137
6315
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6138
6316
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|