@owney/sdk 0.7.25-beta.7 → 0.7.25-beta.9
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 +317 -138
- package/dist/index.d.cts +161 -143
- package/dist/index.d.ts +161 -143
- package/dist/index.js +317 -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,188 @@ 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
|
+
// Force only during the bounded post-transaction window. Keep unknown
|
|
4114
|
+
// or unreconciled movements for verification, but do not let them turn
|
|
4115
|
+
// routine polling into forced refreshes indefinitely.
|
|
4116
|
+
this.requestPortfolioSnapshot(
|
|
4020
4117
|
state,
|
|
4021
4118
|
chainId,
|
|
4022
|
-
|
|
4119
|
+
context,
|
|
4120
|
+
contextKey,
|
|
4121
|
+
version,
|
|
4122
|
+
generation,
|
|
4123
|
+
this.snapshotMovements.has(contextKey) && (this.reconcileUntil.get(contextKey) ?? 0) > Date.now()
|
|
4023
4124
|
)
|
|
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 } : {}
|
|
4125
|
+
))
|
|
4038
4126
|
};
|
|
4039
4127
|
}
|
|
4128
|
+
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
4129
|
+
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4130
|
+
const generation = this.readGeneration;
|
|
4131
|
+
const assertSession = () => this.assertSession(generation, chainId, context.asset);
|
|
4132
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4133
|
+
const { version, read: read2 } = this.portfolioSnapshot(state, chainId, context, contextKey);
|
|
4134
|
+
try {
|
|
4135
|
+
const snapshot = await read2;
|
|
4136
|
+
assertSession();
|
|
4137
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4138
|
+
const [positions, historic, actions] = await Promise.all([
|
|
4139
|
+
this.cachedRead(
|
|
4140
|
+
`positions:${contextKey}`,
|
|
4141
|
+
this.portfolioCacheMs(contextKey),
|
|
4142
|
+
async () => {
|
|
4143
|
+
const response = await this.walletRequest(
|
|
4144
|
+
state,
|
|
4145
|
+
chainId,
|
|
4146
|
+
this.agentPath(context, "yield-positions")
|
|
4147
|
+
);
|
|
4148
|
+
if (!Array.isArray(response?.yieldPositions)) {
|
|
4149
|
+
throw this.invalidResponse("yield positions");
|
|
4150
|
+
}
|
|
4151
|
+
return response;
|
|
4152
|
+
}
|
|
4153
|
+
),
|
|
4154
|
+
options.historic ? this.cachedRead(
|
|
4155
|
+
`historic:${contextKey}`,
|
|
4156
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4157
|
+
() => this.walletRequest(
|
|
4158
|
+
state,
|
|
4159
|
+
chainId,
|
|
4160
|
+
this.agentPath(context, "wallet/historic-position")
|
|
4161
|
+
)
|
|
4162
|
+
) : Promise.resolve(void 0),
|
|
4163
|
+
options.actions ? this.cachedRead(
|
|
4164
|
+
`actions:${contextKey}`,
|
|
4165
|
+
(this.activityRefreshUntil.get(contextKey) ?? 0) > Date.now() ? YIELDSEEKER_SETTLEMENT_ACTIVITY_CACHE_MS : YIELDSEEKER_ACTIVITY_CACHE_MS,
|
|
4166
|
+
() => this.walletRequest(
|
|
4167
|
+
state,
|
|
4168
|
+
chainId,
|
|
4169
|
+
this.agentPath(context, "actions")
|
|
4170
|
+
)
|
|
4171
|
+
) : Promise.resolve(void 0)
|
|
4172
|
+
]);
|
|
4173
|
+
assertSession();
|
|
4174
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4175
|
+
if (!Array.isArray(positions?.yieldPositions)) {
|
|
4176
|
+
throw this.invalidResponse("yield positions");
|
|
4177
|
+
}
|
|
4178
|
+
return {
|
|
4179
|
+
...context,
|
|
4180
|
+
snapshot: snapshot.agentSnapshot,
|
|
4181
|
+
positions: positions.yieldPositions,
|
|
4182
|
+
...historic?.position ? { historic: historic.position } : {},
|
|
4183
|
+
...actions?.actions ? { actions: actions.actions } : {}
|
|
4184
|
+
};
|
|
4185
|
+
} catch (error) {
|
|
4186
|
+
assertSession();
|
|
4187
|
+
if (this.portfolioVersion(contextKey) !== version) continue;
|
|
4188
|
+
throw error;
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
throw new OwneyError(
|
|
4192
|
+
"AGENT_API_ERROR",
|
|
4193
|
+
"Yieldseeker portfolio changed during the read. Please retry.",
|
|
4194
|
+
{ rpcSource: "agent-api", chainId, asset: context.asset },
|
|
4195
|
+
this.id
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4040
4198
|
async deployAgent(state, chainId, context) {
|
|
4041
4199
|
if (context.wallet.initializedDate != null) return;
|
|
4042
4200
|
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
@@ -4053,42 +4211,52 @@ var YieldseekerAgent = class {
|
|
|
4053
4211
|
}
|
|
4054
4212
|
context.wallet = deployed.agentWallet;
|
|
4055
4213
|
}
|
|
4056
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
4214
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement, settlement) {
|
|
4057
4215
|
const contextKey = this.contextKey(state, chainId, context.asset);
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4216
|
+
this.reconcileUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4217
|
+
this.activityRefreshUntil.set(contextKey, Date.now() + YIELDSEEKER_SETTLEMENT_WINDOW_MS);
|
|
4218
|
+
const epoch = this.movementEpochs.get(contextKey) ?? 0;
|
|
4219
|
+
this.snapshotMovements.set(contextKey, settlement?.epoch === epoch ? settlement : null);
|
|
4220
|
+
this.movementEpochs.set(contextKey, epoch + 1);
|
|
4221
|
+
const version = this.advancePortfolioVersion(contextKey);
|
|
4222
|
+
const generation = this.readGeneration;
|
|
4064
4223
|
try {
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
shouldForceRefresh: true
|
|
4070
|
-
})}`
|
|
4224
|
+
await this.cachedRead(
|
|
4225
|
+
`snapshot:${contextKey}`,
|
|
4226
|
+
YIELDSEEKER_SETTLEMENT_CACHE_MS,
|
|
4227
|
+
() => this.requestPortfolioSnapshot(state, chainId, context, contextKey, version, generation, true)
|
|
4071
4228
|
);
|
|
4072
|
-
if (!response?.agentSnapshot) {
|
|
4073
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
4074
|
-
}
|
|
4075
4229
|
} catch (error) {
|
|
4076
4230
|
console.warn(
|
|
4077
4231
|
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
4078
4232
|
error
|
|
4079
4233
|
);
|
|
4080
|
-
} finally {
|
|
4081
|
-
invalidatePortfolio();
|
|
4082
4234
|
}
|
|
4083
4235
|
}
|
|
4084
4236
|
agentPath(context, suffix) {
|
|
4085
4237
|
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
4086
4238
|
}
|
|
4087
4239
|
async walletRequest(state, chainId, path, options = {}) {
|
|
4240
|
+
const read2 = (options.method ?? "GET") === "GET";
|
|
4241
|
+
const userKey = this.userKey(state, chainId);
|
|
4242
|
+
if (read2) {
|
|
4243
|
+
const failure = this.standardReadFailures.get(userKey);
|
|
4244
|
+
if (failure && failure.retryAt > Date.now()) throw failure.error;
|
|
4245
|
+
if (failure) this.standardReadFailures.delete(userKey);
|
|
4246
|
+
}
|
|
4247
|
+
const generation = this.readGeneration;
|
|
4088
4248
|
try {
|
|
4089
4249
|
return await this.providerRequest(state, chainId, path, options);
|
|
4090
4250
|
} catch (error) {
|
|
4091
|
-
|
|
4251
|
+
const mapped = this.mapApiError(error);
|
|
4252
|
+
const delay = read2 ? rateLimitDelay(mapped) : void 0;
|
|
4253
|
+
if (delay !== void 0 && this.readGeneration === generation) {
|
|
4254
|
+
this.standardReadFailures.set(userKey, {
|
|
4255
|
+
retryAt: Date.now() + Math.max(YIELDSEEKER_READ_FAILURE_COOLDOWN_MS, delay),
|
|
4256
|
+
error: mapped
|
|
4257
|
+
});
|
|
4258
|
+
}
|
|
4259
|
+
throw mapped;
|
|
4092
4260
|
}
|
|
4093
4261
|
}
|
|
4094
4262
|
async providerRequest(state, chainId, path, options = {}) {
|
|
@@ -4385,16 +4553,16 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4385
4553
|
const positionChain = position2.chain.trim().toUpperCase();
|
|
4386
4554
|
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4387
4555
|
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);
|
|
4556
|
+
balance += position2.withdrawableAmountRaw !== void 0 ? BigInt(position2.withdrawableAmountRaw) : (0, import_viem8.parseUnits)(position2.amount, decimals);
|
|
4396
4557
|
}
|
|
4397
4558
|
}
|
|
4559
|
+
const assetTotal = agentBalance?.assetBalances?.find(
|
|
4560
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4561
|
+
);
|
|
4562
|
+
if (assetTotal) {
|
|
4563
|
+
const total = (0, import_viem8.parseUnits)(assetTotal.amount, decimals);
|
|
4564
|
+
if (total < balance) balance = total > 0n ? total : 0n;
|
|
4565
|
+
}
|
|
4398
4566
|
return { agent, balance };
|
|
4399
4567
|
});
|
|
4400
4568
|
}
|
|
@@ -6015,7 +6183,14 @@ var OwneySDK = class {
|
|
|
6015
6183
|
* @returns {AgentWithdrawResult} for a single agent, or {OwneyWithdrawResult} with per-agent results
|
|
6016
6184
|
*/
|
|
6017
6185
|
async withdraw(options) {
|
|
6018
|
-
const { asset, amount, agentId } = options;
|
|
6186
|
+
const { asset, amount, agentId, onAgentResult } = options;
|
|
6187
|
+
const notifyAgentResult = (id, result) => {
|
|
6188
|
+
try {
|
|
6189
|
+
onAgentResult?.(id, result);
|
|
6190
|
+
} catch (error) {
|
|
6191
|
+
console.warn("Withdrawal result observer failed:", error);
|
|
6192
|
+
}
|
|
6193
|
+
};
|
|
6019
6194
|
const state = this.requireState();
|
|
6020
6195
|
const chainId = this.requireChainId();
|
|
6021
6196
|
const token = asset;
|
|
@@ -6031,12 +6206,14 @@ var OwneySDK = class {
|
|
|
6031
6206
|
if (agentId) {
|
|
6032
6207
|
const agent = this.getAgent(agentId);
|
|
6033
6208
|
this.validateAssetSupport(agent, chainId, asset);
|
|
6034
|
-
|
|
6209
|
+
const result = await withFailureReporting(
|
|
6035
6210
|
this.apiKey,
|
|
6036
6211
|
agent.id,
|
|
6037
6212
|
() => agent.withdraw(state, chainId, token, amount),
|
|
6038
6213
|
this.routingApiBaseUrl
|
|
6039
6214
|
);
|
|
6215
|
+
notifyAgentResult(agent.id, result);
|
|
6216
|
+
return result;
|
|
6040
6217
|
}
|
|
6041
6218
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
6042
6219
|
const withdrawalAgents = this.orderAgentsForWithdrawal(eligibleAgents);
|
|
@@ -6046,6 +6223,7 @@ var OwneySDK = class {
|
|
|
6046
6223
|
for (const agent of withdrawalAgents) {
|
|
6047
6224
|
try {
|
|
6048
6225
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
6226
|
+
notifyAgentResult(agent.id, results2[agent.id]);
|
|
6049
6227
|
} catch (err) {
|
|
6050
6228
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6051
6229
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
@@ -6143,6 +6321,7 @@ var OwneySDK = class {
|
|
|
6143
6321
|
token,
|
|
6144
6322
|
p.planned.toString()
|
|
6145
6323
|
);
|
|
6324
|
+
notifyAgentResult(p.agent.id, results[p.agent.id]);
|
|
6146
6325
|
} catch (err) {
|
|
6147
6326
|
if (this.isUserRejectedWithdrawal(err)) throw err;
|
|
6148
6327
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|