@owney/sdk 0.7.21-beta.0 → 0.7.21-beta.1
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/README.md +2 -0
- package/dist/index.cjs +2426 -682
- package/dist/index.d.cts +127 -11
- package/dist/index.d.ts +127 -11
- package/dist/index.js +2436 -680
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
NotConnectedError: () => NotConnectedError,
|
|
27
27
|
OwneyError: () => OwneyError,
|
|
28
28
|
OwneySDK: () => OwneySDK,
|
|
29
|
+
YieldseekerAgent: () => YieldseekerAgent,
|
|
29
30
|
createOwneySIWX: () => createOwneySIWX,
|
|
30
31
|
setOwneyDebug: () => setOwneyDebug
|
|
31
32
|
});
|
|
@@ -310,18 +311,18 @@ function tokenDecimals(symbol, explicit) {
|
|
|
310
311
|
return explicit;
|
|
311
312
|
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
312
313
|
}
|
|
313
|
-
function mapDeposit(
|
|
314
|
+
function mapDeposit(raw2) {
|
|
314
315
|
return {
|
|
315
|
-
txHash:
|
|
316
|
-
smartWallet:
|
|
317
|
-
amount:
|
|
316
|
+
txHash: raw2.txHash,
|
|
317
|
+
smartWallet: raw2.smartWallet,
|
|
318
|
+
amount: raw2.amount
|
|
318
319
|
};
|
|
319
320
|
}
|
|
320
|
-
function mapWithdraw(
|
|
321
|
+
function mapWithdraw(raw2) {
|
|
321
322
|
return {
|
|
322
|
-
txHash:
|
|
323
|
-
type:
|
|
324
|
-
amount:
|
|
323
|
+
txHash: raw2.txHash,
|
|
324
|
+
type: raw2.type,
|
|
325
|
+
amount: raw2.amount
|
|
325
326
|
};
|
|
326
327
|
}
|
|
327
328
|
var CHAIN_ID_TO_NAME = {
|
|
@@ -340,10 +341,10 @@ function resolveChainId(chain) {
|
|
|
340
341
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
341
342
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
342
343
|
}
|
|
343
|
-
function mapPendingAllocations(
|
|
344
|
-
if (!Array.isArray(
|
|
344
|
+
function mapPendingAllocations(raw2) {
|
|
345
|
+
if (!Array.isArray(raw2)) return void 0;
|
|
345
346
|
const pending = [];
|
|
346
|
-
for (const entry of
|
|
347
|
+
for (const entry of raw2) {
|
|
347
348
|
if (typeof entry !== "object" || entry === null) continue;
|
|
348
349
|
const e = entry;
|
|
349
350
|
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
@@ -366,8 +367,8 @@ function mapPendingAllocations(raw) {
|
|
|
366
367
|
}
|
|
367
368
|
return pending.length > 0 ? pending : void 0;
|
|
368
369
|
}
|
|
369
|
-
function mapBalances(
|
|
370
|
-
const portfolio =
|
|
370
|
+
function mapBalances(raw2, _chainId, smartWallet) {
|
|
371
|
+
const portfolio = raw2.portfolio;
|
|
371
372
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
372
373
|
let totalBalance = 0;
|
|
373
374
|
const tokens = [];
|
|
@@ -436,8 +437,8 @@ function sumTokenValues(tokens) {
|
|
|
436
437
|
function sumTokenEarnings(tokens) {
|
|
437
438
|
return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
|
|
438
439
|
}
|
|
439
|
-
function mapEarnings(
|
|
440
|
-
const totalEarningsByChain =
|
|
440
|
+
function mapEarnings(raw2, smartWallet) {
|
|
441
|
+
const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
|
|
441
442
|
const tokens = [];
|
|
442
443
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(
|
|
443
444
|
totalEarningsByChain
|
|
@@ -456,15 +457,15 @@ function mapEarnings(raw, smartWallet) {
|
|
|
456
457
|
return {
|
|
457
458
|
smartWallet,
|
|
458
459
|
lifetimeEarnings: sumTokenEarnings(
|
|
459
|
-
|
|
460
|
+
raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
|
|
460
461
|
),
|
|
461
462
|
tokens
|
|
462
463
|
};
|
|
463
464
|
}
|
|
464
|
-
function mapWeightedApyByChain(
|
|
465
|
-
if (!
|
|
465
|
+
function mapWeightedApyByChain(raw2) {
|
|
466
|
+
if (!raw2) return void 0;
|
|
466
467
|
const out = {};
|
|
467
|
-
for (const [chainKey, tokenApy] of Object.entries(
|
|
468
|
+
for (const [chainKey, tokenApy] of Object.entries(raw2)) {
|
|
468
469
|
const chainId = Number(chainKey);
|
|
469
470
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
470
471
|
const perAsset = {};
|
|
@@ -504,8 +505,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
|
504
505
|
}
|
|
505
506
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
506
507
|
}
|
|
507
|
-
function mapApyHistory(
|
|
508
|
-
const history = Object.entries(
|
|
508
|
+
function mapApyHistory(raw2, chainId, tokenSymbol) {
|
|
509
|
+
const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
|
|
509
510
|
date,
|
|
510
511
|
apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
|
|
511
512
|
// Provider position balances are treated as decimal amounts of the
|
|
@@ -521,9 +522,9 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
|
|
|
521
522
|
} : {}
|
|
522
523
|
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
523
524
|
return {
|
|
524
|
-
walletAddress:
|
|
525
|
-
weightedApyAfterFee:
|
|
526
|
-
apyByChainAndAsset: mapWeightedApyByChain(
|
|
525
|
+
walletAddress: raw2.walletAddress,
|
|
526
|
+
weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
|
|
527
|
+
apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
|
|
527
528
|
history
|
|
528
529
|
};
|
|
529
530
|
}
|
|
@@ -619,23 +620,23 @@ function mapEntries(rawEntries, chainId) {
|
|
|
619
620
|
};
|
|
620
621
|
});
|
|
621
622
|
}
|
|
622
|
-
function mapUserProfile(
|
|
623
|
+
function mapUserProfile(raw2, userAddress) {
|
|
623
624
|
return {
|
|
624
625
|
address: userAddress,
|
|
625
|
-
smartWallet:
|
|
626
|
-
chains:
|
|
627
|
-
strategy:
|
|
628
|
-
hasActiveSessionKey:
|
|
629
|
-
protocols:
|
|
630
|
-
splitting:
|
|
631
|
-
minSplits:
|
|
626
|
+
smartWallet: raw2.smartWallet || "",
|
|
627
|
+
chains: raw2.chains || [],
|
|
628
|
+
strategy: raw2.strategy,
|
|
629
|
+
hasActiveSessionKey: raw2.hasActiveSessionKey || false,
|
|
630
|
+
protocols: raw2.protocols || [],
|
|
631
|
+
splitting: raw2.splitting,
|
|
632
|
+
minSplits: raw2.minSplits
|
|
632
633
|
};
|
|
633
634
|
}
|
|
634
|
-
function mapApyByStrategy(
|
|
635
|
+
function mapApyByStrategy(raw2) {
|
|
635
636
|
const apyPerAsset = {};
|
|
636
637
|
let apySum = 0;
|
|
637
638
|
let apyCount = 0;
|
|
638
|
-
for (const entry of
|
|
639
|
+
for (const entry of raw2.data) {
|
|
639
640
|
const supported = SupportedAssets.find(
|
|
640
641
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
641
642
|
);
|
|
@@ -806,15 +807,15 @@ var readSession = (address, _chainId) => {
|
|
|
806
807
|
if (typeof window === "undefined") return null;
|
|
807
808
|
const key2 = buildKey(address);
|
|
808
809
|
const store = storage();
|
|
809
|
-
let
|
|
810
|
+
let raw2 = null;
|
|
810
811
|
try {
|
|
811
|
-
|
|
812
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
812
813
|
} catch {
|
|
813
|
-
|
|
814
|
+
raw2 = null;
|
|
814
815
|
}
|
|
815
|
-
if (
|
|
816
|
+
if (raw2) {
|
|
816
817
|
try {
|
|
817
|
-
const parsed = JSON.parse(
|
|
818
|
+
const parsed = JSON.parse(raw2);
|
|
818
819
|
if (isFreshSession(parsed)) return parsed;
|
|
819
820
|
} catch {
|
|
820
821
|
}
|
|
@@ -982,8 +983,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
|
|
|
982
983
|
}
|
|
983
984
|
return result;
|
|
984
985
|
}
|
|
985
|
-
function flattenAvailablePools(
|
|
986
|
-
const byChain =
|
|
986
|
+
function flattenAvailablePools(raw2) {
|
|
987
|
+
const byChain = raw2 ?? {};
|
|
987
988
|
const names = [];
|
|
988
989
|
for (const byToken of Object.values(byChain ?? {})) {
|
|
989
990
|
for (const entry of Object.values(byToken ?? {})) {
|
|
@@ -1486,8 +1487,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1486
1487
|
const poolResults = await Promise.all(
|
|
1487
1488
|
universe.map(async (protocol) => {
|
|
1488
1489
|
try {
|
|
1489
|
-
const
|
|
1490
|
-
return [protocol.id, flattenAvailablePools(
|
|
1490
|
+
const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
|
|
1491
|
+
return [protocol.id, flattenAvailablePools(raw2)];
|
|
1491
1492
|
} catch (error) {
|
|
1492
1493
|
console.warn(
|
|
1493
1494
|
`[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
|
|
@@ -1553,14 +1554,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1553
1554
|
async readWalletState(ownerAddress) {
|
|
1554
1555
|
try {
|
|
1555
1556
|
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1556
|
-
const
|
|
1557
|
+
const raw2 = portfolio;
|
|
1557
1558
|
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1558
|
-
predeployed:
|
|
1559
|
-
hasActiveSessionKey:
|
|
1559
|
+
predeployed: raw2?.predeployed,
|
|
1560
|
+
hasActiveSessionKey: raw2?.hasActiveSessionKey
|
|
1560
1561
|
});
|
|
1561
1562
|
return {
|
|
1562
|
-
predeployed:
|
|
1563
|
-
hasActiveSessionKey:
|
|
1563
|
+
predeployed: raw2?.predeployed,
|
|
1564
|
+
hasActiveSessionKey: raw2?.hasActiveSessionKey
|
|
1564
1565
|
};
|
|
1565
1566
|
} catch (error) {
|
|
1566
1567
|
console.warn(
|
|
@@ -1846,14 +1847,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1846
1847
|
return { txHash, smartWallet, amount };
|
|
1847
1848
|
}
|
|
1848
1849
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
1849
|
-
const
|
|
1850
|
+
const raw2 = await this.sdk.depositFunds(
|
|
1850
1851
|
this.getAddress(),
|
|
1851
1852
|
validChainId,
|
|
1852
1853
|
amount,
|
|
1853
1854
|
asset,
|
|
1854
1855
|
"aggressive"
|
|
1855
1856
|
);
|
|
1856
|
-
return mapDeposit(
|
|
1857
|
+
return mapDeposit(raw2);
|
|
1857
1858
|
} catch (error) {
|
|
1858
1859
|
throw error;
|
|
1859
1860
|
}
|
|
@@ -1862,27 +1863,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1862
1863
|
async withdraw(state, chainId, token, amount) {
|
|
1863
1864
|
const validChainId = isValidChainId(chainId);
|
|
1864
1865
|
await this.ensureConnected(state, validChainId);
|
|
1865
|
-
const
|
|
1866
|
+
const raw2 = await this.sdk.withdrawFunds(
|
|
1866
1867
|
this.getAddress(),
|
|
1867
1868
|
validChainId,
|
|
1868
1869
|
amount,
|
|
1869
1870
|
token
|
|
1870
1871
|
);
|
|
1871
|
-
if (!
|
|
1872
|
+
if (!raw2.success) {
|
|
1872
1873
|
throw new OwneyError(
|
|
1873
1874
|
"WITHDRAW_FAILED",
|
|
1874
|
-
|
|
1875
|
-
{ chainId: validChainId, token, amount, response:
|
|
1875
|
+
raw2.message || "Zyfai withdraw failed.",
|
|
1876
|
+
{ chainId: validChainId, token, amount, response: raw2 },
|
|
1876
1877
|
this.id
|
|
1877
1878
|
);
|
|
1878
1879
|
}
|
|
1879
|
-
return mapWithdraw(
|
|
1880
|
+
return mapWithdraw(raw2);
|
|
1880
1881
|
}
|
|
1881
1882
|
// --- IAgent: Portfolio reads ---
|
|
1882
1883
|
async getBalances(state, chainId) {
|
|
1883
1884
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
1884
|
-
const
|
|
1885
|
-
return mapBalances(
|
|
1885
|
+
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
1886
|
+
return mapBalances(raw2, validChainId, smartWallet);
|
|
1886
1887
|
}
|
|
1887
1888
|
earningsKey(state, chainId, smartWallet) {
|
|
1888
1889
|
return JSON.stringify([
|
|
@@ -1895,11 +1896,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1895
1896
|
const existing = this.earningsReads.get(key2);
|
|
1896
1897
|
if (existing) return existing;
|
|
1897
1898
|
const generation = this.earningsGeneration;
|
|
1898
|
-
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((
|
|
1899
|
+
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
|
|
1899
1900
|
if (generation === this.earningsGeneration) {
|
|
1900
|
-
this.earningsSnapshot = { key: key2, raw, at: Date.now() };
|
|
1901
|
+
this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
|
|
1901
1902
|
}
|
|
1902
|
-
return
|
|
1903
|
+
return raw2;
|
|
1903
1904
|
}).finally(() => {
|
|
1904
1905
|
if (this.earningsReads.get(key2) === pending)
|
|
1905
1906
|
this.earningsReads.delete(key2);
|
|
@@ -1909,11 +1910,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1909
1910
|
}
|
|
1910
1911
|
async getEarnings(state, chainId) {
|
|
1911
1912
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1912
|
-
const
|
|
1913
|
+
const raw2 = await this.readEarnings(
|
|
1913
1914
|
this.earningsKey(state, chainId, smartWallet),
|
|
1914
1915
|
smartWallet
|
|
1915
1916
|
);
|
|
1916
|
-
return mapEarnings(
|
|
1917
|
+
return mapEarnings(raw2, smartWallet);
|
|
1917
1918
|
}
|
|
1918
1919
|
async refreshEarnings(state, chainId) {
|
|
1919
1920
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -1940,8 +1941,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1940
1941
|
}
|
|
1941
1942
|
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
1942
1943
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1943
|
-
const
|
|
1944
|
-
return mapApyHistory(
|
|
1944
|
+
const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
1945
|
+
return mapApyHistory(raw2, chainId, tokenSymbol);
|
|
1945
1946
|
}
|
|
1946
1947
|
/**
|
|
1947
1948
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
@@ -1976,7 +1977,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1976
1977
|
const matched = [];
|
|
1977
1978
|
let backendExhausted = false;
|
|
1978
1979
|
for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
|
|
1979
|
-
const
|
|
1980
|
+
const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
|
|
1980
1981
|
limit: backendPageSize,
|
|
1981
1982
|
offset,
|
|
1982
1983
|
fromDate: options?.fromDate,
|
|
@@ -1987,13 +1988,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1987
1988
|
// asset's rows and handing back a page that filters to nothing.
|
|
1988
1989
|
assetType
|
|
1989
1990
|
});
|
|
1990
|
-
|
|
1991
|
+
raw2.data.forEach((entry, idx) => {
|
|
1991
1992
|
if (entry.chainId === validChainId) {
|
|
1992
1993
|
matched.push({ entry, rawIdx: offset + idx });
|
|
1993
1994
|
}
|
|
1994
1995
|
});
|
|
1995
|
-
offset +=
|
|
1996
|
-
if (
|
|
1996
|
+
offset += raw2.data.length;
|
|
1997
|
+
if (raw2.data.length < backendPageSize) {
|
|
1997
1998
|
backendExhausted = true;
|
|
1998
1999
|
break;
|
|
1999
2000
|
}
|
|
@@ -2015,18 +2016,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2015
2016
|
}
|
|
2016
2017
|
async getUserProfile(state, chainId) {
|
|
2017
2018
|
await this.connectAuth(state, chainId);
|
|
2018
|
-
const
|
|
2019
|
+
const raw2 = await this.sdk.getUserDetails();
|
|
2019
2020
|
debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
|
|
2020
2021
|
asset: "USDC (default \u2014 no asset passed)",
|
|
2021
|
-
splitting:
|
|
2022
|
-
minSplits:
|
|
2023
|
-
strategy:
|
|
2024
|
-
chains:
|
|
2025
|
-
protocolCount:
|
|
2026
|
-
hasActiveSessionKey:
|
|
2027
|
-
smartWallet:
|
|
2022
|
+
splitting: raw2.splitting,
|
|
2023
|
+
minSplits: raw2.minSplits,
|
|
2024
|
+
strategy: raw2.strategy,
|
|
2025
|
+
chains: raw2.chains,
|
|
2026
|
+
protocolCount: raw2.protocols?.length,
|
|
2027
|
+
hasActiveSessionKey: raw2.hasActiveSessionKey,
|
|
2028
|
+
smartWallet: raw2.smartWallet
|
|
2028
2029
|
});
|
|
2029
|
-
return mapUserProfile(
|
|
2030
|
+
return mapUserProfile(raw2, this.connectedAddress);
|
|
2030
2031
|
}
|
|
2031
2032
|
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
2032
2033
|
await this.connectAuth(state, chainId);
|
|
@@ -2045,396 +2046,219 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2045
2046
|
}
|
|
2046
2047
|
// --- IAgent: Discovery (no wallet required) ---
|
|
2047
2048
|
async getAgentApy(days, options) {
|
|
2048
|
-
const
|
|
2049
|
+
const raw2 = await this.sdk.getAPYPerStrategy(
|
|
2049
2050
|
false,
|
|
2050
2051
|
DayFilterMapping[days],
|
|
2051
2052
|
"aggressive",
|
|
2052
2053
|
options?.chainId,
|
|
2053
2054
|
options?.tokenSymbol
|
|
2054
2055
|
);
|
|
2055
|
-
return mapApyByStrategy(
|
|
2056
|
+
return mapApyByStrategy(raw2);
|
|
2056
2057
|
}
|
|
2057
2058
|
};
|
|
2058
2059
|
|
|
2059
|
-
// src/
|
|
2060
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2061
|
+
var import_viem6 = require("viem");
|
|
2062
|
+
var import_chains3 = require("viem/chains");
|
|
2063
|
+
|
|
2064
|
+
// src/lib/chain-guard.ts
|
|
2065
|
+
var CHAIN_NAMES = {
|
|
2066
|
+
1: "Ethereum",
|
|
2067
|
+
8453: "Base",
|
|
2068
|
+
42161: "Arbitrum"
|
|
2069
|
+
};
|
|
2070
|
+
function chainName(chainId) {
|
|
2071
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2072
|
+
}
|
|
2073
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2074
|
+
const actual = await pub.getChainId();
|
|
2075
|
+
if (actual === expected) return;
|
|
2076
|
+
try {
|
|
2077
|
+
await wallet.switchChain({ id: expected });
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
throw new OwneyError(
|
|
2080
|
+
"CHAIN_MISMATCH",
|
|
2081
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2082
|
+
{
|
|
2083
|
+
expectedChainId: expected,
|
|
2084
|
+
actualChainId: actual,
|
|
2085
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2086
|
+
}
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
const after = await pub.getChainId();
|
|
2090
|
+
if (after !== expected) {
|
|
2091
|
+
throw new OwneyError(
|
|
2092
|
+
"CHAIN_MISMATCH",
|
|
2093
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2094
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// src/lib/transfer-auth.ts
|
|
2100
|
+
var import_viem2 = require("viem");
|
|
2101
|
+
var ERC20_META_ABI = [
|
|
2102
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2103
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2104
|
+
];
|
|
2105
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
2106
|
+
return {
|
|
2107
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2108
|
+
types: {
|
|
2109
|
+
TransferWithAuthorization: [
|
|
2110
|
+
{ name: "from", type: "address" },
|
|
2111
|
+
{ name: "to", type: "address" },
|
|
2112
|
+
{ name: "value", type: "uint256" },
|
|
2113
|
+
{ name: "validAfter", type: "uint256" },
|
|
2114
|
+
{ name: "validBefore", type: "uint256" },
|
|
2115
|
+
{ name: "nonce", type: "bytes32" }
|
|
2116
|
+
]
|
|
2117
|
+
},
|
|
2118
|
+
primaryType: "TransferWithAuthorization",
|
|
2119
|
+
message: input.message
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
async function readTokenMeta(publicClient, token) {
|
|
2123
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
2124
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2125
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2126
|
+
]);
|
|
2127
|
+
return { tokenName, tokenVersion };
|
|
2128
|
+
}
|
|
2129
|
+
function randomAuthNonce() {
|
|
2130
|
+
const bytes = new Uint8Array(32);
|
|
2131
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2132
|
+
return (0, import_viem2.bytesToHex)(bytes);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// src/lib/sponsor-client.ts
|
|
2060
2136
|
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2061
|
-
async function
|
|
2062
|
-
const
|
|
2137
|
+
async function postPaymasterIntent(input) {
|
|
2138
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2139
|
+
let res;
|
|
2063
2140
|
try {
|
|
2064
|
-
|
|
2065
|
-
method: "
|
|
2141
|
+
res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
|
|
2142
|
+
method: "POST",
|
|
2066
2143
|
headers: {
|
|
2067
|
-
"
|
|
2068
|
-
"x-owney-api-key":
|
|
2069
|
-
|
|
2144
|
+
"content-type": "application/json",
|
|
2145
|
+
"x-owney-api-key": input.apiKey,
|
|
2146
|
+
Authorization: `Signature ${input.yieldseekerSignature}`
|
|
2147
|
+
},
|
|
2148
|
+
body: JSON.stringify(input.body)
|
|
2070
2149
|
});
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
}
|
|
2077
|
-
return null;
|
|
2078
|
-
}
|
|
2079
|
-
const json = await res.json();
|
|
2080
|
-
const policy = json.success ? json.data ?? null : null;
|
|
2081
|
-
debugLog(
|
|
2082
|
-
"owney-sdk",
|
|
2083
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
2084
|
-
policy ?? void 0
|
|
2150
|
+
} catch (networkError) {
|
|
2151
|
+
throw new OwneyError(
|
|
2152
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2153
|
+
`Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2154
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
2085
2155
|
);
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2156
|
+
}
|
|
2157
|
+
const text = await res.text();
|
|
2158
|
+
let parsed = null;
|
|
2159
|
+
try {
|
|
2160
|
+
parsed = JSON.parse(text);
|
|
2161
|
+
} catch {
|
|
2162
|
+
}
|
|
2163
|
+
if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
|
|
2164
|
+
throw new OwneyError(
|
|
2165
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2166
|
+
`Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2167
|
+
{
|
|
2168
|
+
statusCode: res.status,
|
|
2169
|
+
responseBody: text.slice(0, 500),
|
|
2170
|
+
safeToFallback: true
|
|
2171
|
+
}
|
|
2091
2172
|
);
|
|
2092
|
-
return null;
|
|
2093
2173
|
}
|
|
2174
|
+
return parsed.data;
|
|
2094
2175
|
}
|
|
2095
|
-
async function
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2176
|
+
async function postSponsorTransferAuth(input) {
|
|
2177
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2178
|
+
let res;
|
|
2179
|
+
try {
|
|
2180
|
+
res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2181
|
+
method: "POST",
|
|
2182
|
+
headers: {
|
|
2183
|
+
"content-type": "application/json",
|
|
2184
|
+
"x-owney-api-key": input.apiKey,
|
|
2185
|
+
...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
|
|
2186
|
+
},
|
|
2187
|
+
body: JSON.stringify(input.body)
|
|
2188
|
+
});
|
|
2189
|
+
} catch (networkError) {
|
|
2106
2190
|
throw new OwneyError(
|
|
2107
|
-
"
|
|
2108
|
-
`
|
|
2109
|
-
{
|
|
2191
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2192
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2193
|
+
{ cause: String(networkError) }
|
|
2110
2194
|
);
|
|
2111
2195
|
}
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2196
|
+
const text = await res.text();
|
|
2197
|
+
let parsed = null;
|
|
2198
|
+
try {
|
|
2199
|
+
parsed = JSON.parse(text);
|
|
2200
|
+
} catch {
|
|
2201
|
+
}
|
|
2202
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2114
2203
|
throw new OwneyError(
|
|
2115
|
-
"
|
|
2116
|
-
`
|
|
2117
|
-
{
|
|
2204
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2205
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2206
|
+
{
|
|
2207
|
+
statusCode: res.status,
|
|
2208
|
+
responseBody: text.slice(0, 500),
|
|
2209
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2210
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2211
|
+
safeToFallback: res.status === 503
|
|
2212
|
+
}
|
|
2118
2213
|
);
|
|
2119
2214
|
}
|
|
2120
|
-
return
|
|
2215
|
+
return parsed.data;
|
|
2121
2216
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
2217
|
+
async function postSponsorPermit2Transfer(input) {
|
|
2218
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2219
|
+
let res;
|
|
2126
2220
|
try {
|
|
2127
|
-
await fetch(`${
|
|
2221
|
+
res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
|
|
2128
2222
|
method: "POST",
|
|
2129
2223
|
headers: {
|
|
2130
|
-
"
|
|
2131
|
-
"x-owney-api-key": apiKey
|
|
2224
|
+
"content-type": "application/json",
|
|
2225
|
+
"x-owney-api-key": input.apiKey,
|
|
2226
|
+
...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
|
|
2132
2227
|
},
|
|
2133
|
-
body: JSON.stringify(
|
|
2134
|
-
agent_type: agentType,
|
|
2135
|
-
error_code: errorCode,
|
|
2136
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2137
|
-
})
|
|
2228
|
+
body: JSON.stringify(input.body)
|
|
2138
2229
|
});
|
|
2139
|
-
} catch (
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2230
|
+
} catch (networkError) {
|
|
2231
|
+
throw new OwneyError(
|
|
2232
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2233
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2234
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
2143
2235
|
);
|
|
2144
2236
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2237
|
+
const text = await res.text();
|
|
2238
|
+
let parsed = null;
|
|
2147
2239
|
try {
|
|
2148
|
-
|
|
2149
|
-
} catch
|
|
2150
|
-
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
2151
|
-
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
2152
|
-
throw err;
|
|
2240
|
+
parsed = JSON.parse(text);
|
|
2241
|
+
} catch {
|
|
2153
2242
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2243
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2244
|
+
throw new OwneyError(
|
|
2245
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2246
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2247
|
+
{
|
|
2248
|
+
statusCode: res.status,
|
|
2249
|
+
responseBody: text.slice(0, 500),
|
|
2250
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2251
|
+
}
|
|
2164
2252
|
);
|
|
2165
|
-
if (!tokenBalance) return { agent, balance: 0n };
|
|
2166
|
-
return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
function planProportionalShares(balances, requested, totalAvailable) {
|
|
2170
|
-
const plans = balances.map(({ agent, balance }) => ({
|
|
2171
|
-
agent,
|
|
2172
|
-
balance,
|
|
2173
|
-
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
2174
|
-
}));
|
|
2175
|
-
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
2176
|
-
let remainder = requested - assigned;
|
|
2177
|
-
const byHeadroom = [...plans].sort((a, b) => {
|
|
2178
|
-
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
2179
|
-
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2180
|
-
});
|
|
2181
|
-
for (const p of byHeadroom) {
|
|
2182
|
-
if (remainder === 0n) break;
|
|
2183
|
-
const headroom = p.balance - p.planned;
|
|
2184
|
-
if (headroom <= 0n) continue;
|
|
2185
|
-
const take = headroom < remainder ? headroom : remainder;
|
|
2186
|
-
p.planned += take;
|
|
2187
|
-
remainder -= take;
|
|
2188
2253
|
}
|
|
2189
|
-
return
|
|
2190
|
-
}
|
|
2191
|
-
function planDisabledDrain(disabled, requested) {
|
|
2192
|
-
const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
2193
|
-
const plans = [];
|
|
2194
|
-
let remaining = requested;
|
|
2195
|
-
for (const { agent, balance } of sorted) {
|
|
2196
|
-
if (remaining === 0n) {
|
|
2197
|
-
plans.push({ agent, balance, planned: 0n });
|
|
2198
|
-
continue;
|
|
2199
|
-
}
|
|
2200
|
-
const take = balance < remaining ? balance : remaining;
|
|
2201
|
-
plans.push({ agent, balance, planned: take });
|
|
2202
|
-
remaining -= take;
|
|
2203
|
-
}
|
|
2204
|
-
return { plans, remaining };
|
|
2205
|
-
}
|
|
2206
|
-
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
2207
|
-
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
2208
|
-
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
2209
|
-
const totalHeadroom = candidates.reduce(
|
|
2210
|
-
(s, c) => s + (c.balance - c.planned),
|
|
2211
|
-
0n
|
|
2212
|
-
);
|
|
2213
|
-
if (totalHeadroom === 0n) return;
|
|
2214
|
-
let distributed = 0n;
|
|
2215
|
-
for (const c of candidates) {
|
|
2216
|
-
const headroom = c.balance - c.planned;
|
|
2217
|
-
const proportional = headroom * amount / totalHeadroom;
|
|
2218
|
-
const give = proportional > headroom ? headroom : proportional;
|
|
2219
|
-
c.planned += give;
|
|
2220
|
-
distributed += give;
|
|
2221
|
-
}
|
|
2222
|
-
let leftover = amount - distributed;
|
|
2223
|
-
for (const c of candidates) {
|
|
2224
|
-
if (leftover === 0n) break;
|
|
2225
|
-
const headroom = c.balance - c.planned;
|
|
2226
|
-
if (headroom <= 0n) continue;
|
|
2227
|
-
const take = headroom < leftover ? headroom : leftover;
|
|
2228
|
-
c.planned += take;
|
|
2229
|
-
leftover -= take;
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
function sumWithdrawnAmount(results) {
|
|
2233
|
-
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
// src/lib/helpers/account-apy-helper.ts
|
|
2237
|
-
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
2238
|
-
if (!tokenSymbol) {
|
|
2239
|
-
const total = Number(balance.totalBalance);
|
|
2240
|
-
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
2241
|
-
}
|
|
2242
|
-
const normalizedToken = tokenSymbol.toUpperCase();
|
|
2243
|
-
return balance.tokens.reduce((total, token) => {
|
|
2244
|
-
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
2245
|
-
return total;
|
|
2246
|
-
}
|
|
2247
|
-
const amount = Number(token.amount);
|
|
2248
|
-
return Number.isFinite(amount) && amount > 0 ? total + amount : total;
|
|
2249
|
-
}, 0);
|
|
2250
|
-
}
|
|
2251
|
-
function aggregateApyHistory(agentApys) {
|
|
2252
|
-
const byDate = /* @__PURE__ */ new Map();
|
|
2253
|
-
for (const accountApy of Object.values(agentApys)) {
|
|
2254
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2255
|
-
for (const point of accountApy.history ?? []) {
|
|
2256
|
-
if (!point.date || seen.has(point.date) || !Number.isFinite(point.apy)) continue;
|
|
2257
|
-
seen.add(point.date);
|
|
2258
|
-
const points = byDate.get(point.date) ?? [];
|
|
2259
|
-
points.push(point);
|
|
2260
|
-
byDate.set(point.date, points);
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([date, points]) => {
|
|
2264
|
-
if (points.length === 1 && !points[0].historicalBalance) {
|
|
2265
|
-
return [{ date, apy: points[0].apy }];
|
|
2266
|
-
}
|
|
2267
|
-
const unit = points[0].historicalBalance?.unit;
|
|
2268
|
-
if (!unit || points.some(
|
|
2269
|
-
({ historicalBalance: balance }) => !balance || balance.unit !== unit || !Number.isFinite(balance.amount) || balance.amount < 0
|
|
2270
|
-
)) return [];
|
|
2271
|
-
const total = points.reduce((sum, p) => sum + p.historicalBalance.amount, 0);
|
|
2272
|
-
if (total <= 0 || !Number.isFinite(total)) return [];
|
|
2273
|
-
const apy = points.reduce((sum, p) => sum + p.apy * (p.historicalBalance.amount / total), 0);
|
|
2274
|
-
return Number.isFinite(apy) ? [{ date, apy }] : [];
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
2278
|
-
const sums = {};
|
|
2279
|
-
const weights = {};
|
|
2280
|
-
for (const id of Object.keys(agentApys)) {
|
|
2281
|
-
const cells = agentApys[id].apyByChainAndAsset;
|
|
2282
|
-
const balance = agentBalances[id] ?? 0;
|
|
2283
|
-
if (!cells || balance <= 0) continue;
|
|
2284
|
-
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
2285
|
-
if (!perAsset) continue;
|
|
2286
|
-
const chainId = Number(chainKey);
|
|
2287
|
-
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
2288
|
-
const apy = Number(apyValue ?? 0);
|
|
2289
|
-
if (apy === 0) continue;
|
|
2290
|
-
sums[chainId] ??= {};
|
|
2291
|
-
weights[chainId] ??= {};
|
|
2292
|
-
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
2293
|
-
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
|
-
const out = {};
|
|
2298
|
-
for (const chainKey of Object.keys(sums)) {
|
|
2299
|
-
const chainId = Number(chainKey);
|
|
2300
|
-
const perAssetOut = {};
|
|
2301
|
-
for (const asset of Object.keys(sums[chainId])) {
|
|
2302
|
-
const w = weights[chainId][asset];
|
|
2303
|
-
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
2304
|
-
}
|
|
2305
|
-
if (Object.keys(perAssetOut).length > 0) {
|
|
2306
|
-
out[chainId] = perAssetOut;
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
return out;
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
|
-
// src/client.ts
|
|
2313
|
-
var import_viem6 = require("viem");
|
|
2314
|
-
var import_chains2 = require("viem/chains");
|
|
2315
|
-
|
|
2316
|
-
// src/lib/transfer-auth.ts
|
|
2317
|
-
var import_viem3 = require("viem");
|
|
2318
|
-
var ERC20_META_ABI = [
|
|
2319
|
-
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2320
|
-
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2321
|
-
];
|
|
2322
|
-
function buildTransferWithAuthorizationTypedData(input) {
|
|
2323
|
-
return {
|
|
2324
|
-
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2325
|
-
types: {
|
|
2326
|
-
TransferWithAuthorization: [
|
|
2327
|
-
{ name: "from", type: "address" },
|
|
2328
|
-
{ name: "to", type: "address" },
|
|
2329
|
-
{ name: "value", type: "uint256" },
|
|
2330
|
-
{ name: "validAfter", type: "uint256" },
|
|
2331
|
-
{ name: "validBefore", type: "uint256" },
|
|
2332
|
-
{ name: "nonce", type: "bytes32" }
|
|
2333
|
-
]
|
|
2334
|
-
},
|
|
2335
|
-
primaryType: "TransferWithAuthorization",
|
|
2336
|
-
message: input.message
|
|
2337
|
-
};
|
|
2338
|
-
}
|
|
2339
|
-
async function readTokenMeta(publicClient, token) {
|
|
2340
|
-
const [tokenName, tokenVersion] = await Promise.all([
|
|
2341
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2342
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2343
|
-
]);
|
|
2344
|
-
return { tokenName, tokenVersion };
|
|
2345
|
-
}
|
|
2346
|
-
function randomAuthNonce() {
|
|
2347
|
-
const bytes = new Uint8Array(32);
|
|
2348
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2349
|
-
return (0, import_viem3.bytesToHex)(bytes);
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
// src/lib/sponsor-client.ts
|
|
2353
|
-
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2354
|
-
async function postSponsorTransferAuth(input) {
|
|
2355
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2356
|
-
let res;
|
|
2357
|
-
try {
|
|
2358
|
-
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2359
|
-
method: "POST",
|
|
2360
|
-
headers: {
|
|
2361
|
-
"content-type": "application/json",
|
|
2362
|
-
"x-owney-api-key": input.apiKey
|
|
2363
|
-
},
|
|
2364
|
-
body: JSON.stringify(input.body)
|
|
2365
|
-
});
|
|
2366
|
-
} catch (networkError) {
|
|
2367
|
-
throw new OwneyError(
|
|
2368
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2369
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2370
|
-
{ cause: String(networkError) }
|
|
2371
|
-
);
|
|
2372
|
-
}
|
|
2373
|
-
const text = await res.text();
|
|
2374
|
-
let parsed = null;
|
|
2375
|
-
try {
|
|
2376
|
-
parsed = JSON.parse(text);
|
|
2377
|
-
} catch {
|
|
2378
|
-
}
|
|
2379
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2380
|
-
throw new OwneyError(
|
|
2381
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2382
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2383
|
-
{
|
|
2384
|
-
statusCode: res.status,
|
|
2385
|
-
responseBody: text.slice(0, 500),
|
|
2386
|
-
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2387
|
-
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2388
|
-
safeToFallback: res.status === 503
|
|
2389
|
-
}
|
|
2390
|
-
);
|
|
2391
|
-
}
|
|
2392
|
-
return parsed.data;
|
|
2393
|
-
}
|
|
2394
|
-
async function postSponsorPermit2Transfer(input) {
|
|
2395
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2396
|
-
let res;
|
|
2397
|
-
try {
|
|
2398
|
-
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
2399
|
-
method: "POST",
|
|
2400
|
-
headers: {
|
|
2401
|
-
"content-type": "application/json",
|
|
2402
|
-
"x-owney-api-key": input.apiKey
|
|
2403
|
-
},
|
|
2404
|
-
body: JSON.stringify(input.body)
|
|
2405
|
-
});
|
|
2406
|
-
} catch (networkError) {
|
|
2407
|
-
throw new OwneyError(
|
|
2408
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2409
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2410
|
-
{ cause: String(networkError), safeToFallback: false }
|
|
2411
|
-
);
|
|
2412
|
-
}
|
|
2413
|
-
const text = await res.text();
|
|
2414
|
-
let parsed = null;
|
|
2415
|
-
try {
|
|
2416
|
-
parsed = JSON.parse(text);
|
|
2417
|
-
} catch {
|
|
2418
|
-
}
|
|
2419
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2420
|
-
throw new OwneyError(
|
|
2421
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2422
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2423
|
-
{
|
|
2424
|
-
statusCode: res.status,
|
|
2425
|
-
responseBody: text.slice(0, 500),
|
|
2426
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2427
|
-
}
|
|
2428
|
-
);
|
|
2429
|
-
}
|
|
2430
|
-
return parsed.data;
|
|
2254
|
+
return parsed.data;
|
|
2431
2255
|
}
|
|
2432
2256
|
async function getSponsorRelayerAddress(input) {
|
|
2433
|
-
const
|
|
2257
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2434
2258
|
let res;
|
|
2435
2259
|
try {
|
|
2436
2260
|
res = await fetch(
|
|
2437
|
-
`${
|
|
2261
|
+
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2438
2262
|
{
|
|
2439
2263
|
headers: { "x-owney-api-key": input.apiKey }
|
|
2440
2264
|
}
|
|
@@ -2467,7 +2291,7 @@ async function getSponsorRelayerAddress(input) {
|
|
|
2467
2291
|
}
|
|
2468
2292
|
|
|
2469
2293
|
// src/lib/permit2.ts
|
|
2470
|
-
var
|
|
2294
|
+
var import_viem3 = require("viem");
|
|
2471
2295
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2472
2296
|
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2473
2297
|
var ERC20_ALLOWANCE_ABI = [
|
|
@@ -2525,7 +2349,7 @@ function buildPermitTransferFromTypedData(input) {
|
|
|
2525
2349
|
function randomPermit2Nonce() {
|
|
2526
2350
|
const bytes = new Uint8Array(32);
|
|
2527
2351
|
globalThis.crypto.getRandomValues(bytes);
|
|
2528
|
-
return BigInt((0,
|
|
2352
|
+
return BigInt((0, import_viem3.bytesToHex)(bytes));
|
|
2529
2353
|
}
|
|
2530
2354
|
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2531
2355
|
return publicClient.readContract({
|
|
@@ -2535,213 +2359,2032 @@ async function readPermit2Allowance(publicClient, token, owner) {
|
|
|
2535
2359
|
args: [owner, PERMIT2_ADDRESS]
|
|
2536
2360
|
});
|
|
2537
2361
|
}
|
|
2538
|
-
async function readErc20Balance(publicClient, token, owner) {
|
|
2539
|
-
return publicClient.readContract({
|
|
2540
|
-
address: token,
|
|
2541
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2542
|
-
functionName: "balanceOf",
|
|
2543
|
-
args: [owner]
|
|
2544
|
-
});
|
|
2362
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2363
|
+
return publicClient.readContract({
|
|
2364
|
+
address: token,
|
|
2365
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2366
|
+
functionName: "balanceOf",
|
|
2367
|
+
args: [owner]
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// src/lib/sponsored-deposit.ts
|
|
2372
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2373
|
+
var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
|
|
2374
|
+
function provideDepositVerificationContext(callback, context) {
|
|
2375
|
+
callback[verificationSetter]?.(context);
|
|
2376
|
+
}
|
|
2377
|
+
function makeVerificationAwareDepositCallback(implementation) {
|
|
2378
|
+
let nextVerification;
|
|
2379
|
+
const callback = async (smartWallet, chainId, amount) => {
|
|
2380
|
+
const verification = nextVerification;
|
|
2381
|
+
nextVerification = void 0;
|
|
2382
|
+
return implementation(smartWallet, chainId, amount, verification);
|
|
2383
|
+
};
|
|
2384
|
+
Object.defineProperty(callback, verificationSetter, {
|
|
2385
|
+
value: (context) => {
|
|
2386
|
+
nextVerification = context;
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
return callback;
|
|
2390
|
+
}
|
|
2391
|
+
function makeSponsoredDepositCallback(deps) {
|
|
2392
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
2393
|
+
return makeVerificationAwareDepositCallback(
|
|
2394
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
2395
|
+
const cid = chainId;
|
|
2396
|
+
const token = deps.tokenAddressByChain[cid];
|
|
2397
|
+
if (!token) {
|
|
2398
|
+
throw new OwneyError(
|
|
2399
|
+
"CHAIN_UNSUPPORTED",
|
|
2400
|
+
`No sponsored token configured for chain ${chainId}`
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
const pub = deps.getPublicClient(cid);
|
|
2404
|
+
const wallet = deps.getWalletClient(cid);
|
|
2405
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2406
|
+
try {
|
|
2407
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2408
|
+
if (balance < BigInt(amount)) {
|
|
2409
|
+
throw new OwneyError(
|
|
2410
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2411
|
+
"Insufficient balance for this deposit.",
|
|
2412
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
} catch (err) {
|
|
2416
|
+
if (err instanceof OwneyError) throw err;
|
|
2417
|
+
console.warn(
|
|
2418
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2419
|
+
err instanceof Error ? err.message : String(err)
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
2423
|
+
const validAfter = 0n;
|
|
2424
|
+
const validBefore = BigInt(
|
|
2425
|
+
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2426
|
+
);
|
|
2427
|
+
const nonce = randomAuthNonce();
|
|
2428
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
2429
|
+
token,
|
|
2430
|
+
chainId: cid,
|
|
2431
|
+
tokenName,
|
|
2432
|
+
tokenVersion,
|
|
2433
|
+
message: {
|
|
2434
|
+
from: deps.ownerAddress,
|
|
2435
|
+
to: smartWallet,
|
|
2436
|
+
value: BigInt(amount),
|
|
2437
|
+
validAfter,
|
|
2438
|
+
validBefore,
|
|
2439
|
+
nonce
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
const authSignature = await wallet.signTypedData({
|
|
2443
|
+
account: deps.ownerAddress,
|
|
2444
|
+
...typedData
|
|
2445
|
+
});
|
|
2446
|
+
deps.onApproved?.();
|
|
2447
|
+
const result = await post({
|
|
2448
|
+
baseUrl: deps.baseUrl,
|
|
2449
|
+
apiKey: deps.apiKey,
|
|
2450
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
2451
|
+
body: {
|
|
2452
|
+
chainId: cid,
|
|
2453
|
+
token,
|
|
2454
|
+
from: deps.ownerAddress,
|
|
2455
|
+
to: smartWallet,
|
|
2456
|
+
value: amount,
|
|
2457
|
+
validAfter: validAfter.toString(),
|
|
2458
|
+
validBefore: validBefore.toString(),
|
|
2459
|
+
nonce,
|
|
2460
|
+
authSignature,
|
|
2461
|
+
tokenName,
|
|
2462
|
+
tokenVersion,
|
|
2463
|
+
...verification?.agentId === "yieldseeker" ? {
|
|
2464
|
+
yieldseekerUserId: verification.userId,
|
|
2465
|
+
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
2466
|
+
} : {}
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
return result.txHash;
|
|
2470
|
+
}
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2475
|
+
var import_siwe = require("siwe");
|
|
2476
|
+
var import_viem4 = require("viem");
|
|
2477
|
+
var import_chains2 = require("viem/chains");
|
|
2478
|
+
|
|
2479
|
+
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
2480
|
+
var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
|
|
2481
|
+
var INVALIDATED_KEY_PREFIXES = [
|
|
2482
|
+
"owney.yieldseeker.session",
|
|
2483
|
+
"owney.yieldseeker.session.v3",
|
|
2484
|
+
"owney.yieldseeker.session.v4"
|
|
2485
|
+
];
|
|
2486
|
+
var storage2 = () => {
|
|
2487
|
+
if (typeof window === "undefined") return null;
|
|
2488
|
+
try {
|
|
2489
|
+
return window.localStorage;
|
|
2490
|
+
} catch {
|
|
2491
|
+
return null;
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2495
|
+
var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
|
|
2496
|
+
(prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
|
|
2497
|
+
);
|
|
2498
|
+
var clearInvalidatedSessions = (store, address, chainId) => {
|
|
2499
|
+
for (const key2 of invalidatedKeys(address, chainId)) {
|
|
2500
|
+
memorySessions2.delete(key2);
|
|
2501
|
+
try {
|
|
2502
|
+
store?.removeItem(key2);
|
|
2503
|
+
} catch {
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2508
|
+
var isValidSession = (session) => {
|
|
2509
|
+
if (!session?.token) return false;
|
|
2510
|
+
try {
|
|
2511
|
+
const parsed = JSON.parse(atob(session.token));
|
|
2512
|
+
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2513
|
+
} catch {
|
|
2514
|
+
return false;
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
var readYieldseekerSession = (address, chainId) => {
|
|
2518
|
+
if (typeof window === "undefined") return null;
|
|
2519
|
+
const key2 = buildKey2(address, chainId);
|
|
2520
|
+
const store = storage2();
|
|
2521
|
+
clearInvalidatedSessions(store, address, chainId);
|
|
2522
|
+
let raw2 = null;
|
|
2523
|
+
try {
|
|
2524
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
2525
|
+
} catch {
|
|
2526
|
+
raw2 = null;
|
|
2527
|
+
}
|
|
2528
|
+
if (raw2) {
|
|
2529
|
+
try {
|
|
2530
|
+
const parsed = JSON.parse(raw2);
|
|
2531
|
+
if (isValidSession(parsed)) return parsed.token;
|
|
2532
|
+
} catch {
|
|
2533
|
+
}
|
|
2534
|
+
memorySessions2.delete(key2);
|
|
2535
|
+
try {
|
|
2536
|
+
store?.removeItem(key2);
|
|
2537
|
+
} catch {
|
|
2538
|
+
}
|
|
2539
|
+
return null;
|
|
2540
|
+
}
|
|
2541
|
+
const cached = memorySessions2.get(key2);
|
|
2542
|
+
if (isValidSession(cached)) return cached.token;
|
|
2543
|
+
if (cached) memorySessions2.delete(key2);
|
|
2544
|
+
return null;
|
|
2545
|
+
};
|
|
2546
|
+
var writeYieldseekerSession = (address, chainId, token) => {
|
|
2547
|
+
if (typeof window === "undefined") return;
|
|
2548
|
+
const session = { token };
|
|
2549
|
+
if (!isValidSession(session)) return;
|
|
2550
|
+
const key2 = buildKey2(address, chainId);
|
|
2551
|
+
memorySessions2.set(key2, session);
|
|
2552
|
+
const store = storage2();
|
|
2553
|
+
try {
|
|
2554
|
+
store?.setItem(key2, JSON.stringify(session));
|
|
2555
|
+
} catch {
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
var clearYieldseekerSession = (address, chainId) => {
|
|
2559
|
+
const key2 = buildKey2(address, chainId);
|
|
2560
|
+
memorySessions2.delete(key2);
|
|
2561
|
+
const store = storage2();
|
|
2562
|
+
clearInvalidatedSessions(store, address, chainId);
|
|
2563
|
+
try {
|
|
2564
|
+
store?.removeItem(key2);
|
|
2565
|
+
} catch {
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2569
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2570
|
+
function resolveSiweOrigin(override) {
|
|
2571
|
+
const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
|
|
2572
|
+
if (!origin || origin === "null") {
|
|
2573
|
+
throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
|
|
2574
|
+
}
|
|
2575
|
+
const url = new URL(origin);
|
|
2576
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2577
|
+
throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
|
|
2578
|
+
}
|
|
2579
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
2580
|
+
throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
|
|
2581
|
+
}
|
|
2582
|
+
return url;
|
|
2583
|
+
}
|
|
2584
|
+
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2585
|
+
const url = resolveSiweOrigin(dependencies.origin);
|
|
2586
|
+
return new import_siwe.SiweMessage({
|
|
2587
|
+
scheme: url.protocol.slice(0, -1),
|
|
2588
|
+
domain: url.host,
|
|
2589
|
+
address: (0, import_viem4.getAddress)(address),
|
|
2590
|
+
uri: url.origin,
|
|
2591
|
+
version: "1",
|
|
2592
|
+
chainId,
|
|
2593
|
+
nonce: (dependencies.nonce ?? import_siwe.generateNonce)(),
|
|
2594
|
+
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2595
|
+
}).prepareMessage();
|
|
2596
|
+
}
|
|
2597
|
+
function encodeYieldseekerAuthToken(token) {
|
|
2598
|
+
const bytes = new TextEncoder().encode(JSON.stringify(token));
|
|
2599
|
+
let binary = "";
|
|
2600
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2601
|
+
return btoa(binary);
|
|
2602
|
+
}
|
|
2603
|
+
var YieldseekerAuth = class {
|
|
2604
|
+
constructor(dependencies = {}) {
|
|
2605
|
+
this.dependencies = dependencies;
|
|
2606
|
+
}
|
|
2607
|
+
dependencies;
|
|
2608
|
+
tokens = /* @__PURE__ */ new Map();
|
|
2609
|
+
pending = /* @__PURE__ */ new Map();
|
|
2610
|
+
scopes = /* @__PURE__ */ new Map();
|
|
2611
|
+
key(state, chainId) {
|
|
2612
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
2613
|
+
}
|
|
2614
|
+
async getToken(state, chainId) {
|
|
2615
|
+
const key2 = this.key(state, chainId);
|
|
2616
|
+
const scope = { address: state.walletAddress, chainId };
|
|
2617
|
+
this.scopes.set(key2, scope);
|
|
2618
|
+
const cached = this.tokens.get(key2);
|
|
2619
|
+
if (cached) return cached;
|
|
2620
|
+
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2621
|
+
if (persisted && this.matchesOrigin(persisted)) {
|
|
2622
|
+
this.tokens.set(key2, persisted);
|
|
2623
|
+
return persisted;
|
|
2624
|
+
}
|
|
2625
|
+
if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
|
|
2626
|
+
const inFlight = this.pending.get(key2);
|
|
2627
|
+
if (inFlight) return inFlight;
|
|
2628
|
+
const request = this.sign(state, chainId).then((token) => {
|
|
2629
|
+
this.tokens.set(key2, token);
|
|
2630
|
+
writeYieldseekerSession(scope.address, scope.chainId, token);
|
|
2631
|
+
return token;
|
|
2632
|
+
});
|
|
2633
|
+
this.pending.set(key2, request);
|
|
2634
|
+
try {
|
|
2635
|
+
return await request;
|
|
2636
|
+
} finally {
|
|
2637
|
+
this.pending.delete(key2);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
matchesOrigin(token) {
|
|
2641
|
+
try {
|
|
2642
|
+
const message = new import_siwe.SiweMessage(JSON.parse(atob(token)).message);
|
|
2643
|
+
const url = resolveSiweOrigin(this.dependencies.origin);
|
|
2644
|
+
return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
|
|
2645
|
+
} catch {
|
|
2646
|
+
return false;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
clear(state, chainId) {
|
|
2650
|
+
if (!state || chainId === void 0) {
|
|
2651
|
+
for (const scope of this.scopes.values()) {
|
|
2652
|
+
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2653
|
+
}
|
|
2654
|
+
this.tokens.clear();
|
|
2655
|
+
this.pending.clear();
|
|
2656
|
+
this.scopes.clear();
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
const key2 = this.key(state, chainId);
|
|
2660
|
+
this.tokens.delete(key2);
|
|
2661
|
+
this.pending.delete(key2);
|
|
2662
|
+
this.scopes.delete(key2);
|
|
2663
|
+
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2664
|
+
}
|
|
2665
|
+
async sign(state, chainId) {
|
|
2666
|
+
const account = (0, import_viem4.getAddress)(state.walletAddress);
|
|
2667
|
+
const publicClient = (0, import_viem4.createPublicClient)({
|
|
2668
|
+
chain: import_chains2.base,
|
|
2669
|
+
transport: (0, import_viem4.custom)(state.provider)
|
|
2670
|
+
});
|
|
2671
|
+
const walletClient = (0, import_viem4.createWalletClient)({
|
|
2672
|
+
account,
|
|
2673
|
+
chain: import_chains2.base,
|
|
2674
|
+
transport: (0, import_viem4.custom)(state.provider)
|
|
2675
|
+
});
|
|
2676
|
+
await ensureWalletOnChain(
|
|
2677
|
+
publicClient,
|
|
2678
|
+
walletClient,
|
|
2679
|
+
8453
|
|
2680
|
+
);
|
|
2681
|
+
const message = createYieldseekerSiweMessage(
|
|
2682
|
+
account,
|
|
2683
|
+
chainId,
|
|
2684
|
+
this.dependencies
|
|
2685
|
+
);
|
|
2686
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
2687
|
+
return encodeYieldseekerAuthToken({ message, signature });
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
|
|
2691
|
+
// src/agents/yieldseeker/yieldseeker.identity-cache.ts
|
|
2692
|
+
var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
|
|
2693
|
+
var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2694
|
+
var memoryIdentities = /* @__PURE__ */ new Map();
|
|
2695
|
+
var storage3 = () => {
|
|
2696
|
+
if (typeof window === "undefined") return null;
|
|
2697
|
+
try {
|
|
2698
|
+
return window.localStorage;
|
|
2699
|
+
} catch {
|
|
2700
|
+
return null;
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
|
|
2704
|
+
function valid(value, walletAddress, chainId, now) {
|
|
2705
|
+
return Boolean(
|
|
2706
|
+
value && typeof value.userId === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value.userId) && value.walletAddress.toLowerCase() === walletAddress.toLowerCase() && value.chainId === chainId && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
|
|
2710
|
+
if (typeof window === "undefined") return null;
|
|
2711
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2712
|
+
const store = storage3();
|
|
2713
|
+
let parsed = null;
|
|
2714
|
+
try {
|
|
2715
|
+
const raw2 = store?.getItem(key2);
|
|
2716
|
+
parsed = raw2 ? JSON.parse(raw2) : null;
|
|
2717
|
+
} catch {
|
|
2718
|
+
parsed = null;
|
|
2719
|
+
}
|
|
2720
|
+
const candidate = parsed ?? memoryIdentities.get(key2);
|
|
2721
|
+
if (valid(candidate, walletAddress, chainId, now)) {
|
|
2722
|
+
memoryIdentities.set(key2, candidate);
|
|
2723
|
+
return { userId: candidate.userId };
|
|
2724
|
+
}
|
|
2725
|
+
memoryIdentities.delete(key2);
|
|
2726
|
+
try {
|
|
2727
|
+
store?.removeItem(key2);
|
|
2728
|
+
} catch {
|
|
2729
|
+
}
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2732
|
+
function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
|
|
2733
|
+
if (typeof window === "undefined") return;
|
|
2734
|
+
const identity = {
|
|
2735
|
+
userId,
|
|
2736
|
+
walletAddress,
|
|
2737
|
+
chainId,
|
|
2738
|
+
expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
|
|
2739
|
+
};
|
|
2740
|
+
if (!valid(identity, walletAddress, chainId, now)) return;
|
|
2741
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2742
|
+
memoryIdentities.set(key2, identity);
|
|
2743
|
+
try {
|
|
2744
|
+
storage3()?.setItem(key2, JSON.stringify(identity));
|
|
2745
|
+
} catch {
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
function clearYieldseekerIdentity(walletAddress, chainId) {
|
|
2749
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2750
|
+
memoryIdentities.delete(key2);
|
|
2751
|
+
try {
|
|
2752
|
+
storage3()?.removeItem(key2);
|
|
2753
|
+
} catch {
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2758
|
+
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2759
|
+
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2760
|
+
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2761
|
+
}
|
|
2762
|
+
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2763
|
+
var YieldseekerApiError = class extends Error {
|
|
2764
|
+
constructor(status, providerCode, responseFields) {
|
|
2765
|
+
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2766
|
+
this.status = status;
|
|
2767
|
+
this.providerCode = providerCode;
|
|
2768
|
+
this.responseFields = responseFields;
|
|
2769
|
+
this.name = "YieldseekerApiError";
|
|
2770
|
+
}
|
|
2771
|
+
status;
|
|
2772
|
+
providerCode;
|
|
2773
|
+
responseFields;
|
|
2774
|
+
get isAuthenticationError() {
|
|
2775
|
+
return this.status === 401 || this.status === 403;
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
function providerError(body, fallback) {
|
|
2779
|
+
if (!body || typeof body !== "object") return { code: fallback };
|
|
2780
|
+
const record = body;
|
|
2781
|
+
return {
|
|
2782
|
+
code: typeof record.message === "string" ? record.message : fallback,
|
|
2783
|
+
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
var YieldseekerApiClient = class {
|
|
2787
|
+
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
|
|
2788
|
+
this.owneyApiKey = owneyApiKey;
|
|
2789
|
+
this.baseUrl = baseUrl;
|
|
2790
|
+
this.fetchFn = fetchFn;
|
|
2791
|
+
}
|
|
2792
|
+
owneyApiKey;
|
|
2793
|
+
baseUrl;
|
|
2794
|
+
fetchFn;
|
|
2795
|
+
async request(path, options = {}) {
|
|
2796
|
+
const controller = new AbortController();
|
|
2797
|
+
const timer = setTimeout(
|
|
2798
|
+
() => controller.abort(),
|
|
2799
|
+
options.timeoutMs ?? 15e3
|
|
2800
|
+
);
|
|
2801
|
+
try {
|
|
2802
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2803
|
+
method: options.method ?? "GET",
|
|
2804
|
+
headers: {
|
|
2805
|
+
"Content-Type": "application/json",
|
|
2806
|
+
"x-owney-api-key": this.owneyApiKey,
|
|
2807
|
+
...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
|
|
2808
|
+
},
|
|
2809
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2810
|
+
signal: controller.signal
|
|
2811
|
+
});
|
|
2812
|
+
const payload = await response.json().catch(() => null);
|
|
2813
|
+
if (!response.ok) {
|
|
2814
|
+
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2815
|
+
throw new YieldseekerApiError(
|
|
2816
|
+
response.status,
|
|
2817
|
+
error.code,
|
|
2818
|
+
error.fields
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2822
|
+
return payload.data;
|
|
2823
|
+
}
|
|
2824
|
+
return payload;
|
|
2825
|
+
} catch (error) {
|
|
2826
|
+
if (error instanceof YieldseekerApiError) throw error;
|
|
2827
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2828
|
+
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2829
|
+
}
|
|
2830
|
+
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2831
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2832
|
+
});
|
|
2833
|
+
} finally {
|
|
2834
|
+
clearTimeout(timer);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
|
|
2839
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2840
|
+
var import_viem5 = require("viem");
|
|
2841
|
+
|
|
2842
|
+
// src/lib/helpers/snapshot-apy.ts
|
|
2843
|
+
var DAY_MS = 864e5;
|
|
2844
|
+
function snapshotTime(date) {
|
|
2845
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
|
|
2846
|
+
const time = Date.parse(date);
|
|
2847
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
|
|
2848
|
+
}
|
|
2849
|
+
function returnFactor(value) {
|
|
2850
|
+
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
2851
|
+
if (typeof value === "string" && value.trim() === "") return void 0;
|
|
2852
|
+
const factor = Number(value);
|
|
2853
|
+
return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
|
|
2854
|
+
}
|
|
2855
|
+
function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
|
|
2856
|
+
if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
|
|
2857
|
+
return void 0;
|
|
2858
|
+
}
|
|
2859
|
+
const points = snapshots.flatMap((snapshot) => {
|
|
2860
|
+
const time = snapshotTime(snapshot.date);
|
|
2861
|
+
return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
|
|
2862
|
+
}).sort((a, b) => a.time - b.time);
|
|
2863
|
+
const end = points.at(-1);
|
|
2864
|
+
if (!end) return void 0;
|
|
2865
|
+
const cutoff = end.time - lookbackDays * DAY_MS;
|
|
2866
|
+
const start = points.find((point) => point.time >= cutoff);
|
|
2867
|
+
const actualDays = (end.time - start.time) / DAY_MS;
|
|
2868
|
+
if (actualDays <= 0) return void 0;
|
|
2869
|
+
const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
|
|
2870
|
+
const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
|
|
2871
|
+
if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
|
|
2872
|
+
return void 0;
|
|
2873
|
+
}
|
|
2874
|
+
const periodReturn = endFactor / startFactor - 1;
|
|
2875
|
+
const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
|
|
2876
|
+
return Number.isFinite(apy) ? apy : void 0;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
// src/agents/yieldseeker/yieldseeker.types.ts
|
|
2880
|
+
var YIELDSEEKER_ASSET_METADATA = {
|
|
2881
|
+
USDC: {
|
|
2882
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2883
|
+
decimals: 6
|
|
2884
|
+
},
|
|
2885
|
+
WETH: {
|
|
2886
|
+
address: "0x4200000000000000000000000000000000000006",
|
|
2887
|
+
decimals: 18
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
|
|
2891
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2892
|
+
function invalid(endpoint, detail) {
|
|
2893
|
+
throw new OwneyError(
|
|
2894
|
+
"AGENT_INVALID_RESPONSE",
|
|
2895
|
+
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2896
|
+
{ endpoint, detail },
|
|
2897
|
+
"yieldseeker"
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
function raw(value, endpoint) {
|
|
2901
|
+
if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
|
|
2902
|
+
return invalid(endpoint, "expected a base-10 integer string");
|
|
2903
|
+
}
|
|
2904
|
+
return BigInt(value);
|
|
2905
|
+
}
|
|
2906
|
+
function decimal(value, decimals, endpoint) {
|
|
2907
|
+
return (0, import_viem5.formatUnits)(raw(value, endpoint), decimals);
|
|
2908
|
+
}
|
|
2909
|
+
function usd(rawAmount, decimals, price) {
|
|
2910
|
+
return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
|
|
2911
|
+
}
|
|
2912
|
+
function percent(value) {
|
|
2913
|
+
const result = Number(value);
|
|
2914
|
+
return Number.isFinite(result) ? result * 100 : 0;
|
|
2915
|
+
}
|
|
2916
|
+
var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
|
|
2917
|
+
function publicApyAfterYieldseekerFee(value) {
|
|
2918
|
+
const grossPercent = percent(value);
|
|
2919
|
+
if (grossPercent <= 0) return grossPercent;
|
|
2920
|
+
const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
|
|
2921
|
+
return Math.round(netPercent * 1e12) / 1e12;
|
|
2922
|
+
}
|
|
2923
|
+
function riskAdjustedApyForDays(option, days) {
|
|
2924
|
+
if (days === "7D") return option.riskAdjustedApy7dAverage;
|
|
2925
|
+
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
2926
|
+
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
2927
|
+
}
|
|
2928
|
+
function assetAddressValue(record, address) {
|
|
2929
|
+
const entry = Object.entries(record).find(
|
|
2930
|
+
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
2931
|
+
);
|
|
2932
|
+
return entry?.[1] ?? "0";
|
|
2933
|
+
}
|
|
2934
|
+
function position(value, asset, baseAssetDecimals) {
|
|
2935
|
+
const option = value?.yieldOption;
|
|
2936
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
|
|
2937
|
+
return invalid("yield positions", "missing vault metadata");
|
|
2938
|
+
}
|
|
2939
|
+
return {
|
|
2940
|
+
chain: "BASE",
|
|
2941
|
+
protocol: option.provider,
|
|
2942
|
+
protocolId: option.address,
|
|
2943
|
+
pool: option.name,
|
|
2944
|
+
asset,
|
|
2945
|
+
// `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
|
|
2946
|
+
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2947
|
+
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2948
|
+
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2949
|
+
// share quantity separately because withdraw-from-position expects it.
|
|
2950
|
+
amount: decimal(
|
|
2951
|
+
value.assetsBase,
|
|
2952
|
+
baseAssetDecimals,
|
|
2953
|
+
"yield positions"
|
|
2954
|
+
),
|
|
2955
|
+
amountRaw: String(value.assetsRaw),
|
|
2956
|
+
apy: percent(option.riskAdjustedApy),
|
|
2957
|
+
tvl: Number(option.totalDepositsUsd),
|
|
2958
|
+
liquidity: Number(option.withdrawableDepositsUsd)
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
function mapYieldseekerBalances(contexts) {
|
|
2962
|
+
const tokens = [];
|
|
2963
|
+
const assetBalances = [];
|
|
2964
|
+
const positions = [];
|
|
2965
|
+
let totalUsd = 0;
|
|
2966
|
+
for (const context of contexts) {
|
|
2967
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
2968
|
+
assetBalances.push({
|
|
2969
|
+
chain: "BASE",
|
|
2970
|
+
chainId: 8453,
|
|
2971
|
+
asset: context.asset,
|
|
2972
|
+
amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
|
|
2973
|
+
});
|
|
2974
|
+
const idle = assetAddressValue(
|
|
2975
|
+
context.snapshot.tokenBalances,
|
|
2976
|
+
metadata.address
|
|
2977
|
+
);
|
|
2978
|
+
tokens.push({
|
|
2979
|
+
chain: "BASE",
|
|
2980
|
+
chainId: 8453,
|
|
2981
|
+
asset: context.asset,
|
|
2982
|
+
amount: decimal(idle, metadata.decimals, "snapshot")
|
|
2983
|
+
});
|
|
2984
|
+
positions.push(
|
|
2985
|
+
...context.positions.map(
|
|
2986
|
+
(entry) => position(
|
|
2987
|
+
entry,
|
|
2988
|
+
context.asset,
|
|
2989
|
+
context.snapshot.baseAssetDecimals
|
|
2990
|
+
)
|
|
2991
|
+
)
|
|
2992
|
+
);
|
|
2993
|
+
totalUsd += usd(
|
|
2994
|
+
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
2995
|
+
context.snapshot.baseAssetDecimals,
|
|
2996
|
+
context.snapshot.baseAssetPriceUsd
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2999
|
+
return {
|
|
3000
|
+
...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
|
|
3001
|
+
totalBalance: String(totalUsd),
|
|
3002
|
+
totalBalanceAsset: "usdc",
|
|
3003
|
+
assetBalances,
|
|
3004
|
+
tokens,
|
|
3005
|
+
positions
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
function mapYieldseekerEarnings(contexts) {
|
|
3009
|
+
const tokens = [];
|
|
3010
|
+
let lifetimeEarnings = 0;
|
|
3011
|
+
for (const context of contexts) {
|
|
3012
|
+
const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
|
|
3013
|
+
tokens.push({
|
|
3014
|
+
chain: "BASE",
|
|
3015
|
+
chainId: 8453,
|
|
3016
|
+
asset: context.asset,
|
|
3017
|
+
amount: (0, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
|
|
3018
|
+
});
|
|
3019
|
+
lifetimeEarnings += usd(
|
|
3020
|
+
amount,
|
|
3021
|
+
context.snapshot.baseAssetDecimals,
|
|
3022
|
+
context.snapshot.baseAssetPriceUsd
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
return {
|
|
3026
|
+
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3027
|
+
lifetimeEarnings,
|
|
3028
|
+
tokens
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
function apyForDays(context, days, now) {
|
|
3032
|
+
if (days === "7D") return percent(context.snapshot.apy7d);
|
|
3033
|
+
if (days === "30D") return percent(context.snapshot.apy30d);
|
|
3034
|
+
const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
|
|
3035
|
+
const apyPercent = apy === void 0 ? void 0 : apy * 100;
|
|
3036
|
+
return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
|
|
3037
|
+
}
|
|
3038
|
+
function dailyApy(point) {
|
|
3039
|
+
const total = raw(point.totalValueBase, "historic position");
|
|
3040
|
+
const earned = raw(point.dailyYieldBase, "historic position");
|
|
3041
|
+
const principal = total - earned;
|
|
3042
|
+
if (principal <= 0n || earned === 0n) return 0;
|
|
3043
|
+
return Number(earned) / Number(principal) * 365 * 100;
|
|
3044
|
+
}
|
|
3045
|
+
function aggregateHistory(contexts, dayCount, now) {
|
|
3046
|
+
const today = new Date(now).toISOString().slice(0, 10);
|
|
3047
|
+
const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
|
|
3048
|
+
const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
|
|
3049
|
+
const unit = assets.size === 1 ? [...assets][0] : "USD";
|
|
3050
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
3051
|
+
for (const context of contexts) {
|
|
3052
|
+
const points = context.historic?.dailyYieldSnapshots ?? [];
|
|
3053
|
+
for (const point of points) {
|
|
3054
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
|
|
3055
|
+
const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
|
|
3056
|
+
const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
|
|
3057
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
3058
|
+
invalid("historic position", "expected a finite non-negative balance");
|
|
3059
|
+
}
|
|
3060
|
+
const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
|
|
3061
|
+
current.weighted += dailyApy(point) * amount;
|
|
3062
|
+
current.amount += amount;
|
|
3063
|
+
byDate.set(point.date, current);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
3067
|
+
date,
|
|
3068
|
+
apy: value.amount > 0 ? value.weighted / value.amount : 0,
|
|
3069
|
+
historicalBalance: { amount: value.amount, unit }
|
|
3070
|
+
}));
|
|
3071
|
+
}
|
|
3072
|
+
function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
|
|
3073
|
+
let weighted = 0;
|
|
3074
|
+
let totalUsd = 0;
|
|
3075
|
+
const byAsset = {};
|
|
3076
|
+
for (const context of contexts) {
|
|
3077
|
+
const valueUsd = usd(
|
|
3078
|
+
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
3079
|
+
context.snapshot.baseAssetDecimals,
|
|
3080
|
+
context.snapshot.baseAssetPriceUsd
|
|
3081
|
+
);
|
|
3082
|
+
const apy = apyForDays(context, days, now);
|
|
3083
|
+
if (apy === void 0) continue;
|
|
3084
|
+
weighted += apy * valueUsd;
|
|
3085
|
+
totalUsd += valueUsd;
|
|
3086
|
+
byAsset[context.asset] = apy;
|
|
3087
|
+
}
|
|
3088
|
+
const dayCount = Number(days.slice(0, -1));
|
|
3089
|
+
return {
|
|
3090
|
+
walletAddress,
|
|
3091
|
+
...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
|
|
3092
|
+
apyByChainAndAsset: { 8453: byAsset },
|
|
3093
|
+
history: aggregateHistory(contexts, dayCount, now)
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
function actionType(value) {
|
|
3097
|
+
const normalized = value.toLowerCase();
|
|
3098
|
+
if (normalized.includes("deposit")) return "Deposit";
|
|
3099
|
+
if (normalized.includes("withdraw")) return "Withdraw";
|
|
3100
|
+
if (normalized.includes("yield") || normalized.includes("earn"))
|
|
3101
|
+
return "Earned";
|
|
3102
|
+
return "Rebalance";
|
|
3103
|
+
}
|
|
3104
|
+
function transactionHashes(details) {
|
|
3105
|
+
if (!details) return [];
|
|
3106
|
+
const values = [
|
|
3107
|
+
details.transactionHash,
|
|
3108
|
+
details.txHash,
|
|
3109
|
+
...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
|
|
3110
|
+
...Array.isArray(details.txHashes) ? details.txHashes : []
|
|
3111
|
+
];
|
|
3112
|
+
return values.filter(
|
|
3113
|
+
(value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
|
|
3114
|
+
).filter((value, index, all) => all.indexOf(value) === index);
|
|
3115
|
+
}
|
|
3116
|
+
function actionEntry(action) {
|
|
3117
|
+
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3118
|
+
return {
|
|
3119
|
+
agent: "yieldseeker",
|
|
3120
|
+
action: actionType(action.actionType),
|
|
3121
|
+
date: action.createdDate,
|
|
3122
|
+
oldApy: null,
|
|
3123
|
+
newApy: null,
|
|
3124
|
+
transactions: [
|
|
3125
|
+
{
|
|
3126
|
+
txHashes: transactionHashes(action.details),
|
|
3127
|
+
chainId: 8453
|
|
3128
|
+
}
|
|
3129
|
+
],
|
|
3130
|
+
rebalanceLog: []
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses) {
|
|
3134
|
+
const from = movement.fromAddress.toLowerCase();
|
|
3135
|
+
const to = movement.toAddress.toLowerCase();
|
|
3136
|
+
const owner = ownerAddress.toLowerCase();
|
|
3137
|
+
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3138
|
+
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3139
|
+
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3140
|
+
return void 0;
|
|
3141
|
+
}
|
|
3142
|
+
let action;
|
|
3143
|
+
if (from === owner && to === agentWallet) {
|
|
3144
|
+
action = "Top up";
|
|
3145
|
+
} else if (from === agentWallet && to === owner) {
|
|
3146
|
+
action = "Withdraw";
|
|
3147
|
+
} else if (from === agentWallet && vaultAddresses.has(to)) {
|
|
3148
|
+
action = "Deposit";
|
|
3149
|
+
}
|
|
3150
|
+
if (!action) return void 0;
|
|
3151
|
+
return {
|
|
3152
|
+
agent: "yieldseeker",
|
|
3153
|
+
action,
|
|
3154
|
+
date: movement.blockDate,
|
|
3155
|
+
oldApy: null,
|
|
3156
|
+
newApy: null,
|
|
3157
|
+
transactions: [
|
|
3158
|
+
{
|
|
3159
|
+
txHashes: [movement.transactionHash],
|
|
3160
|
+
chainId: agent.chainId,
|
|
3161
|
+
tokenSymbol: asset,
|
|
3162
|
+
amount: decimal(
|
|
3163
|
+
movement.assetAmount,
|
|
3164
|
+
YIELDSEEKER_ASSET_METADATA[asset].decimals,
|
|
3165
|
+
"historic position"
|
|
3166
|
+
)
|
|
3167
|
+
}
|
|
3168
|
+
],
|
|
3169
|
+
rebalanceLog: []
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
function mapYieldseekerHistory(contexts, options) {
|
|
3173
|
+
const entries = contexts.flatMap((context) => {
|
|
3174
|
+
const vaultAddresses = new Set(
|
|
3175
|
+
context.positions.map(
|
|
3176
|
+
(position2) => position2.yieldOption.address.toLowerCase()
|
|
3177
|
+
)
|
|
3178
|
+
);
|
|
3179
|
+
return [
|
|
3180
|
+
...(context.historic?.movements ?? []).map(
|
|
3181
|
+
(movement) => movementEntry(
|
|
3182
|
+
movement,
|
|
3183
|
+
context.wallet,
|
|
3184
|
+
context.agent,
|
|
3185
|
+
context.asset,
|
|
3186
|
+
options.ownerAddress,
|
|
3187
|
+
vaultAddresses
|
|
3188
|
+
)
|
|
3189
|
+
),
|
|
3190
|
+
...(context.actions ?? []).map(actionEntry)
|
|
3191
|
+
].filter((entry) => entry !== void 0);
|
|
3192
|
+
});
|
|
3193
|
+
const filtered = entries.filter(
|
|
3194
|
+
(entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)
|
|
3195
|
+
).filter((entry, index, all) => {
|
|
3196
|
+
const transactionHash = entry.transactions[0]?.txHashes[0];
|
|
3197
|
+
if (!transactionHash) return true;
|
|
3198
|
+
return all.findIndex(
|
|
3199
|
+
(candidate) => candidate.action === entry.action && candidate.transactions[0]?.txHashes[0] === transactionHash
|
|
3200
|
+
) === index;
|
|
3201
|
+
}).sort((left, right) => right.date.localeCompare(left.date));
|
|
3202
|
+
return {
|
|
3203
|
+
data: filtered.slice(0, options.limit),
|
|
3204
|
+
// v1 returns the whole action/movement collection and defines no cursor.
|
|
3205
|
+
// Report a terminal page so callers never loop over the same prefix.
|
|
3206
|
+
hasMore: false
|
|
3207
|
+
};
|
|
3208
|
+
}
|
|
3209
|
+
function mapYieldseekerProfile(address, contexts) {
|
|
3210
|
+
const protocols = /* @__PURE__ */ new Set();
|
|
3211
|
+
for (const context of contexts) {
|
|
3212
|
+
for (const current of context.positions) {
|
|
3213
|
+
if (current.yieldOption?.provider) {
|
|
3214
|
+
protocols.add(String(current.yieldOption.provider));
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
return {
|
|
3219
|
+
address,
|
|
3220
|
+
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3221
|
+
chains: contexts.length > 0 ? [8453] : [],
|
|
3222
|
+
hasActiveSessionKey: contexts.some(
|
|
3223
|
+
(context) => context.wallet.initializedDate != null
|
|
3224
|
+
),
|
|
3225
|
+
protocols: [...protocols]
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
function mapYieldseekerAgentApy(options, days) {
|
|
3229
|
+
const perAsset = {};
|
|
3230
|
+
const all = [];
|
|
3231
|
+
for (const entry of options) {
|
|
3232
|
+
const apys = entry.yieldOptions.map(
|
|
3233
|
+
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
3234
|
+
).filter(Number.isFinite);
|
|
3235
|
+
if (apys.length === 0) continue;
|
|
3236
|
+
const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
|
|
3237
|
+
perAsset[entry.asset] = average;
|
|
3238
|
+
all.push(average);
|
|
3239
|
+
}
|
|
3240
|
+
return {
|
|
3241
|
+
averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
|
|
3242
|
+
detailedApys: { apyPerAsset: { 8453: perAsset } }
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3247
|
+
var OWNEY_AGENT_NAME = "owney";
|
|
3248
|
+
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3249
|
+
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3250
|
+
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3251
|
+
function generateYieldseekerUsername() {
|
|
3252
|
+
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3253
|
+
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
3254
|
+
}
|
|
3255
|
+
function isUsernameConflict(error) {
|
|
3256
|
+
if (!(error instanceof YieldseekerApiError)) return false;
|
|
3257
|
+
const code = error.providerCode.toUpperCase();
|
|
3258
|
+
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
3259
|
+
}
|
|
3260
|
+
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
3261
|
+
{
|
|
3262
|
+
type: "function",
|
|
3263
|
+
name: "withdrawAssetToUser",
|
|
3264
|
+
stateMutability: "nonpayable",
|
|
3265
|
+
inputs: [
|
|
3266
|
+
{ name: "recipient", type: "address" },
|
|
3267
|
+
{ name: "asset", type: "address" },
|
|
3268
|
+
{ name: "amount", type: "uint256" }
|
|
3269
|
+
],
|
|
3270
|
+
outputs: []
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
type: "function",
|
|
3274
|
+
name: "withdrawAllAssetToUser",
|
|
3275
|
+
stateMutability: "nonpayable",
|
|
3276
|
+
inputs: [
|
|
3277
|
+
{ name: "recipient", type: "address" },
|
|
3278
|
+
{ name: "asset", type: "address" }
|
|
3279
|
+
],
|
|
3280
|
+
outputs: []
|
|
3281
|
+
}
|
|
3282
|
+
];
|
|
3283
|
+
function query(params) {
|
|
3284
|
+
const search = new URLSearchParams();
|
|
3285
|
+
for (const [key2, value] of Object.entries(params)) {
|
|
3286
|
+
if (value !== void 0) search.set(key2, String(value));
|
|
3287
|
+
}
|
|
3288
|
+
const encoded = search.toString();
|
|
3289
|
+
return encoded ? `?${encoded}` : "";
|
|
3290
|
+
}
|
|
3291
|
+
var YieldseekerAgent = class {
|
|
3292
|
+
id = "yieldseeker";
|
|
3293
|
+
balanceComposition = "tokens-plus-positions";
|
|
3294
|
+
supportedChainIds = [8453];
|
|
3295
|
+
supportedAssets = [
|
|
3296
|
+
{
|
|
3297
|
+
chainId: 8453,
|
|
3298
|
+
chain: "BASE",
|
|
3299
|
+
assets: [
|
|
3300
|
+
{ symbol: "USDC", minDepositAmount: "10000000" },
|
|
3301
|
+
{ symbol: "WETH", minDepositAmount: "1" }
|
|
3302
|
+
]
|
|
3303
|
+
}
|
|
3304
|
+
];
|
|
3305
|
+
api;
|
|
3306
|
+
auth;
|
|
3307
|
+
transactionExecutor;
|
|
3308
|
+
unwindReceiptWaiter;
|
|
3309
|
+
agentContexts = /* @__PURE__ */ new Map();
|
|
3310
|
+
users = /* @__PURE__ */ new Map();
|
|
3311
|
+
pendingAgents = /* @__PURE__ */ new Map();
|
|
3312
|
+
constructor(owneyApiKey, options = {}) {
|
|
3313
|
+
this.api = new YieldseekerApiClient(
|
|
3314
|
+
owneyApiKey,
|
|
3315
|
+
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
3316
|
+
options.fetchFn
|
|
3317
|
+
);
|
|
3318
|
+
this.auth = new YieldseekerAuth(options.auth);
|
|
3319
|
+
this.transactionExecutor = options.transactionExecutor;
|
|
3320
|
+
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3321
|
+
}
|
|
3322
|
+
async disconnect() {
|
|
3323
|
+
this.auth.clear();
|
|
3324
|
+
for (const key2 of this.users.keys()) {
|
|
3325
|
+
const [walletAddress, chainId] = key2.split(":");
|
|
3326
|
+
clearYieldseekerIdentity(walletAddress, Number(chainId));
|
|
3327
|
+
}
|
|
3328
|
+
this.users.clear();
|
|
3329
|
+
this.agentContexts.clear();
|
|
3330
|
+
this.pendingAgents.clear();
|
|
3331
|
+
}
|
|
3332
|
+
async activateAgent(state, chainId, asset) {
|
|
3333
|
+
this.assertChain(chainId);
|
|
3334
|
+
const targetAsset = asset ?? "USDC";
|
|
3335
|
+
this.assertAsset(targetAsset);
|
|
3336
|
+
await this.ensureAgent(state, chainId, targetAsset);
|
|
3337
|
+
}
|
|
3338
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3339
|
+
this.assertChain(chainId);
|
|
3340
|
+
this.assertAsset(asset);
|
|
3341
|
+
if (BigInt(amount) <= 0n) {
|
|
3342
|
+
throw new OwneyError(
|
|
3343
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
3344
|
+
"Yieldseeker deposits must be greater than zero.",
|
|
3345
|
+
{ amount, minDepositAmount: "1" },
|
|
3346
|
+
this.id
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
const context = await this.ensureAgent(state, chainId, asset);
|
|
3350
|
+
let txHash;
|
|
3351
|
+
try {
|
|
3352
|
+
if (depositCallback) {
|
|
3353
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3354
|
+
agentId: "yieldseeker",
|
|
3355
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3356
|
+
userId: context.user.userId,
|
|
3357
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3358
|
+
});
|
|
3359
|
+
txHash = await depositCallback(
|
|
3360
|
+
context.wallet.walletAddress,
|
|
3361
|
+
chainId,
|
|
3362
|
+
amount
|
|
3363
|
+
);
|
|
3364
|
+
await this.waitForReceipt(state, chainId, txHash);
|
|
3365
|
+
} else {
|
|
3366
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3367
|
+
from: (0, import_viem6.getAddress)(state.walletAddress),
|
|
3368
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3369
|
+
data: (0, import_viem6.encodeFunctionData)({
|
|
3370
|
+
abi: import_viem6.erc20Abi,
|
|
3371
|
+
functionName: "transfer",
|
|
3372
|
+
args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
3373
|
+
}),
|
|
3374
|
+
value: "0",
|
|
3375
|
+
chainId
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
await this.deployAfterFunding(state, chainId, context);
|
|
3379
|
+
} finally {
|
|
3380
|
+
await this.refreshSnapshotAfterMovement(
|
|
3381
|
+
state,
|
|
3382
|
+
chainId,
|
|
3383
|
+
context,
|
|
3384
|
+
"deposit"
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
return {
|
|
3388
|
+
txHash,
|
|
3389
|
+
smartWallet: context.wallet.walletAddress,
|
|
3390
|
+
amount
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3393
|
+
async withdraw(state, chainId, asset, amount) {
|
|
3394
|
+
this.assertChain(chainId);
|
|
3395
|
+
this.assertAsset(asset);
|
|
3396
|
+
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
3397
|
+
throw new OwneyError(
|
|
3398
|
+
"WITHDRAW_FAILED",
|
|
3399
|
+
"Yieldseeker withdrawals must be greater than zero.",
|
|
3400
|
+
{ amount },
|
|
3401
|
+
this.id
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
3405
|
+
if (!context) {
|
|
3406
|
+
throw new OwneyError(
|
|
3407
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3408
|
+
`No Yieldseeker ${asset} agent exists for this wallet.`,
|
|
3409
|
+
{ asset, available: "0" },
|
|
3410
|
+
this.id
|
|
3411
|
+
);
|
|
3412
|
+
}
|
|
3413
|
+
try {
|
|
3414
|
+
const portfolio = await this.loadPortfolioContext(
|
|
3415
|
+
state,
|
|
3416
|
+
chainId,
|
|
3417
|
+
context
|
|
3418
|
+
);
|
|
3419
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3420
|
+
const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
|
|
3421
|
+
([address]) => address.toLowerCase() === metadata.address.toLowerCase()
|
|
3422
|
+
);
|
|
3423
|
+
const idle = BigInt(idleEntry?.[1] ?? "0");
|
|
3424
|
+
const deployed = portfolio.positions.reduce(
|
|
3425
|
+
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3426
|
+
0n
|
|
3427
|
+
);
|
|
3428
|
+
const totalAvailable = idle + deployed;
|
|
3429
|
+
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3430
|
+
if (requested > totalAvailable) {
|
|
3431
|
+
throw new OwneyError(
|
|
3432
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3433
|
+
`Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
|
|
3434
|
+
{
|
|
3435
|
+
asset,
|
|
3436
|
+
requested: requested.toString(),
|
|
3437
|
+
available: totalAvailable.toString()
|
|
3438
|
+
},
|
|
3439
|
+
this.id
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
let remaining = requested > idle ? requested - idle : 0n;
|
|
3443
|
+
for (const position2 of portfolio.positions) {
|
|
3444
|
+
if (remaining === 0n) break;
|
|
3445
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
3446
|
+
if (available <= 0n) continue;
|
|
3447
|
+
const assetsRaw = available < remaining ? available : remaining;
|
|
3448
|
+
const response = await this.walletRequest(
|
|
3449
|
+
state,
|
|
3450
|
+
chainId,
|
|
3451
|
+
this.agentPath(context, "withdraw-from-position"),
|
|
3452
|
+
{
|
|
3453
|
+
method: "POST",
|
|
3454
|
+
body: {
|
|
3455
|
+
chainId,
|
|
3456
|
+
vaultAddress: position2.yieldOption.address,
|
|
3457
|
+
assetsRaw: assetsRaw.toString()
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
);
|
|
3461
|
+
if (!this.isTransactionHash(response?.transactionHash)) {
|
|
3462
|
+
throw this.invalidResponse("position withdrawal");
|
|
3463
|
+
}
|
|
3464
|
+
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3465
|
+
remaining -= assetsRaw;
|
|
3466
|
+
}
|
|
3467
|
+
if (remaining > 0n) {
|
|
3468
|
+
throw this.invalidResponse("yield positions", {
|
|
3469
|
+
reason: "Withdrawable positions could not cover the request.",
|
|
3470
|
+
remaining: remaining.toString()
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
const account = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3474
|
+
const txHash = await this.submitTransaction(state, chainId, {
|
|
3475
|
+
from: account,
|
|
3476
|
+
to: (0, import_viem6.getAddress)(context.wallet.walletAddress),
|
|
3477
|
+
data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
|
|
3478
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3479
|
+
functionName: "withdrawAllAssetToUser",
|
|
3480
|
+
args: [account, metadata.address]
|
|
3481
|
+
}) : (0, import_viem6.encodeFunctionData)({
|
|
3482
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3483
|
+
functionName: "withdrawAssetToUser",
|
|
3484
|
+
args: [account, metadata.address, requested]
|
|
3485
|
+
}),
|
|
3486
|
+
value: "0",
|
|
3487
|
+
chainId
|
|
3488
|
+
});
|
|
3489
|
+
return {
|
|
3490
|
+
txHash,
|
|
3491
|
+
type: amount === void 0 ? "full" : "partial",
|
|
3492
|
+
amount: requested.toString()
|
|
3493
|
+
};
|
|
3494
|
+
} finally {
|
|
3495
|
+
await this.refreshSnapshotAfterMovement(
|
|
3496
|
+
state,
|
|
3497
|
+
chainId,
|
|
3498
|
+
context,
|
|
3499
|
+
"withdrawal"
|
|
3500
|
+
);
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
async getBalances(state, chainId) {
|
|
3504
|
+
this.assertChain(chainId);
|
|
3505
|
+
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
|
|
3506
|
+
}
|
|
3507
|
+
async getEarnings(state, chainId) {
|
|
3508
|
+
this.assertChain(chainId);
|
|
3509
|
+
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
3510
|
+
}
|
|
3511
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
3512
|
+
this.assertChain(chainId);
|
|
3513
|
+
const asset = tokenSymbol?.toUpperCase();
|
|
3514
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
3515
|
+
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3516
|
+
...asset ? { asset } : {},
|
|
3517
|
+
historic: true
|
|
3518
|
+
});
|
|
3519
|
+
return mapYieldseekerApy(state.walletAddress, contexts, days);
|
|
3520
|
+
}
|
|
3521
|
+
async getHistory(state, chainId, options) {
|
|
3522
|
+
this.assertChain(chainId);
|
|
3523
|
+
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3524
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
3525
|
+
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3526
|
+
...asset ? { asset } : {},
|
|
3527
|
+
historic: true,
|
|
3528
|
+
actions: true
|
|
3529
|
+
});
|
|
3530
|
+
return mapYieldseekerHistory(contexts, {
|
|
3531
|
+
limit: options?.limit ?? 10,
|
|
3532
|
+
ownerAddress: state.walletAddress,
|
|
3533
|
+
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
3534
|
+
...options?.toDate ? { toDate: options.toDate } : {}
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
async getUserProfile(state, chainId) {
|
|
3538
|
+
this.assertChain(chainId);
|
|
3539
|
+
return mapYieldseekerProfile(
|
|
3540
|
+
state.walletAddress,
|
|
3541
|
+
await this.loadPortfolio(state, chainId, {})
|
|
3542
|
+
);
|
|
3543
|
+
}
|
|
3544
|
+
async getAgentApy(days, options) {
|
|
3545
|
+
this.assertOptionalChain(options?.chainId);
|
|
3546
|
+
const requested = options?.tokenSymbol?.toUpperCase();
|
|
3547
|
+
if (requested !== void 0) this.assertAsset(requested);
|
|
3548
|
+
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
3549
|
+
const values = await Promise.all(
|
|
3550
|
+
assets.map(async (asset) => {
|
|
3551
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3552
|
+
const response = await this.api.request(
|
|
3553
|
+
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3554
|
+
);
|
|
3555
|
+
if (!Array.isArray(response?.yieldOptions)) {
|
|
3556
|
+
throw this.invalidResponse("yield options");
|
|
3557
|
+
}
|
|
3558
|
+
return { asset, yieldOptions: response.yieldOptions };
|
|
3559
|
+
})
|
|
3560
|
+
);
|
|
3561
|
+
return mapYieldseekerAgentApy(values, days);
|
|
3562
|
+
}
|
|
3563
|
+
userKey(state, chainId) {
|
|
3564
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
3565
|
+
}
|
|
3566
|
+
contextKey(state, chainId, asset) {
|
|
3567
|
+
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3568
|
+
}
|
|
3569
|
+
async resolveUser(state, chainId) {
|
|
3570
|
+
const key2 = this.userKey(state, chainId);
|
|
3571
|
+
const inMemory = this.users.get(key2);
|
|
3572
|
+
if (inMemory) return inMemory;
|
|
3573
|
+
const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
|
|
3574
|
+
if (persisted) {
|
|
3575
|
+
this.users.set(key2, persisted);
|
|
3576
|
+
return persisted;
|
|
3577
|
+
}
|
|
3578
|
+
const walletAddress = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3579
|
+
let user = null;
|
|
3580
|
+
try {
|
|
3581
|
+
const login = await this.providerRequest(
|
|
3582
|
+
state,
|
|
3583
|
+
chainId,
|
|
3584
|
+
"/users/login-with-wallet",
|
|
3585
|
+
{ method: "POST", body: { walletAddress } }
|
|
3586
|
+
);
|
|
3587
|
+
user = login?.user ?? null;
|
|
3588
|
+
if (!user) {
|
|
3589
|
+
throw this.invalidResponse("wallet login", {
|
|
3590
|
+
reason: "A successful login returned no user."
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3593
|
+
} catch (error) {
|
|
3594
|
+
if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
|
|
3595
|
+
if (error instanceof OwneyError) throw error;
|
|
3596
|
+
throw this.mapApiError(error);
|
|
3597
|
+
}
|
|
3598
|
+
let created;
|
|
3599
|
+
for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
|
|
3600
|
+
try {
|
|
3601
|
+
created = await this.providerRequest(
|
|
3602
|
+
state,
|
|
3603
|
+
chainId,
|
|
3604
|
+
"/users",
|
|
3605
|
+
{
|
|
3606
|
+
method: "POST",
|
|
3607
|
+
body: {
|
|
3608
|
+
walletAddress,
|
|
3609
|
+
username: generateYieldseekerUsername()
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
);
|
|
3613
|
+
break;
|
|
3614
|
+
} catch (createError) {
|
|
3615
|
+
const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
|
|
3616
|
+
if (canRetry) continue;
|
|
3617
|
+
throw this.mapApiError(createError);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
user = created?.user ?? null;
|
|
3621
|
+
}
|
|
3622
|
+
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3623
|
+
throw this.invalidResponse("wallet identity");
|
|
3624
|
+
}
|
|
3625
|
+
const resolved = { userId: user.userId };
|
|
3626
|
+
this.users.set(key2, resolved);
|
|
3627
|
+
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
3628
|
+
return resolved;
|
|
3629
|
+
}
|
|
3630
|
+
forgetUser(state, chainId) {
|
|
3631
|
+
this.users.delete(this.userKey(state, chainId));
|
|
3632
|
+
clearYieldseekerIdentity(state.walletAddress, chainId);
|
|
3633
|
+
}
|
|
3634
|
+
async ensureAgent(state, chainId, asset) {
|
|
3635
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3636
|
+
const cached = this.agentContexts.get(key2);
|
|
3637
|
+
if (cached) return cached;
|
|
3638
|
+
const pending = this.pendingAgents.get(key2);
|
|
3639
|
+
if (pending) return pending;
|
|
3640
|
+
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3641
|
+
(context) => {
|
|
3642
|
+
if (!context) throw this.invalidResponse("agent creation");
|
|
3643
|
+
this.agentContexts.set(key2, context);
|
|
3644
|
+
return context;
|
|
3645
|
+
}
|
|
3646
|
+
);
|
|
3647
|
+
this.pendingAgents.set(key2, request);
|
|
3648
|
+
try {
|
|
3649
|
+
return await request;
|
|
3650
|
+
} finally {
|
|
3651
|
+
this.pendingAgents.delete(key2);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
async findAgent(state, chainId, asset) {
|
|
3655
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3656
|
+
const cached = this.agentContexts.get(key2);
|
|
3657
|
+
if (cached) return cached;
|
|
3658
|
+
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3659
|
+
if (context) this.agentContexts.set(key2, context);
|
|
3660
|
+
return context;
|
|
3661
|
+
}
|
|
3662
|
+
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3663
|
+
const user = await this.resolveUser(state, chainId);
|
|
3664
|
+
const response = await this.walletRequest(
|
|
3665
|
+
state,
|
|
3666
|
+
chainId,
|
|
3667
|
+
`/users/${user.userId}/agents`
|
|
3668
|
+
);
|
|
3669
|
+
if (!Array.isArray(response?.agents)) {
|
|
3670
|
+
throw this.invalidResponse("agent list");
|
|
3671
|
+
}
|
|
3672
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3673
|
+
let agent = response.agents.find(
|
|
3674
|
+
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3675
|
+
);
|
|
3676
|
+
if (!agent && createIfMissing) {
|
|
3677
|
+
const created = await this.walletRequest(
|
|
3678
|
+
state,
|
|
3679
|
+
chainId,
|
|
3680
|
+
`/users/${user.userId}/agents`,
|
|
3681
|
+
{
|
|
3682
|
+
method: "POST",
|
|
3683
|
+
body: {
|
|
3684
|
+
name: OWNEY_AGENT_NAME,
|
|
3685
|
+
emoji: "\u{1F989}",
|
|
3686
|
+
chainId,
|
|
3687
|
+
assetAddress: metadata.address,
|
|
3688
|
+
type: "vault",
|
|
3689
|
+
rulePreset: null
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
);
|
|
3693
|
+
agent = created?.agent;
|
|
3694
|
+
}
|
|
3695
|
+
if (!agent) return null;
|
|
3696
|
+
this.assertAgent(agent);
|
|
3697
|
+
const walletResponse = await this.walletRequest(
|
|
3698
|
+
state,
|
|
3699
|
+
chainId,
|
|
3700
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3701
|
+
);
|
|
3702
|
+
if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
3703
|
+
throw this.invalidResponse("agent wallet");
|
|
3704
|
+
}
|
|
3705
|
+
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
3706
|
+
}
|
|
3707
|
+
async loadPortfolio(state, chainId, options) {
|
|
3708
|
+
const user = await this.resolveUser(state, chainId);
|
|
3709
|
+
const response = await this.walletRequest(
|
|
3710
|
+
state,
|
|
3711
|
+
chainId,
|
|
3712
|
+
`/users/${user.userId}/agents`
|
|
3713
|
+
);
|
|
3714
|
+
if (!Array.isArray(response?.agents)) {
|
|
3715
|
+
throw this.invalidResponse("agent list");
|
|
3716
|
+
}
|
|
3717
|
+
const contexts = [];
|
|
3718
|
+
for (const agent of response.agents) {
|
|
3719
|
+
const asset = this.assetForAgent(agent);
|
|
3720
|
+
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
|
|
3721
|
+
continue;
|
|
3722
|
+
}
|
|
3723
|
+
this.assertAgent(agent);
|
|
3724
|
+
const walletResponse = await this.walletRequest(
|
|
3725
|
+
state,
|
|
3726
|
+
chainId,
|
|
3727
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3728
|
+
);
|
|
3729
|
+
if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
3730
|
+
throw this.invalidResponse("agent wallet");
|
|
3731
|
+
}
|
|
3732
|
+
const context = {
|
|
3733
|
+
user,
|
|
3734
|
+
agent,
|
|
3735
|
+
wallet: walletResponse.agentWallet,
|
|
3736
|
+
asset
|
|
3737
|
+
};
|
|
3738
|
+
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3739
|
+
contexts.push(context);
|
|
3740
|
+
}
|
|
3741
|
+
return Promise.all(
|
|
3742
|
+
contexts.map(
|
|
3743
|
+
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3744
|
+
)
|
|
3745
|
+
);
|
|
3746
|
+
}
|
|
3747
|
+
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3748
|
+
const [snapshot, positions, historic, actions] = await Promise.all([
|
|
3749
|
+
this.walletRequest(
|
|
3750
|
+
state,
|
|
3751
|
+
chainId,
|
|
3752
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3753
|
+
shouldOnlyUseRecentValue: true,
|
|
3754
|
+
shouldAllowStaleOnError: true
|
|
3755
|
+
})}`
|
|
3756
|
+
),
|
|
3757
|
+
this.walletRequest(
|
|
3758
|
+
state,
|
|
3759
|
+
chainId,
|
|
3760
|
+
this.agentPath(context, "yield-positions")
|
|
3761
|
+
),
|
|
3762
|
+
options.historic ? this.walletRequest(
|
|
3763
|
+
state,
|
|
3764
|
+
chainId,
|
|
3765
|
+
this.agentPath(context, "wallet/historic-position")
|
|
3766
|
+
) : Promise.resolve(void 0),
|
|
3767
|
+
options.actions ? this.walletRequest(
|
|
3768
|
+
state,
|
|
3769
|
+
chainId,
|
|
3770
|
+
this.agentPath(context, "actions")
|
|
3771
|
+
) : Promise.resolve(void 0)
|
|
3772
|
+
]);
|
|
3773
|
+
if (!snapshot?.agentSnapshot) {
|
|
3774
|
+
throw this.invalidResponse("agent snapshot");
|
|
3775
|
+
}
|
|
3776
|
+
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3777
|
+
throw this.invalidResponse("yield positions");
|
|
3778
|
+
}
|
|
3779
|
+
return {
|
|
3780
|
+
...context,
|
|
3781
|
+
snapshot: snapshot.agentSnapshot,
|
|
3782
|
+
positions: positions.yieldPositions,
|
|
3783
|
+
...historic?.position ? { historic: historic.position } : {},
|
|
3784
|
+
...actions?.actions ? { actions: actions.actions } : {}
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
async deployAfterFunding(state, chainId, context) {
|
|
3788
|
+
if (context.wallet.initializedDate != null) return;
|
|
3789
|
+
try {
|
|
3790
|
+
const deployed = await this.walletRequest(
|
|
3791
|
+
state,
|
|
3792
|
+
chainId,
|
|
3793
|
+
this.agentPath(context, "deploy"),
|
|
3794
|
+
{ method: "POST", body: {} }
|
|
3795
|
+
);
|
|
3796
|
+
if (deployed?.agentWallet) {
|
|
3797
|
+
context.wallet = deployed.agentWallet;
|
|
3798
|
+
}
|
|
3799
|
+
} catch (error) {
|
|
3800
|
+
console.warn(
|
|
3801
|
+
"[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
|
|
3802
|
+
error
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
3807
|
+
try {
|
|
3808
|
+
const response = await this.walletRequest(
|
|
3809
|
+
state,
|
|
3810
|
+
chainId,
|
|
3811
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3812
|
+
shouldForceRefresh: true
|
|
3813
|
+
})}`
|
|
3814
|
+
);
|
|
3815
|
+
if (!response?.agentSnapshot) {
|
|
3816
|
+
throw this.invalidResponse("agent snapshot refresh");
|
|
3817
|
+
}
|
|
3818
|
+
} catch (error) {
|
|
3819
|
+
console.warn(
|
|
3820
|
+
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
3821
|
+
error
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
agentPath(context, suffix) {
|
|
3826
|
+
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3827
|
+
}
|
|
3828
|
+
async walletRequest(state, chainId, path, options = {}) {
|
|
3829
|
+
try {
|
|
3830
|
+
return await this.providerRequest(state, chainId, path, options);
|
|
3831
|
+
} catch (error) {
|
|
3832
|
+
throw this.mapApiError(error);
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
async providerRequest(state, chainId, path, options = {}) {
|
|
3836
|
+
this.assertChain(chainId);
|
|
3837
|
+
const request = (signature2) => this.api.request(path, {
|
|
3838
|
+
...options,
|
|
3839
|
+
signature: signature2
|
|
3840
|
+
});
|
|
3841
|
+
let signature = await this.auth.getToken(state, chainId);
|
|
3842
|
+
try {
|
|
3843
|
+
return await request(signature);
|
|
3844
|
+
} catch (error) {
|
|
3845
|
+
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
3846
|
+
if (error.providerCode === "NO_USER") throw error;
|
|
3847
|
+
if (!error.isAuthenticationError) throw error;
|
|
3848
|
+
this.auth.clear(state, chainId);
|
|
3849
|
+
signature = await this.auth.getToken(state, chainId);
|
|
3850
|
+
try {
|
|
3851
|
+
return await request(signature);
|
|
3852
|
+
} catch (retryError) {
|
|
3853
|
+
if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
|
|
3854
|
+
this.forgetUser(state, chainId);
|
|
3855
|
+
}
|
|
3856
|
+
throw retryError;
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
mapApiError(error) {
|
|
3861
|
+
if (!(error instanceof YieldseekerApiError)) {
|
|
3862
|
+
return new OwneyError(
|
|
3863
|
+
"AGENT_API_ERROR",
|
|
3864
|
+
"Yieldseeker request failed.",
|
|
3865
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
3866
|
+
this.id
|
|
3867
|
+
);
|
|
3868
|
+
}
|
|
3869
|
+
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
3870
|
+
return new OwneyError(
|
|
3871
|
+
code,
|
|
3872
|
+
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3873
|
+
{
|
|
3874
|
+
statusCode: error.status,
|
|
3875
|
+
providerCode: error.providerCode,
|
|
3876
|
+
...error.responseFields ? { fields: error.responseFields } : {}
|
|
3877
|
+
},
|
|
3878
|
+
this.id
|
|
3879
|
+
);
|
|
3880
|
+
}
|
|
3881
|
+
async submitTransaction(state, chainId, transaction) {
|
|
3882
|
+
if (this.transactionExecutor) {
|
|
3883
|
+
return this.transactionExecutor(state, chainId, transaction);
|
|
3884
|
+
}
|
|
3885
|
+
this.assertTransaction(transaction, state, chainId);
|
|
3886
|
+
const account = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3887
|
+
const walletClient = (0, import_viem6.createWalletClient)({
|
|
3888
|
+
account,
|
|
3889
|
+
chain: import_chains3.base,
|
|
3890
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
3891
|
+
});
|
|
3892
|
+
const publicClient = (0, import_viem6.createPublicClient)({
|
|
3893
|
+
chain: import_chains3.base,
|
|
3894
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
3895
|
+
});
|
|
3896
|
+
await ensureWalletOnChain(
|
|
3897
|
+
publicClient,
|
|
3898
|
+
walletClient,
|
|
3899
|
+
8453
|
|
3900
|
+
);
|
|
3901
|
+
const hash = await walletClient.sendTransaction({
|
|
3902
|
+
account,
|
|
3903
|
+
chain: import_chains3.base,
|
|
3904
|
+
to: (0, import_viem6.getAddress)(transaction.to),
|
|
3905
|
+
data: transaction.data,
|
|
3906
|
+
value: BigInt(transaction.value)
|
|
3907
|
+
});
|
|
3908
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3909
|
+
hash,
|
|
3910
|
+
confirmations: 1
|
|
3911
|
+
});
|
|
3912
|
+
if (receipt.status !== "success") {
|
|
3913
|
+
throw new OwneyError(
|
|
3914
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3915
|
+
`Yieldseeker transaction reverted (${hash}).`,
|
|
3916
|
+
{ transactionHash: hash },
|
|
3917
|
+
this.id
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
return hash;
|
|
3921
|
+
}
|
|
3922
|
+
async waitForReceipt(state, chainId, transactionHash) {
|
|
3923
|
+
if (this.unwindReceiptWaiter) {
|
|
3924
|
+
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
const publicClient = (0, import_viem6.createPublicClient)({
|
|
3928
|
+
chain: import_chains3.base,
|
|
3929
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
3930
|
+
});
|
|
3931
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3932
|
+
hash: transactionHash,
|
|
3933
|
+
confirmations: 1
|
|
3934
|
+
});
|
|
3935
|
+
if (receipt.status !== "success") {
|
|
3936
|
+
throw new OwneyError(
|
|
3937
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3938
|
+
`Yieldseeker transaction reverted (${transactionHash}).`,
|
|
3939
|
+
{ transactionHash },
|
|
3940
|
+
this.id
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
assertTransaction(transaction, state, chainId) {
|
|
3945
|
+
if (!transaction || typeof transaction.from !== "string" || !(0, import_viem6.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem6.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem6.getAddress)(transaction.from) !== (0, import_viem6.getAddress)(state.walletAddress)) {
|
|
3946
|
+
throw this.invalidResponse("transaction");
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
assertAgent(agent) {
|
|
3950
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
|
|
3951
|
+
throw this.invalidResponse("agent");
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
isOwneyAgent(agent) {
|
|
3955
|
+
return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
|
|
3956
|
+
}
|
|
3957
|
+
assetForAgent(agent) {
|
|
3958
|
+
for (const asset of ["USDC", "WETH"]) {
|
|
3959
|
+
if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
|
|
3960
|
+
return asset;
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
return null;
|
|
3964
|
+
}
|
|
3965
|
+
isTransactionHash(value) {
|
|
3966
|
+
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3967
|
+
}
|
|
3968
|
+
assertChain(chainId) {
|
|
3969
|
+
if (chainId !== 8453) {
|
|
3970
|
+
throw new OwneyError(
|
|
3971
|
+
"CHAIN_UNSUPPORTED",
|
|
3972
|
+
`Yieldseeker does not support chain ${chainId}.`,
|
|
3973
|
+
{ chainId, supportedChainIds: [8453] },
|
|
3974
|
+
this.id
|
|
3975
|
+
);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
assertOptionalChain(chainId) {
|
|
3979
|
+
if (chainId !== void 0) this.assertChain(chainId);
|
|
3980
|
+
}
|
|
3981
|
+
assertAsset(asset) {
|
|
3982
|
+
if (asset !== "USDC" && asset !== "WETH") {
|
|
3983
|
+
throw new OwneyError(
|
|
3984
|
+
"ASSET_UNSUPPORTED",
|
|
3985
|
+
`Yieldseeker does not support asset ${asset} in the Owney rollout.`,
|
|
3986
|
+
{
|
|
3987
|
+
asset,
|
|
3988
|
+
supportedAssets: ["USDC", "WETH"],
|
|
3989
|
+
providerAlsoAdvertises: ["cbBTC"]
|
|
3990
|
+
},
|
|
3991
|
+
this.id
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
invalidResponse(operation, details = {}) {
|
|
3996
|
+
return new OwneyError(
|
|
3997
|
+
"AGENT_INVALID_RESPONSE",
|
|
3998
|
+
`Yieldseeker returned an invalid ${operation} response.`,
|
|
3999
|
+
details,
|
|
4000
|
+
this.id
|
|
4001
|
+
);
|
|
4002
|
+
}
|
|
4003
|
+
};
|
|
4004
|
+
|
|
4005
|
+
// src/lib/routing-api.ts
|
|
4006
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
4007
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4008
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
4009
|
+
try {
|
|
4010
|
+
const res = await fetch(url, {
|
|
4011
|
+
method: "GET",
|
|
4012
|
+
headers: {
|
|
4013
|
+
"Content-Type": "application/json",
|
|
4014
|
+
"x-owney-api-key": `${apiKey}`
|
|
4015
|
+
}
|
|
4016
|
+
});
|
|
4017
|
+
if (!res.ok) {
|
|
4018
|
+
if (res.status !== 404) {
|
|
4019
|
+
console.warn(
|
|
4020
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
return null;
|
|
4024
|
+
}
|
|
4025
|
+
const json = await res.json();
|
|
4026
|
+
const policy = json.success ? json.data ?? null : null;
|
|
4027
|
+
debugLog(
|
|
4028
|
+
"owney-sdk",
|
|
4029
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
4030
|
+
policy ?? void 0
|
|
4031
|
+
);
|
|
4032
|
+
return policy;
|
|
4033
|
+
} catch (error) {
|
|
4034
|
+
console.warn(
|
|
4035
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
4036
|
+
error instanceof Error ? error.message : String(error)
|
|
4037
|
+
);
|
|
4038
|
+
return null;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4042
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
4043
|
+
const res = await fetch(url, {
|
|
4044
|
+
method: "GET",
|
|
4045
|
+
headers: {
|
|
4046
|
+
"Content-Type": "application/json",
|
|
4047
|
+
"x-owney-api-key": `${apiKey}`
|
|
4048
|
+
}
|
|
4049
|
+
});
|
|
4050
|
+
if (!res.ok) {
|
|
4051
|
+
const text = await res.text().catch(() => "");
|
|
4052
|
+
throw new OwneyError(
|
|
4053
|
+
"API_ROUTING_ERROR",
|
|
4054
|
+
`Routing API error ${res.status}: ${text}`,
|
|
4055
|
+
{ statusCode: res.status, responseBody: text }
|
|
4056
|
+
);
|
|
4057
|
+
}
|
|
4058
|
+
const json = await res.json();
|
|
4059
|
+
if (!json.success) {
|
|
4060
|
+
throw new OwneyError(
|
|
4061
|
+
"API_ROUTING_FAILED",
|
|
4062
|
+
`Routing API request failed: ${json.message}`,
|
|
4063
|
+
{ message: json.message }
|
|
4064
|
+
);
|
|
4065
|
+
}
|
|
4066
|
+
return json.data;
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
// src/lib/health-report.ts
|
|
4070
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
4071
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
|
|
4072
|
+
try {
|
|
4073
|
+
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
4074
|
+
method: "POST",
|
|
4075
|
+
headers: {
|
|
4076
|
+
"Content-Type": "application/json",
|
|
4077
|
+
"x-owney-api-key": apiKey
|
|
4078
|
+
},
|
|
4079
|
+
body: JSON.stringify({
|
|
4080
|
+
agent_type: agentType,
|
|
4081
|
+
error_code: errorCode,
|
|
4082
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4083
|
+
})
|
|
4084
|
+
});
|
|
4085
|
+
} catch (err) {
|
|
4086
|
+
console.warn(
|
|
4087
|
+
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
4088
|
+
err instanceof Error ? err.message : err
|
|
4089
|
+
);
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
4093
|
+
try {
|
|
4094
|
+
return await fn();
|
|
4095
|
+
} catch (err) {
|
|
4096
|
+
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
4097
|
+
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
4098
|
+
throw err;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
|
|
4102
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
4103
|
+
var import_viem7 = require("viem");
|
|
4104
|
+
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
4105
|
+
const target = asset.toUpperCase();
|
|
4106
|
+
return agents.map((agent) => {
|
|
4107
|
+
const agentBalance = aggregated[agent.id];
|
|
4108
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
4109
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4110
|
+
);
|
|
4111
|
+
let balance = tokenBalance ? (0, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
|
|
4112
|
+
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
4113
|
+
const chainNameById = {
|
|
4114
|
+
1: "ETHEREUM",
|
|
4115
|
+
8453: "BASE",
|
|
4116
|
+
42161: "ARBITRUM"
|
|
4117
|
+
};
|
|
4118
|
+
const targetChain = chainNameById[chainId];
|
|
4119
|
+
for (const position2 of agentBalance?.positions ?? []) {
|
|
4120
|
+
const positionChain = position2.chain.trim().toUpperCase();
|
|
4121
|
+
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4122
|
+
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4123
|
+
if (position2.amountRaw !== void 0) {
|
|
4124
|
+
try {
|
|
4125
|
+
balance += BigInt(position2.amountRaw);
|
|
4126
|
+
continue;
|
|
4127
|
+
} catch {
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
return { agent, balance };
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
4137
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
4138
|
+
agent,
|
|
4139
|
+
balance,
|
|
4140
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
4141
|
+
}));
|
|
4142
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
4143
|
+
let remainder = requested - assigned;
|
|
4144
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
4145
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
4146
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
4147
|
+
});
|
|
4148
|
+
for (const p of byHeadroom) {
|
|
4149
|
+
if (remainder === 0n) break;
|
|
4150
|
+
const headroom = p.balance - p.planned;
|
|
4151
|
+
if (headroom <= 0n) continue;
|
|
4152
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
4153
|
+
p.planned += take;
|
|
4154
|
+
remainder -= take;
|
|
4155
|
+
}
|
|
4156
|
+
return plans;
|
|
4157
|
+
}
|
|
4158
|
+
function planDisabledDrain(disabled, requested) {
|
|
4159
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
4160
|
+
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
4161
|
+
);
|
|
4162
|
+
const plans = [];
|
|
4163
|
+
let remaining = requested;
|
|
4164
|
+
for (const { agent, balance } of sorted) {
|
|
4165
|
+
if (remaining === 0n) {
|
|
4166
|
+
plans.push({ agent, balance, planned: 0n });
|
|
4167
|
+
continue;
|
|
4168
|
+
}
|
|
4169
|
+
const take = balance < remaining ? balance : remaining;
|
|
4170
|
+
plans.push({ agent, balance, planned: take });
|
|
4171
|
+
remaining -= take;
|
|
4172
|
+
}
|
|
4173
|
+
return { plans, remaining };
|
|
4174
|
+
}
|
|
4175
|
+
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
4176
|
+
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
4177
|
+
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
4178
|
+
const totalHeadroom = candidates.reduce(
|
|
4179
|
+
(s, c) => s + (c.balance - c.planned),
|
|
4180
|
+
0n
|
|
4181
|
+
);
|
|
4182
|
+
if (totalHeadroom === 0n) return;
|
|
4183
|
+
let distributed = 0n;
|
|
4184
|
+
for (const c of candidates) {
|
|
4185
|
+
const headroom = c.balance - c.planned;
|
|
4186
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
4187
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
4188
|
+
c.planned += give;
|
|
4189
|
+
distributed += give;
|
|
4190
|
+
}
|
|
4191
|
+
let leftover = amount - distributed;
|
|
4192
|
+
for (const c of candidates) {
|
|
4193
|
+
if (leftover === 0n) break;
|
|
4194
|
+
const headroom = c.balance - c.planned;
|
|
4195
|
+
if (headroom <= 0n) continue;
|
|
4196
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
4197
|
+
c.planned += take;
|
|
4198
|
+
leftover -= take;
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
function sumWithdrawnAmount(results) {
|
|
4202
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
2545
4203
|
}
|
|
2546
4204
|
|
|
2547
|
-
// src/lib/
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
};
|
|
2553
|
-
function chainName(chainId) {
|
|
2554
|
-
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2555
|
-
}
|
|
2556
|
-
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2557
|
-
const actual = await pub.getChainId();
|
|
2558
|
-
if (actual === expected) return;
|
|
2559
|
-
try {
|
|
2560
|
-
await wallet.switchChain({ id: expected });
|
|
2561
|
-
} catch (error) {
|
|
2562
|
-
throw new OwneyError(
|
|
2563
|
-
"CHAIN_MISMATCH",
|
|
2564
|
-
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2565
|
-
{
|
|
2566
|
-
expectedChainId: expected,
|
|
2567
|
-
actualChainId: actual,
|
|
2568
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2569
|
-
}
|
|
2570
|
-
);
|
|
4205
|
+
// src/lib/helpers/account-apy-helper.ts
|
|
4206
|
+
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
4207
|
+
if (!tokenSymbol) {
|
|
4208
|
+
const total = Number(balance.totalBalance);
|
|
4209
|
+
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
2571
4210
|
}
|
|
2572
|
-
const
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
);
|
|
4211
|
+
const normalizedToken = tokenSymbol.toUpperCase();
|
|
4212
|
+
const snapshots = balance.assetBalances?.filter(
|
|
4213
|
+
(token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
|
|
4214
|
+
);
|
|
4215
|
+
if (snapshots?.length) {
|
|
4216
|
+
const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
|
|
4217
|
+
if (Number.isFinite(amount)) return Math.max(0, amount);
|
|
2579
4218
|
}
|
|
4219
|
+
return balance.tokens.reduce((total, token) => {
|
|
4220
|
+
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
4221
|
+
return total;
|
|
4222
|
+
}
|
|
4223
|
+
const amount = Number(token.amount);
|
|
4224
|
+
return Number.isFinite(amount) && amount > 0 ? total + amount : total;
|
|
4225
|
+
}, 0);
|
|
2580
4226
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
"CHAIN_UNSUPPORTED",
|
|
2592
|
-
`No sponsored token configured for chain ${chainId}`
|
|
2593
|
-
);
|
|
4227
|
+
function aggregateApyHistory(agentApys) {
|
|
4228
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
4229
|
+
for (const accountApy of Object.values(agentApys)) {
|
|
4230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4231
|
+
for (const point of accountApy.history ?? []) {
|
|
4232
|
+
if (!point.date || seen.has(point.date) || !Number.isFinite(point.apy)) continue;
|
|
4233
|
+
seen.add(point.date);
|
|
4234
|
+
const points = byDate.get(point.date) ?? [];
|
|
4235
|
+
points.push(point);
|
|
4236
|
+
byDate.set(point.date, points);
|
|
2594
4237
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2600
|
-
if (balance < BigInt(amount)) {
|
|
2601
|
-
throw new OwneyError(
|
|
2602
|
-
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2603
|
-
"Insufficient balance for this deposit.",
|
|
2604
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
} catch (err) {
|
|
2608
|
-
if (err instanceof OwneyError) throw err;
|
|
2609
|
-
console.warn(
|
|
2610
|
-
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2611
|
-
err instanceof Error ? err.message : String(err)
|
|
2612
|
-
);
|
|
4238
|
+
}
|
|
4239
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([date, points]) => {
|
|
4240
|
+
if (points.length === 1 && !points[0].historicalBalance) {
|
|
4241
|
+
return [{ date, apy: points[0].apy }];
|
|
2613
4242
|
}
|
|
2614
|
-
const
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
);
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
apiKey: deps.apiKey,
|
|
2642
|
-
body: {
|
|
2643
|
-
chainId: cid,
|
|
2644
|
-
token,
|
|
2645
|
-
from: deps.ownerAddress,
|
|
2646
|
-
to: smartWallet,
|
|
2647
|
-
value: amount,
|
|
2648
|
-
validAfter: validAfter.toString(),
|
|
2649
|
-
validBefore: validBefore.toString(),
|
|
2650
|
-
nonce,
|
|
2651
|
-
authSignature,
|
|
2652
|
-
tokenName,
|
|
2653
|
-
tokenVersion
|
|
4243
|
+
const unit = points[0].historicalBalance?.unit;
|
|
4244
|
+
if (!unit || points.some(
|
|
4245
|
+
({ historicalBalance: balance }) => !balance || balance.unit !== unit || !Number.isFinite(balance.amount) || balance.amount < 0
|
|
4246
|
+
)) return [];
|
|
4247
|
+
const total = points.reduce((sum, p) => sum + p.historicalBalance.amount, 0);
|
|
4248
|
+
if (total <= 0 || !Number.isFinite(total)) return [];
|
|
4249
|
+
const apy = points.reduce((sum, p) => sum + p.apy * (p.historicalBalance.amount / total), 0);
|
|
4250
|
+
return Number.isFinite(apy) ? [{ date, apy }] : [];
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4253
|
+
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
4254
|
+
const sums = {};
|
|
4255
|
+
const weights = {};
|
|
4256
|
+
for (const id of Object.keys(agentApys)) {
|
|
4257
|
+
const cells = agentApys[id].apyByChainAndAsset;
|
|
4258
|
+
const balance = agentBalances[id] ?? 0;
|
|
4259
|
+
if (!cells || balance <= 0) continue;
|
|
4260
|
+
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
4261
|
+
if (!perAsset) continue;
|
|
4262
|
+
const chainId = Number(chainKey);
|
|
4263
|
+
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
4264
|
+
const apy = Number(apyValue ?? 0);
|
|
4265
|
+
if (apy === 0) continue;
|
|
4266
|
+
sums[chainId] ??= {};
|
|
4267
|
+
weights[chainId] ??= {};
|
|
4268
|
+
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
4269
|
+
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2654
4270
|
}
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
};
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
const out = {};
|
|
4274
|
+
for (const chainKey of Object.keys(sums)) {
|
|
4275
|
+
const chainId = Number(chainKey);
|
|
4276
|
+
const perAssetOut = {};
|
|
4277
|
+
for (const asset of Object.keys(sums[chainId])) {
|
|
4278
|
+
const w = weights[chainId][asset];
|
|
4279
|
+
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
4280
|
+
}
|
|
4281
|
+
if (Object.keys(perAssetOut).length > 0) {
|
|
4282
|
+
out[chainId] = perAssetOut;
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
return out;
|
|
2658
4286
|
}
|
|
2659
4287
|
|
|
4288
|
+
// src/client.ts
|
|
4289
|
+
var import_viem9 = require("viem");
|
|
4290
|
+
var import_chains4 = require("viem/chains");
|
|
4291
|
+
|
|
2660
4292
|
// src/lib/sponsored-weth-deposit.ts
|
|
2661
4293
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2662
4294
|
function makeSponsoredWethCallback(deps) {
|
|
2663
4295
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2664
4296
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2665
|
-
return
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
"CHAIN_UNSUPPORTED",
|
|
2671
|
-
`No sponsored WETH configured for chain ${chainId}`
|
|
2672
|
-
);
|
|
2673
|
-
}
|
|
2674
|
-
const amountWei = BigInt(amount);
|
|
2675
|
-
const pub = deps.getPublicClient(cid);
|
|
2676
|
-
const wallet = deps.getWalletClient(cid);
|
|
2677
|
-
await ensureWalletOnChain(pub, wallet, cid);
|
|
2678
|
-
try {
|
|
2679
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2680
|
-
if (balance < amountWei) {
|
|
4297
|
+
return makeVerificationAwareDepositCallback(
|
|
4298
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
4299
|
+
const cid = chainId;
|
|
4300
|
+
const token = deps.tokenAddressByChain[cid];
|
|
4301
|
+
if (!token) {
|
|
2681
4302
|
throw new OwneyError(
|
|
2682
|
-
"
|
|
2683
|
-
|
|
2684
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
4303
|
+
"CHAIN_UNSUPPORTED",
|
|
4304
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2685
4305
|
);
|
|
2686
4306
|
}
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
)
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
});
|
|
2707
|
-
const nonce = randomPermit2Nonce();
|
|
2708
|
-
const deadline = BigInt(
|
|
2709
|
-
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
2710
|
-
);
|
|
2711
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
2712
|
-
chainId: cid,
|
|
2713
|
-
message: {
|
|
2714
|
-
permitted: { token, amount: amountWei },
|
|
2715
|
-
spender: relayer,
|
|
2716
|
-
nonce,
|
|
2717
|
-
deadline
|
|
4307
|
+
const amountWei = BigInt(amount);
|
|
4308
|
+
const pub = deps.getPublicClient(cid);
|
|
4309
|
+
const wallet = deps.getWalletClient(cid);
|
|
4310
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
4311
|
+
try {
|
|
4312
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
4313
|
+
if (balance < amountWei) {
|
|
4314
|
+
throw new OwneyError(
|
|
4315
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4316
|
+
"Insufficient WETH balance for this deposit.",
|
|
4317
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
4318
|
+
);
|
|
4319
|
+
}
|
|
4320
|
+
} catch (err) {
|
|
4321
|
+
if (err instanceof OwneyError) throw err;
|
|
4322
|
+
console.warn(
|
|
4323
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
4324
|
+
err instanceof Error ? err.message : String(err)
|
|
4325
|
+
);
|
|
2718
4326
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
account: deps.ownerAddress,
|
|
2722
|
-
...typedData
|
|
2723
|
-
});
|
|
2724
|
-
deps.onApproved?.();
|
|
2725
|
-
const result = await post({
|
|
2726
|
-
baseUrl: deps.baseUrl,
|
|
2727
|
-
apiKey: deps.apiKey,
|
|
2728
|
-
body: {
|
|
2729
|
-
chainId: cid,
|
|
4327
|
+
const allowance = await readPermit2Allowance(
|
|
4328
|
+
pub,
|
|
2730
4329
|
token,
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
4330
|
+
deps.ownerAddress
|
|
4331
|
+
);
|
|
4332
|
+
if (allowance < amountWei) {
|
|
4333
|
+
throw new OwneyError(
|
|
4334
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
4335
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
4336
|
+
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
4337
|
+
);
|
|
2737
4338
|
}
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
4339
|
+
const relayer = await get({
|
|
4340
|
+
baseUrl: deps.baseUrl,
|
|
4341
|
+
apiKey: deps.apiKey,
|
|
4342
|
+
chainId: cid
|
|
4343
|
+
});
|
|
4344
|
+
const nonce = randomPermit2Nonce();
|
|
4345
|
+
const deadline = BigInt(
|
|
4346
|
+
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
4347
|
+
);
|
|
4348
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
4349
|
+
chainId: cid,
|
|
4350
|
+
message: {
|
|
4351
|
+
permitted: { token, amount: amountWei },
|
|
4352
|
+
spender: relayer,
|
|
4353
|
+
nonce,
|
|
4354
|
+
deadline
|
|
4355
|
+
}
|
|
4356
|
+
});
|
|
4357
|
+
const signature = await wallet.signTypedData({
|
|
4358
|
+
account: deps.ownerAddress,
|
|
4359
|
+
...typedData
|
|
4360
|
+
});
|
|
4361
|
+
deps.onApproved?.();
|
|
4362
|
+
const result = await post({
|
|
4363
|
+
baseUrl: deps.baseUrl,
|
|
4364
|
+
apiKey: deps.apiKey,
|
|
4365
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
4366
|
+
body: {
|
|
4367
|
+
chainId: cid,
|
|
4368
|
+
token,
|
|
4369
|
+
from: deps.ownerAddress,
|
|
4370
|
+
to: smartWallet,
|
|
4371
|
+
amount,
|
|
4372
|
+
nonce: nonce.toString(),
|
|
4373
|
+
deadline: deadline.toString(),
|
|
4374
|
+
signature,
|
|
4375
|
+
...verification?.agentId === "yieldseeker" ? {
|
|
4376
|
+
yieldseekerUserId: verification.userId,
|
|
4377
|
+
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
4378
|
+
} : {}
|
|
4379
|
+
}
|
|
4380
|
+
});
|
|
4381
|
+
return result.txHash;
|
|
4382
|
+
}
|
|
4383
|
+
);
|
|
2741
4384
|
}
|
|
2742
4385
|
|
|
2743
4386
|
// src/lib/sponsored-calls-deposit.ts
|
|
2744
|
-
var
|
|
4387
|
+
var import_viem8 = require("viem");
|
|
2745
4388
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
2746
4389
|
var DEFAULT_MAX_POLLS = 30;
|
|
2747
4390
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -2749,7 +4392,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
2749
4392
|
method: "wallet_getCapabilities",
|
|
2750
4393
|
params: [owner]
|
|
2751
4394
|
});
|
|
2752
|
-
const forChain = caps?.[(0,
|
|
4395
|
+
const forChain = caps?.[(0, import_viem8.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
2753
4396
|
return Boolean(forChain?.paymasterService?.supported);
|
|
2754
4397
|
}
|
|
2755
4398
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -2767,7 +4410,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2767
4410
|
}
|
|
2768
4411
|
return new URL(configured, origin).toString();
|
|
2769
4412
|
};
|
|
2770
|
-
return async (smartWallet, chainId, amount) => {
|
|
4413
|
+
return makeVerificationAwareDepositCallback(async (smartWallet, chainId, amount, verification) => {
|
|
2771
4414
|
const cid = chainId;
|
|
2772
4415
|
const token = deps.tokenAddressByChain[cid];
|
|
2773
4416
|
if (!token) {
|
|
@@ -2783,22 +4426,48 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2783
4426
|
{ chainId }
|
|
2784
4427
|
);
|
|
2785
4428
|
}
|
|
2786
|
-
const data = (0,
|
|
2787
|
-
abi:
|
|
4429
|
+
const data = (0, import_viem8.encodeFunctionData)({
|
|
4430
|
+
abi: import_viem8.erc20Abi,
|
|
2788
4431
|
functionName: "transfer",
|
|
2789
4432
|
args: [smartWallet, BigInt(amount)]
|
|
2790
4433
|
});
|
|
4434
|
+
let paymasterUrl = absolutePaymasterUrl();
|
|
4435
|
+
if (verification?.agentId === "yieldseeker") {
|
|
4436
|
+
if (chainId !== 8453) {
|
|
4437
|
+
throw new OwneyError(
|
|
4438
|
+
"CHAIN_UNSUPPORTED",
|
|
4439
|
+
`Yieldseeker Base Account sponsorship is not available on chain ${chainId}.`
|
|
4440
|
+
);
|
|
4441
|
+
}
|
|
4442
|
+
const { intent } = await postPaymasterIntent({
|
|
4443
|
+
baseUrl: deps.routingApiBaseUrl,
|
|
4444
|
+
apiKey: deps.apiKey,
|
|
4445
|
+
yieldseekerSignature: verification.signature,
|
|
4446
|
+
body: {
|
|
4447
|
+
chainId,
|
|
4448
|
+
token,
|
|
4449
|
+
from: deps.ownerAddress,
|
|
4450
|
+
to: smartWallet,
|
|
4451
|
+
amount,
|
|
4452
|
+
yieldseekerUserId: verification.userId,
|
|
4453
|
+
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
4454
|
+
}
|
|
4455
|
+
});
|
|
4456
|
+
const url = new URL(paymasterUrl);
|
|
4457
|
+
url.searchParams.set("owneyIntent", intent);
|
|
4458
|
+
paymasterUrl = url.toString();
|
|
4459
|
+
}
|
|
2791
4460
|
const sendResult = await deps.provider.request({
|
|
2792
4461
|
method: "wallet_sendCalls",
|
|
2793
4462
|
params: [
|
|
2794
4463
|
{
|
|
2795
4464
|
version: "2.0.0",
|
|
2796
4465
|
from: deps.ownerAddress,
|
|
2797
|
-
chainId: (0,
|
|
4466
|
+
chainId: (0, import_viem8.toHex)(chainId),
|
|
2798
4467
|
atomicRequired: false,
|
|
2799
4468
|
calls: [{ to: token, value: "0x0", data }],
|
|
2800
4469
|
capabilities: {
|
|
2801
|
-
paymasterService: { url:
|
|
4470
|
+
paymasterService: { url: paymasterUrl }
|
|
2802
4471
|
}
|
|
2803
4472
|
}
|
|
2804
4473
|
]
|
|
@@ -2828,7 +4497,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2828
4497
|
`No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
|
|
2829
4498
|
{ chainId, callsId }
|
|
2830
4499
|
);
|
|
2831
|
-
};
|
|
4500
|
+
});
|
|
2832
4501
|
}
|
|
2833
4502
|
|
|
2834
4503
|
// src/client.ts
|
|
@@ -2858,9 +4527,9 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
2858
4527
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
2859
4528
|
};
|
|
2860
4529
|
var VIEM_CHAIN2 = {
|
|
2861
|
-
8453:
|
|
2862
|
-
42161:
|
|
2863
|
-
1:
|
|
4530
|
+
8453: import_chains4.base,
|
|
4531
|
+
42161: import_chains4.arbitrum,
|
|
4532
|
+
1: import_chains4.mainnet
|
|
2864
4533
|
};
|
|
2865
4534
|
var SPONSORED_WETH_BY_CHAIN = {
|
|
2866
4535
|
8453: "0x4200000000000000000000000000000000000006",
|
|
@@ -2888,6 +4557,8 @@ var OwneySDK = class {
|
|
|
2888
4557
|
orgAgentConfig;
|
|
2889
4558
|
orgAgentConfigPromise = null;
|
|
2890
4559
|
zyfaiRpcUrls;
|
|
4560
|
+
yieldseekerApiBaseUrl;
|
|
4561
|
+
yieldseekerSiweOrigin;
|
|
2891
4562
|
routingApiBaseUrl;
|
|
2892
4563
|
referralSource;
|
|
2893
4564
|
cachedSponsoredCallback = null;
|
|
@@ -2910,6 +4581,8 @@ var OwneySDK = class {
|
|
|
2910
4581
|
this.apiKey = config.apiKey;
|
|
2911
4582
|
if (config.debug) setOwneyDebug(true);
|
|
2912
4583
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
4584
|
+
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4585
|
+
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
2913
4586
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
2914
4587
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
2915
4588
|
this.referralSource = config.referralSource;
|
|
@@ -3016,14 +4689,14 @@ var OwneySDK = class {
|
|
|
3016
4689
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3017
4690
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3018
4691
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3019
|
-
getPublicClient: (cid) => (0,
|
|
4692
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
3020
4693
|
chain: VIEM_CHAIN2[cid],
|
|
3021
|
-
transport: (0,
|
|
4694
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3022
4695
|
}),
|
|
3023
|
-
getWalletClient: (cid) => (0,
|
|
4696
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
3024
4697
|
account: owner,
|
|
3025
4698
|
chain: VIEM_CHAIN2[cid],
|
|
3026
|
-
transport: (0,
|
|
4699
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3027
4700
|
})
|
|
3028
4701
|
});
|
|
3029
4702
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
@@ -3040,6 +4713,8 @@ var OwneySDK = class {
|
|
|
3040
4713
|
if (!onApproved && cached) return cached;
|
|
3041
4714
|
const provider = this.requireConnectedProvider();
|
|
3042
4715
|
const callback = makeSponsoredCallsCallback({
|
|
4716
|
+
apiKey: this.apiKey,
|
|
4717
|
+
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
3043
4718
|
provider,
|
|
3044
4719
|
ownerAddress: this.state.walletAddress,
|
|
3045
4720
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
@@ -3069,14 +4744,14 @@ var OwneySDK = class {
|
|
|
3069
4744
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3070
4745
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3071
4746
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3072
|
-
getPublicClient: (cid) => (0,
|
|
4747
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
3073
4748
|
chain: VIEM_CHAIN2[cid],
|
|
3074
|
-
transport: (0,
|
|
4749
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3075
4750
|
}),
|
|
3076
|
-
getWalletClient: (cid) => (0,
|
|
4751
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
3077
4752
|
account: owner,
|
|
3078
4753
|
chain: VIEM_CHAIN2[cid],
|
|
3079
|
-
transport: (0,
|
|
4754
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3080
4755
|
})
|
|
3081
4756
|
});
|
|
3082
4757
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -3107,12 +4782,10 @@ var OwneySDK = class {
|
|
|
3107
4782
|
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
3108
4783
|
this.apiKey,
|
|
3109
4784
|
this.routingApiBaseUrl
|
|
3110
|
-
).then(
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
}
|
|
3115
|
-
);
|
|
4785
|
+
).then((config) => {
|
|
4786
|
+
this.orgAgentConfig = config;
|
|
4787
|
+
return config;
|
|
4788
|
+
});
|
|
3116
4789
|
}
|
|
3117
4790
|
return this.orgAgentConfigPromise;
|
|
3118
4791
|
}
|
|
@@ -3152,7 +4825,14 @@ var OwneySDK = class {
|
|
|
3152
4825
|
this.routingApiBaseUrl
|
|
3153
4826
|
);
|
|
3154
4827
|
this.disabledAgents.clear();
|
|
3155
|
-
for (const {
|
|
4828
|
+
for (const {
|
|
4829
|
+
key: key2,
|
|
4830
|
+
agent_type,
|
|
4831
|
+
is_enabled,
|
|
4832
|
+
is_configured
|
|
4833
|
+
} of agentKeys) {
|
|
4834
|
+
const configured = is_configured ?? Boolean(key2);
|
|
4835
|
+
if (!configured) continue;
|
|
3156
4836
|
const agent = this.createAgent(agent_type, key2);
|
|
3157
4837
|
if (!agent) continue;
|
|
3158
4838
|
this.agents.set(agent_type, agent);
|
|
@@ -3176,8 +4856,15 @@ var OwneySDK = class {
|
|
|
3176
4856
|
}
|
|
3177
4857
|
createAgent(agentId, key2) {
|
|
3178
4858
|
if (agentId === "zyfai") {
|
|
4859
|
+
if (!key2) return null;
|
|
3179
4860
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
3180
4861
|
}
|
|
4862
|
+
if (agentId === "yieldseeker") {
|
|
4863
|
+
return new YieldseekerAgent(this.apiKey, {
|
|
4864
|
+
auth: { origin: this.yieldseekerSiweOrigin },
|
|
4865
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
4866
|
+
});
|
|
4867
|
+
}
|
|
3181
4868
|
return null;
|
|
3182
4869
|
}
|
|
3183
4870
|
/**
|
|
@@ -3222,7 +4909,7 @@ var OwneySDK = class {
|
|
|
3222
4909
|
* If provided, ALL specified agents must support the chainId or the call
|
|
3223
4910
|
* throws before activating any agent.
|
|
3224
4911
|
*/
|
|
3225
|
-
async activateAgent(chainId, agentId) {
|
|
4912
|
+
async activateAgent(chainId, agentId, asset) {
|
|
3226
4913
|
const state = this.requireState();
|
|
3227
4914
|
await this.ensureAgentsInitialized();
|
|
3228
4915
|
if (agentId !== void 0) {
|
|
@@ -3258,7 +4945,7 @@ var OwneySDK = class {
|
|
|
3258
4945
|
this.activeAgents.add(id);
|
|
3259
4946
|
}
|
|
3260
4947
|
state.chainId = chainId;
|
|
3261
|
-
await this.activateAgentsInTurn(agents, state, chainId);
|
|
4948
|
+
await this.activateAgentsInTurn(agents, state, chainId, asset);
|
|
3262
4949
|
return;
|
|
3263
4950
|
}
|
|
3264
4951
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -3279,7 +4966,7 @@ var OwneySDK = class {
|
|
|
3279
4966
|
const enabledCompatible = compatible.filter(
|
|
3280
4967
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
3281
4968
|
);
|
|
3282
|
-
await this.activateAgentsInTurn(enabledCompatible, state, chainId);
|
|
4969
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
|
|
3283
4970
|
}
|
|
3284
4971
|
/**
|
|
3285
4972
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -3295,17 +4982,25 @@ var OwneySDK = class {
|
|
|
3295
4982
|
* at a time anyway.
|
|
3296
4983
|
*
|
|
3297
4984
|
* Every agent is attempted even if an earlier one fails, so one declined
|
|
3298
|
-
* signature can't deny the remaining agents their turn.
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
4985
|
+
* signature can't deny the remaining agents their turn. Once all agents have
|
|
4986
|
+
* had a chance, a partial failure identifies the agents that still need a
|
|
4987
|
+
* retry; if none activated, the original provider error is preserved.
|
|
3301
4988
|
*/
|
|
3302
|
-
async activateAgentsInTurn(agents, state, chainId) {
|
|
4989
|
+
async activateAgentsInTurn(agents, state, chainId, asset) {
|
|
3303
4990
|
let firstError = null;
|
|
4991
|
+
const activatedAgentIds = [];
|
|
4992
|
+
const failedAgents = [];
|
|
3304
4993
|
for (const agent of agents) {
|
|
3305
4994
|
try {
|
|
3306
|
-
await agent.activateAgent(state, chainId);
|
|
4995
|
+
await agent.activateAgent(state, chainId, asset);
|
|
3307
4996
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
4997
|
+
activatedAgentIds.push(agent.id);
|
|
3308
4998
|
} catch (error) {
|
|
4999
|
+
failedAgents.push({
|
|
5000
|
+
agentId: agent.id,
|
|
5001
|
+
code: error instanceof OwneyError ? error.code : void 0,
|
|
5002
|
+
message: error instanceof Error ? error.message : String(error)
|
|
5003
|
+
});
|
|
3309
5004
|
if (firstError === null) {
|
|
3310
5005
|
firstError = error;
|
|
3311
5006
|
} else {
|
|
@@ -3313,7 +5008,16 @@ var OwneySDK = class {
|
|
|
3313
5008
|
}
|
|
3314
5009
|
}
|
|
3315
5010
|
}
|
|
3316
|
-
if (firstError
|
|
5011
|
+
if (firstError === null) return;
|
|
5012
|
+
if (activatedAgentIds.length === 0) throw firstError;
|
|
5013
|
+
const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
|
|
5014
|
+
const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
|
|
5015
|
+
const remainingNames = failedAgentIds.map(this.formatAgentName).join(", ");
|
|
5016
|
+
throw new OwneyError(
|
|
5017
|
+
"AGENT_ACTIVATION_PARTIAL_FAILURE",
|
|
5018
|
+
`${activeNames} activated, but ${remainingNames} still needs activation. Try again and approve the remaining wallet request.`,
|
|
5019
|
+
{ activatedAgentIds, failedAgentIds, failures: failedAgents }
|
|
5020
|
+
);
|
|
3317
5021
|
}
|
|
3318
5022
|
/**
|
|
3319
5023
|
* Deposit into a specific agent, or distribute across all eligible agents.
|
|
@@ -3527,10 +5231,10 @@ var OwneySDK = class {
|
|
|
3527
5231
|
agent,
|
|
3528
5232
|
amount: i === agents.length - 1 ? perAgent + remainder : perAgent
|
|
3529
5233
|
}));
|
|
3530
|
-
const
|
|
5234
|
+
const valid2 = splits.filter(
|
|
3531
5235
|
(s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
|
|
3532
5236
|
);
|
|
3533
|
-
if (
|
|
5237
|
+
if (valid2.length === agents.length) {
|
|
3534
5238
|
return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
|
|
3535
5239
|
}
|
|
3536
5240
|
const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
|
|
@@ -3549,6 +5253,11 @@ var OwneySDK = class {
|
|
|
3549
5253
|
)
|
|
3550
5254
|
}));
|
|
3551
5255
|
}
|
|
5256
|
+
formatAgentName(agentId) {
|
|
5257
|
+
if (agentId === "zyfai") return "Zyfai";
|
|
5258
|
+
if (agentId === "yieldseeker") return "Yieldseeker";
|
|
5259
|
+
return agentId;
|
|
5260
|
+
}
|
|
3552
5261
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
3553
5262
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
3554
5263
|
const parsedAmount = BigInt(amount);
|
|
@@ -3580,12 +5289,12 @@ var OwneySDK = class {
|
|
|
3580
5289
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
3581
5290
|
);
|
|
3582
5291
|
const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
|
|
3583
|
-
const
|
|
5292
|
+
const position2 = (balance.positions ?? []).find((p) => {
|
|
3584
5293
|
const positionChain = p.chain.trim().toUpperCase();
|
|
3585
5294
|
const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
|
|
3586
5295
|
return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
|
|
3587
5296
|
});
|
|
3588
|
-
return !!token && Number(token.amount) > 0 || !!
|
|
5297
|
+
return !!token && Number(token.amount) > 0 || !!position2;
|
|
3589
5298
|
} catch (error) {
|
|
3590
5299
|
if (requireReliableRead) {
|
|
3591
5300
|
throw new OwneyError(
|
|
@@ -3700,6 +5409,10 @@ var OwneySDK = class {
|
|
|
3700
5409
|
}
|
|
3701
5410
|
const requested = BigInt(amount);
|
|
3702
5411
|
const aggregated = await this.getBalances();
|
|
5412
|
+
const unavailableAgents = eligibleAgents.filter(
|
|
5413
|
+
(agent) => !(agent.id in aggregated.agentBalances)
|
|
5414
|
+
);
|
|
5415
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
3703
5416
|
const balances = projectAgentBalancesForAsset(
|
|
3704
5417
|
eligibleAgents,
|
|
3705
5418
|
aggregated.agentBalances,
|
|
@@ -3708,7 +5421,18 @@ var OwneySDK = class {
|
|
|
3708
5421
|
assetInfo.decimals
|
|
3709
5422
|
);
|
|
3710
5423
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
3711
|
-
if (totalAvailable
|
|
5424
|
+
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
5425
|
+
throw new OwneyError(
|
|
5426
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5427
|
+
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
5428
|
+
{
|
|
5429
|
+
asset,
|
|
5430
|
+
unavailableAgents: unavailableAgentIds,
|
|
5431
|
+
agentErrors: aggregated.agentErrors
|
|
5432
|
+
}
|
|
5433
|
+
);
|
|
5434
|
+
}
|
|
5435
|
+
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
3712
5436
|
throw new OwneyError(
|
|
3713
5437
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3714
5438
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -3719,6 +5443,7 @@ var OwneySDK = class {
|
|
|
3719
5443
|
}
|
|
3720
5444
|
);
|
|
3721
5445
|
}
|
|
5446
|
+
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
3722
5447
|
const disabledBalances = balances.filter(
|
|
3723
5448
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
3724
5449
|
);
|
|
@@ -3727,7 +5452,7 @@ var OwneySDK = class {
|
|
|
3727
5452
|
);
|
|
3728
5453
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
3729
5454
|
disabledBalances,
|
|
3730
|
-
|
|
5455
|
+
plannedTarget
|
|
3731
5456
|
);
|
|
3732
5457
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
3733
5458
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -3737,7 +5462,9 @@ var OwneySDK = class {
|
|
|
3737
5462
|
}));
|
|
3738
5463
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
3739
5464
|
const results = {};
|
|
3740
|
-
const agentErrors = {
|
|
5465
|
+
const agentErrors = {
|
|
5466
|
+
...aggregated.agentErrors ?? {}
|
|
5467
|
+
};
|
|
3741
5468
|
for (let i = 0; i < plans.length; i++) {
|
|
3742
5469
|
const p = plans[i];
|
|
3743
5470
|
if (p.planned === 0n) continue;
|
|
@@ -3784,7 +5511,8 @@ var OwneySDK = class {
|
|
|
3784
5511
|
requested: amount,
|
|
3785
5512
|
withdrawn: withdrawn.toString(),
|
|
3786
5513
|
partialResults: results,
|
|
3787
|
-
agentErrors
|
|
5514
|
+
agentErrors,
|
|
5515
|
+
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
3788
5516
|
}
|
|
3789
5517
|
);
|
|
3790
5518
|
}
|
|
@@ -3802,7 +5530,10 @@ var OwneySDK = class {
|
|
|
3802
5530
|
if (agentId) {
|
|
3803
5531
|
const agent = this.getAgent(agentId);
|
|
3804
5532
|
const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3805
|
-
return
|
|
5533
|
+
return {
|
|
5534
|
+
...result,
|
|
5535
|
+
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
5536
|
+
};
|
|
3806
5537
|
}
|
|
3807
5538
|
let totalBalance = 0;
|
|
3808
5539
|
const results = {};
|
|
@@ -3810,7 +5541,13 @@ var OwneySDK = class {
|
|
|
3810
5541
|
const balanceResults = await Promise.allSettled(
|
|
3811
5542
|
entries.map(async ([id, agent]) => {
|
|
3812
5543
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3813
|
-
return [
|
|
5544
|
+
return [
|
|
5545
|
+
id,
|
|
5546
|
+
{
|
|
5547
|
+
...b,
|
|
5548
|
+
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
5549
|
+
}
|
|
5550
|
+
];
|
|
3814
5551
|
})
|
|
3815
5552
|
);
|
|
3816
5553
|
let successCount = 0;
|
|
@@ -3832,6 +5569,7 @@ var OwneySDK = class {
|
|
|
3832
5569
|
const retryDelay = rateLimitDelay(reason);
|
|
3833
5570
|
if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
|
|
3834
5571
|
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
5572
|
+
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
|
|
3835
5573
|
}
|
|
3836
5574
|
if (successCount === 0) {
|
|
3837
5575
|
throw new OwneyError(
|
|
@@ -3944,7 +5682,10 @@ var OwneySDK = class {
|
|
|
3944
5682
|
Promise.all(
|
|
3945
5683
|
entries.map(async ([id, agent]) => {
|
|
3946
5684
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3947
|
-
return [
|
|
5685
|
+
return [
|
|
5686
|
+
id,
|
|
5687
|
+
balanceForApyScope(b, chainId, tokenSymbol)
|
|
5688
|
+
];
|
|
3948
5689
|
})
|
|
3949
5690
|
)
|
|
3950
5691
|
]);
|
|
@@ -4125,10 +5866,10 @@ var OwneySDK = class {
|
|
|
4125
5866
|
);
|
|
4126
5867
|
}
|
|
4127
5868
|
const provider = this.requireConnectedProvider();
|
|
4128
|
-
const wallet = (0,
|
|
5869
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
4129
5870
|
account: state.walletAddress,
|
|
4130
5871
|
chain: VIEM_CHAIN2[chainId],
|
|
4131
|
-
transport: (0,
|
|
5872
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4132
5873
|
});
|
|
4133
5874
|
const hash = await wallet.writeContract({
|
|
4134
5875
|
address: token,
|
|
@@ -4138,9 +5879,9 @@ var OwneySDK = class {
|
|
|
4138
5879
|
account: state.walletAddress,
|
|
4139
5880
|
chain: VIEM_CHAIN2[chainId]
|
|
4140
5881
|
});
|
|
4141
|
-
const publicClient = (0,
|
|
5882
|
+
const publicClient = (0, import_viem9.createPublicClient)({
|
|
4142
5883
|
chain: VIEM_CHAIN2[chainId],
|
|
4143
|
-
transport: (0,
|
|
5884
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4144
5885
|
});
|
|
4145
5886
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4146
5887
|
hash,
|
|
@@ -4177,7 +5918,9 @@ var OwneySDK = class {
|
|
|
4177
5918
|
return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
4178
5919
|
}
|
|
4179
5920
|
const results = {};
|
|
4180
|
-
const agentEntries = [...this.agents.entries()]
|
|
5921
|
+
const agentEntries = [...this.agents.entries()].filter(
|
|
5922
|
+
([id]) => !this.isAgentDisabled(id)
|
|
5923
|
+
);
|
|
4181
5924
|
const apyResults = await Promise.all(
|
|
4182
5925
|
agentEntries.map(async ([id, agent]) => {
|
|
4183
5926
|
const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
@@ -4252,13 +5995,13 @@ var OwneySDK = class {
|
|
|
4252
5995
|
};
|
|
4253
5996
|
|
|
4254
5997
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
4255
|
-
var
|
|
4256
|
-
var
|
|
5998
|
+
var import_viem10 = require("viem");
|
|
5999
|
+
var import_siwe2 = require("siwe");
|
|
4257
6000
|
var import_sdk2 = require("@zyfai/sdk");
|
|
4258
6001
|
|
|
4259
6002
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
4260
|
-
var
|
|
4261
|
-
var
|
|
6003
|
+
var KEY_PREFIX4 = "owney.siwx.session";
|
|
6004
|
+
var storage4 = () => {
|
|
4262
6005
|
if (typeof window === "undefined") return null;
|
|
4263
6006
|
try {
|
|
4264
6007
|
return window.localStorage;
|
|
@@ -4266,8 +6009,8 @@ var storage2 = () => {
|
|
|
4266
6009
|
return null;
|
|
4267
6010
|
}
|
|
4268
6011
|
};
|
|
4269
|
-
var
|
|
4270
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
6012
|
+
var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
|
|
6013
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
|
|
4271
6014
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
4272
6015
|
var readLegacySiwxSession = (store, address) => {
|
|
4273
6016
|
if (!store) return null;
|
|
@@ -4298,17 +6041,17 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
4298
6041
|
};
|
|
4299
6042
|
var readSiwxSession = (address, chainId) => {
|
|
4300
6043
|
if (typeof window === "undefined") return null;
|
|
4301
|
-
const key2 =
|
|
4302
|
-
const store =
|
|
4303
|
-
let
|
|
6044
|
+
const key2 = buildKey3(address);
|
|
6045
|
+
const store = storage4();
|
|
6046
|
+
let raw2 = null;
|
|
4304
6047
|
try {
|
|
4305
|
-
|
|
6048
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
4306
6049
|
} catch {
|
|
4307
|
-
|
|
6050
|
+
raw2 = null;
|
|
4308
6051
|
}
|
|
4309
|
-
if (
|
|
6052
|
+
if (raw2) {
|
|
4310
6053
|
try {
|
|
4311
|
-
return JSON.parse(
|
|
6054
|
+
return JSON.parse(raw2);
|
|
4312
6055
|
} catch {
|
|
4313
6056
|
memorySiwxSessions.delete(key2);
|
|
4314
6057
|
try {
|
|
@@ -4327,18 +6070,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
4327
6070
|
};
|
|
4328
6071
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
4329
6072
|
if (typeof window === "undefined") return;
|
|
4330
|
-
const key2 =
|
|
6073
|
+
const key2 = buildKey3(address);
|
|
4331
6074
|
memorySiwxSessions.set(key2, session);
|
|
4332
|
-
const store =
|
|
6075
|
+
const store = storage4();
|
|
4333
6076
|
try {
|
|
4334
6077
|
store?.setItem(key2, JSON.stringify(session));
|
|
4335
6078
|
} catch {
|
|
4336
6079
|
}
|
|
4337
6080
|
};
|
|
4338
6081
|
var clearSiwxSession = (address, _chainId) => {
|
|
4339
|
-
const key2 =
|
|
6082
|
+
const key2 = buildKey3(address);
|
|
4340
6083
|
memorySiwxSessions.delete(key2);
|
|
4341
|
-
const store =
|
|
6084
|
+
const store = storage4();
|
|
4342
6085
|
try {
|
|
4343
6086
|
store?.removeItem(key2);
|
|
4344
6087
|
} catch {
|
|
@@ -4378,8 +6121,8 @@ function buildSIWXConfig(deps) {
|
|
|
4378
6121
|
statement: STATEMENT,
|
|
4379
6122
|
issuedAt,
|
|
4380
6123
|
toString() {
|
|
4381
|
-
return new
|
|
4382
|
-
address: (0,
|
|
6124
|
+
return new import_siwe2.SiweMessage({
|
|
6125
|
+
address: (0, import_viem10.getAddress)(accountAddress),
|
|
4383
6126
|
chainId: numericChainId(chainId),
|
|
4384
6127
|
domain,
|
|
4385
6128
|
uri,
|
|
@@ -4421,7 +6164,7 @@ function buildSIWXConfig(deps) {
|
|
|
4421
6164
|
const persistSession = async (session) => {
|
|
4422
6165
|
const address = session.data.accountAddress;
|
|
4423
6166
|
const id = numericChainId(session.data.chainId);
|
|
4424
|
-
const message = new
|
|
6167
|
+
const message = new import_siwe2.SiweMessage(session.message);
|
|
4425
6168
|
const login = await post("/auth/login", {
|
|
4426
6169
|
message,
|
|
4427
6170
|
signature: session.signature,
|
|
@@ -4471,6 +6214,7 @@ function createOwneySIWX(config) {
|
|
|
4471
6214
|
NotConnectedError,
|
|
4472
6215
|
OwneyError,
|
|
4473
6216
|
OwneySDK,
|
|
6217
|
+
YieldseekerAgent,
|
|
4474
6218
|
createOwneySIWX,
|
|
4475
6219
|
setOwneyDebug
|
|
4476
6220
|
});
|