@owney/sdk 0.7.21-beta.0 → 0.7.21-beta.2
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 +2430 -682
- package/dist/index.d.cts +127 -11
- package/dist/index.d.ts +127 -11
- package/dist/index.js +2440 -680
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -277,18 +277,18 @@ function tokenDecimals(symbol, explicit) {
|
|
|
277
277
|
return explicit;
|
|
278
278
|
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
279
279
|
}
|
|
280
|
-
function mapDeposit(
|
|
280
|
+
function mapDeposit(raw2) {
|
|
281
281
|
return {
|
|
282
|
-
txHash:
|
|
283
|
-
smartWallet:
|
|
284
|
-
amount:
|
|
282
|
+
txHash: raw2.txHash,
|
|
283
|
+
smartWallet: raw2.smartWallet,
|
|
284
|
+
amount: raw2.amount
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
|
-
function mapWithdraw(
|
|
287
|
+
function mapWithdraw(raw2) {
|
|
288
288
|
return {
|
|
289
|
-
txHash:
|
|
290
|
-
type:
|
|
291
|
-
amount:
|
|
289
|
+
txHash: raw2.txHash,
|
|
290
|
+
type: raw2.type,
|
|
291
|
+
amount: raw2.amount
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
294
|
var CHAIN_ID_TO_NAME = {
|
|
@@ -307,10 +307,10 @@ function resolveChainId(chain) {
|
|
|
307
307
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
308
308
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
309
309
|
}
|
|
310
|
-
function mapPendingAllocations(
|
|
311
|
-
if (!Array.isArray(
|
|
310
|
+
function mapPendingAllocations(raw2) {
|
|
311
|
+
if (!Array.isArray(raw2)) return void 0;
|
|
312
312
|
const pending = [];
|
|
313
|
-
for (const entry of
|
|
313
|
+
for (const entry of raw2) {
|
|
314
314
|
if (typeof entry !== "object" || entry === null) continue;
|
|
315
315
|
const e = entry;
|
|
316
316
|
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
@@ -333,8 +333,8 @@ function mapPendingAllocations(raw) {
|
|
|
333
333
|
}
|
|
334
334
|
return pending.length > 0 ? pending : void 0;
|
|
335
335
|
}
|
|
336
|
-
function mapBalances(
|
|
337
|
-
const portfolio =
|
|
336
|
+
function mapBalances(raw2, _chainId, smartWallet) {
|
|
337
|
+
const portfolio = raw2.portfolio;
|
|
338
338
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
339
339
|
let totalBalance = 0;
|
|
340
340
|
const tokens = [];
|
|
@@ -403,8 +403,8 @@ function sumTokenValues(tokens) {
|
|
|
403
403
|
function sumTokenEarnings(tokens) {
|
|
404
404
|
return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
|
|
405
405
|
}
|
|
406
|
-
function mapEarnings(
|
|
407
|
-
const totalEarningsByChain =
|
|
406
|
+
function mapEarnings(raw2, smartWallet) {
|
|
407
|
+
const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
|
|
408
408
|
const tokens = [];
|
|
409
409
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(
|
|
410
410
|
totalEarningsByChain
|
|
@@ -423,15 +423,15 @@ function mapEarnings(raw, smartWallet) {
|
|
|
423
423
|
return {
|
|
424
424
|
smartWallet,
|
|
425
425
|
lifetimeEarnings: sumTokenEarnings(
|
|
426
|
-
|
|
426
|
+
raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
|
|
427
427
|
),
|
|
428
428
|
tokens
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
|
-
function mapWeightedApyByChain(
|
|
432
|
-
if (!
|
|
431
|
+
function mapWeightedApyByChain(raw2) {
|
|
432
|
+
if (!raw2) return void 0;
|
|
433
433
|
const out = {};
|
|
434
|
-
for (const [chainKey, tokenApy] of Object.entries(
|
|
434
|
+
for (const [chainKey, tokenApy] of Object.entries(raw2)) {
|
|
435
435
|
const chainId = Number(chainKey);
|
|
436
436
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
437
437
|
const perAsset = {};
|
|
@@ -471,8 +471,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
|
471
471
|
}
|
|
472
472
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
473
473
|
}
|
|
474
|
-
function mapApyHistory(
|
|
475
|
-
const history = Object.entries(
|
|
474
|
+
function mapApyHistory(raw2, chainId, tokenSymbol) {
|
|
475
|
+
const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
|
|
476
476
|
date,
|
|
477
477
|
apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
|
|
478
478
|
// Provider position balances are treated as decimal amounts of the
|
|
@@ -488,9 +488,9 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
|
|
|
488
488
|
} : {}
|
|
489
489
|
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
490
490
|
return {
|
|
491
|
-
walletAddress:
|
|
492
|
-
weightedApyAfterFee:
|
|
493
|
-
apyByChainAndAsset: mapWeightedApyByChain(
|
|
491
|
+
walletAddress: raw2.walletAddress,
|
|
492
|
+
weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
|
|
493
|
+
apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
|
|
494
494
|
history
|
|
495
495
|
};
|
|
496
496
|
}
|
|
@@ -586,23 +586,23 @@ function mapEntries(rawEntries, chainId) {
|
|
|
586
586
|
};
|
|
587
587
|
});
|
|
588
588
|
}
|
|
589
|
-
function mapUserProfile(
|
|
589
|
+
function mapUserProfile(raw2, userAddress) {
|
|
590
590
|
return {
|
|
591
591
|
address: userAddress,
|
|
592
|
-
smartWallet:
|
|
593
|
-
chains:
|
|
594
|
-
strategy:
|
|
595
|
-
hasActiveSessionKey:
|
|
596
|
-
protocols:
|
|
597
|
-
splitting:
|
|
598
|
-
minSplits:
|
|
592
|
+
smartWallet: raw2.smartWallet || "",
|
|
593
|
+
chains: raw2.chains || [],
|
|
594
|
+
strategy: raw2.strategy,
|
|
595
|
+
hasActiveSessionKey: raw2.hasActiveSessionKey || false,
|
|
596
|
+
protocols: raw2.protocols || [],
|
|
597
|
+
splitting: raw2.splitting,
|
|
598
|
+
minSplits: raw2.minSplits
|
|
599
599
|
};
|
|
600
600
|
}
|
|
601
|
-
function mapApyByStrategy(
|
|
601
|
+
function mapApyByStrategy(raw2) {
|
|
602
602
|
const apyPerAsset = {};
|
|
603
603
|
let apySum = 0;
|
|
604
604
|
let apyCount = 0;
|
|
605
|
-
for (const entry of
|
|
605
|
+
for (const entry of raw2.data) {
|
|
606
606
|
const supported = SupportedAssets.find(
|
|
607
607
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
608
608
|
);
|
|
@@ -773,15 +773,15 @@ var readSession = (address, _chainId) => {
|
|
|
773
773
|
if (typeof window === "undefined") return null;
|
|
774
774
|
const key2 = buildKey(address);
|
|
775
775
|
const store = storage();
|
|
776
|
-
let
|
|
776
|
+
let raw2 = null;
|
|
777
777
|
try {
|
|
778
|
-
|
|
778
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
779
779
|
} catch {
|
|
780
|
-
|
|
780
|
+
raw2 = null;
|
|
781
781
|
}
|
|
782
|
-
if (
|
|
782
|
+
if (raw2) {
|
|
783
783
|
try {
|
|
784
|
-
const parsed = JSON.parse(
|
|
784
|
+
const parsed = JSON.parse(raw2);
|
|
785
785
|
if (isFreshSession(parsed)) return parsed;
|
|
786
786
|
} catch {
|
|
787
787
|
}
|
|
@@ -949,8 +949,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
|
|
|
949
949
|
}
|
|
950
950
|
return result;
|
|
951
951
|
}
|
|
952
|
-
function flattenAvailablePools(
|
|
953
|
-
const byChain =
|
|
952
|
+
function flattenAvailablePools(raw2) {
|
|
953
|
+
const byChain = raw2 ?? {};
|
|
954
954
|
const names = [];
|
|
955
955
|
for (const byToken of Object.values(byChain ?? {})) {
|
|
956
956
|
for (const entry of Object.values(byToken ?? {})) {
|
|
@@ -1453,8 +1453,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1453
1453
|
const poolResults = await Promise.all(
|
|
1454
1454
|
universe.map(async (protocol) => {
|
|
1455
1455
|
try {
|
|
1456
|
-
const
|
|
1457
|
-
return [protocol.id, flattenAvailablePools(
|
|
1456
|
+
const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
|
|
1457
|
+
return [protocol.id, flattenAvailablePools(raw2)];
|
|
1458
1458
|
} catch (error) {
|
|
1459
1459
|
console.warn(
|
|
1460
1460
|
`[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
|
|
@@ -1520,14 +1520,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1520
1520
|
async readWalletState(ownerAddress) {
|
|
1521
1521
|
try {
|
|
1522
1522
|
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1523
|
-
const
|
|
1523
|
+
const raw2 = portfolio;
|
|
1524
1524
|
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1525
|
-
predeployed:
|
|
1526
|
-
hasActiveSessionKey:
|
|
1525
|
+
predeployed: raw2?.predeployed,
|
|
1526
|
+
hasActiveSessionKey: raw2?.hasActiveSessionKey
|
|
1527
1527
|
});
|
|
1528
1528
|
return {
|
|
1529
|
-
predeployed:
|
|
1530
|
-
hasActiveSessionKey:
|
|
1529
|
+
predeployed: raw2?.predeployed,
|
|
1530
|
+
hasActiveSessionKey: raw2?.hasActiveSessionKey
|
|
1531
1531
|
};
|
|
1532
1532
|
} catch (error) {
|
|
1533
1533
|
console.warn(
|
|
@@ -1813,14 +1813,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1813
1813
|
return { txHash, smartWallet, amount };
|
|
1814
1814
|
}
|
|
1815
1815
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
1816
|
-
const
|
|
1816
|
+
const raw2 = await this.sdk.depositFunds(
|
|
1817
1817
|
this.getAddress(),
|
|
1818
1818
|
validChainId,
|
|
1819
1819
|
amount,
|
|
1820
1820
|
asset,
|
|
1821
1821
|
"aggressive"
|
|
1822
1822
|
);
|
|
1823
|
-
return mapDeposit(
|
|
1823
|
+
return mapDeposit(raw2);
|
|
1824
1824
|
} catch (error) {
|
|
1825
1825
|
throw error;
|
|
1826
1826
|
}
|
|
@@ -1829,27 +1829,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1829
1829
|
async withdraw(state, chainId, token, amount) {
|
|
1830
1830
|
const validChainId = isValidChainId(chainId);
|
|
1831
1831
|
await this.ensureConnected(state, validChainId);
|
|
1832
|
-
const
|
|
1832
|
+
const raw2 = await this.sdk.withdrawFunds(
|
|
1833
1833
|
this.getAddress(),
|
|
1834
1834
|
validChainId,
|
|
1835
1835
|
amount,
|
|
1836
1836
|
token
|
|
1837
1837
|
);
|
|
1838
|
-
if (!
|
|
1838
|
+
if (!raw2.success) {
|
|
1839
1839
|
throw new OwneyError(
|
|
1840
1840
|
"WITHDRAW_FAILED",
|
|
1841
|
-
|
|
1842
|
-
{ chainId: validChainId, token, amount, response:
|
|
1841
|
+
raw2.message || "Zyfai withdraw failed.",
|
|
1842
|
+
{ chainId: validChainId, token, amount, response: raw2 },
|
|
1843
1843
|
this.id
|
|
1844
1844
|
);
|
|
1845
1845
|
}
|
|
1846
|
-
return mapWithdraw(
|
|
1846
|
+
return mapWithdraw(raw2);
|
|
1847
1847
|
}
|
|
1848
1848
|
// --- IAgent: Portfolio reads ---
|
|
1849
1849
|
async getBalances(state, chainId) {
|
|
1850
1850
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
1851
|
-
const
|
|
1852
|
-
return mapBalances(
|
|
1851
|
+
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
1852
|
+
return mapBalances(raw2, validChainId, smartWallet);
|
|
1853
1853
|
}
|
|
1854
1854
|
earningsKey(state, chainId, smartWallet) {
|
|
1855
1855
|
return JSON.stringify([
|
|
@@ -1862,11 +1862,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1862
1862
|
const existing = this.earningsReads.get(key2);
|
|
1863
1863
|
if (existing) return existing;
|
|
1864
1864
|
const generation = this.earningsGeneration;
|
|
1865
|
-
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((
|
|
1865
|
+
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
|
|
1866
1866
|
if (generation === this.earningsGeneration) {
|
|
1867
|
-
this.earningsSnapshot = { key: key2, raw, at: Date.now() };
|
|
1867
|
+
this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
|
|
1868
1868
|
}
|
|
1869
|
-
return
|
|
1869
|
+
return raw2;
|
|
1870
1870
|
}).finally(() => {
|
|
1871
1871
|
if (this.earningsReads.get(key2) === pending)
|
|
1872
1872
|
this.earningsReads.delete(key2);
|
|
@@ -1876,11 +1876,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1876
1876
|
}
|
|
1877
1877
|
async getEarnings(state, chainId) {
|
|
1878
1878
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1879
|
-
const
|
|
1879
|
+
const raw2 = await this.readEarnings(
|
|
1880
1880
|
this.earningsKey(state, chainId, smartWallet),
|
|
1881
1881
|
smartWallet
|
|
1882
1882
|
);
|
|
1883
|
-
return mapEarnings(
|
|
1883
|
+
return mapEarnings(raw2, smartWallet);
|
|
1884
1884
|
}
|
|
1885
1885
|
async refreshEarnings(state, chainId) {
|
|
1886
1886
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -1907,8 +1907,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1907
1907
|
}
|
|
1908
1908
|
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
1909
1909
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1910
|
-
const
|
|
1911
|
-
return mapApyHistory(
|
|
1910
|
+
const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
1911
|
+
return mapApyHistory(raw2, chainId, tokenSymbol);
|
|
1912
1912
|
}
|
|
1913
1913
|
/**
|
|
1914
1914
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
@@ -1943,7 +1943,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1943
1943
|
const matched = [];
|
|
1944
1944
|
let backendExhausted = false;
|
|
1945
1945
|
for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
|
|
1946
|
-
const
|
|
1946
|
+
const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
|
|
1947
1947
|
limit: backendPageSize,
|
|
1948
1948
|
offset,
|
|
1949
1949
|
fromDate: options?.fromDate,
|
|
@@ -1954,13 +1954,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1954
1954
|
// asset's rows and handing back a page that filters to nothing.
|
|
1955
1955
|
assetType
|
|
1956
1956
|
});
|
|
1957
|
-
|
|
1957
|
+
raw2.data.forEach((entry, idx) => {
|
|
1958
1958
|
if (entry.chainId === validChainId) {
|
|
1959
1959
|
matched.push({ entry, rawIdx: offset + idx });
|
|
1960
1960
|
}
|
|
1961
1961
|
});
|
|
1962
|
-
offset +=
|
|
1963
|
-
if (
|
|
1962
|
+
offset += raw2.data.length;
|
|
1963
|
+
if (raw2.data.length < backendPageSize) {
|
|
1964
1964
|
backendExhausted = true;
|
|
1965
1965
|
break;
|
|
1966
1966
|
}
|
|
@@ -1982,18 +1982,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1982
1982
|
}
|
|
1983
1983
|
async getUserProfile(state, chainId) {
|
|
1984
1984
|
await this.connectAuth(state, chainId);
|
|
1985
|
-
const
|
|
1985
|
+
const raw2 = await this.sdk.getUserDetails();
|
|
1986
1986
|
debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
|
|
1987
1987
|
asset: "USDC (default \u2014 no asset passed)",
|
|
1988
|
-
splitting:
|
|
1989
|
-
minSplits:
|
|
1990
|
-
strategy:
|
|
1991
|
-
chains:
|
|
1992
|
-
protocolCount:
|
|
1993
|
-
hasActiveSessionKey:
|
|
1994
|
-
smartWallet:
|
|
1988
|
+
splitting: raw2.splitting,
|
|
1989
|
+
minSplits: raw2.minSplits,
|
|
1990
|
+
strategy: raw2.strategy,
|
|
1991
|
+
chains: raw2.chains,
|
|
1992
|
+
protocolCount: raw2.protocols?.length,
|
|
1993
|
+
hasActiveSessionKey: raw2.hasActiveSessionKey,
|
|
1994
|
+
smartWallet: raw2.smartWallet
|
|
1995
1995
|
});
|
|
1996
|
-
return mapUserProfile(
|
|
1996
|
+
return mapUserProfile(raw2, this.connectedAddress);
|
|
1997
1997
|
}
|
|
1998
1998
|
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
1999
1999
|
await this.connectAuth(state, chainId);
|
|
@@ -2012,400 +2012,227 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2012
2012
|
}
|
|
2013
2013
|
// --- IAgent: Discovery (no wallet required) ---
|
|
2014
2014
|
async getAgentApy(days, options) {
|
|
2015
|
-
const
|
|
2015
|
+
const raw2 = await this.sdk.getAPYPerStrategy(
|
|
2016
2016
|
false,
|
|
2017
2017
|
DayFilterMapping[days],
|
|
2018
2018
|
"aggressive",
|
|
2019
2019
|
options?.chainId,
|
|
2020
2020
|
options?.tokenSymbol
|
|
2021
2021
|
);
|
|
2022
|
-
return mapApyByStrategy(
|
|
2022
|
+
return mapApyByStrategy(raw2);
|
|
2023
2023
|
}
|
|
2024
2024
|
};
|
|
2025
2025
|
|
|
2026
|
-
// src/
|
|
2026
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2027
|
+
import {
|
|
2028
|
+
createPublicClient as createPublicClient3,
|
|
2029
|
+
createWalletClient as createWalletClient2,
|
|
2030
|
+
custom as custom2,
|
|
2031
|
+
encodeFunctionData,
|
|
2032
|
+
erc20Abi,
|
|
2033
|
+
getAddress as getAddress2,
|
|
2034
|
+
isAddress as isAddress2
|
|
2035
|
+
} from "viem";
|
|
2036
|
+
import { base as base3 } from "viem/chains";
|
|
2037
|
+
|
|
2038
|
+
// src/lib/chain-guard.ts
|
|
2039
|
+
var CHAIN_NAMES = {
|
|
2040
|
+
1: "Ethereum",
|
|
2041
|
+
8453: "Base",
|
|
2042
|
+
42161: "Arbitrum"
|
|
2043
|
+
};
|
|
2044
|
+
function chainName(chainId) {
|
|
2045
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2046
|
+
}
|
|
2047
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2048
|
+
const actual = await pub.getChainId();
|
|
2049
|
+
if (actual === expected) return;
|
|
2050
|
+
try {
|
|
2051
|
+
await wallet.switchChain({ id: expected });
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
throw new OwneyError(
|
|
2054
|
+
"CHAIN_MISMATCH",
|
|
2055
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2056
|
+
{
|
|
2057
|
+
expectedChainId: expected,
|
|
2058
|
+
actualChainId: actual,
|
|
2059
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2060
|
+
}
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
const after = await pub.getChainId();
|
|
2064
|
+
if (after !== expected) {
|
|
2065
|
+
throw new OwneyError(
|
|
2066
|
+
"CHAIN_MISMATCH",
|
|
2067
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2068
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
// src/lib/transfer-auth.ts
|
|
2074
|
+
import { bytesToHex } from "viem";
|
|
2075
|
+
var ERC20_META_ABI = [
|
|
2076
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2077
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2078
|
+
];
|
|
2079
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
2080
|
+
return {
|
|
2081
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2082
|
+
types: {
|
|
2083
|
+
TransferWithAuthorization: [
|
|
2084
|
+
{ name: "from", type: "address" },
|
|
2085
|
+
{ name: "to", type: "address" },
|
|
2086
|
+
{ name: "value", type: "uint256" },
|
|
2087
|
+
{ name: "validAfter", type: "uint256" },
|
|
2088
|
+
{ name: "validBefore", type: "uint256" },
|
|
2089
|
+
{ name: "nonce", type: "bytes32" }
|
|
2090
|
+
]
|
|
2091
|
+
},
|
|
2092
|
+
primaryType: "TransferWithAuthorization",
|
|
2093
|
+
message: input.message
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
async function readTokenMeta(publicClient, token) {
|
|
2097
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
2098
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2099
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2100
|
+
]);
|
|
2101
|
+
return { tokenName, tokenVersion };
|
|
2102
|
+
}
|
|
2103
|
+
function randomAuthNonce() {
|
|
2104
|
+
const bytes = new Uint8Array(32);
|
|
2105
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2106
|
+
return bytesToHex(bytes);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/lib/sponsor-client.ts
|
|
2027
2110
|
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2028
|
-
async function
|
|
2029
|
-
const
|
|
2111
|
+
async function postPaymasterIntent(input) {
|
|
2112
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2113
|
+
let res;
|
|
2030
2114
|
try {
|
|
2031
|
-
|
|
2032
|
-
method: "
|
|
2115
|
+
res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
|
|
2116
|
+
method: "POST",
|
|
2033
2117
|
headers: {
|
|
2034
|
-
"
|
|
2035
|
-
"x-owney-api-key":
|
|
2036
|
-
|
|
2118
|
+
"content-type": "application/json",
|
|
2119
|
+
"x-owney-api-key": input.apiKey,
|
|
2120
|
+
Authorization: `Signature ${input.yieldseekerSignature}`
|
|
2121
|
+
},
|
|
2122
|
+
body: JSON.stringify(input.body)
|
|
2037
2123
|
});
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
}
|
|
2044
|
-
return null;
|
|
2045
|
-
}
|
|
2046
|
-
const json = await res.json();
|
|
2047
|
-
const policy = json.success ? json.data ?? null : null;
|
|
2048
|
-
debugLog(
|
|
2049
|
-
"owney-sdk",
|
|
2050
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
2051
|
-
policy ?? void 0
|
|
2124
|
+
} catch (networkError) {
|
|
2125
|
+
throw new OwneyError(
|
|
2126
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2127
|
+
`Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2128
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
2052
2129
|
);
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2130
|
+
}
|
|
2131
|
+
const text = await res.text();
|
|
2132
|
+
let parsed = null;
|
|
2133
|
+
try {
|
|
2134
|
+
parsed = JSON.parse(text);
|
|
2135
|
+
} catch {
|
|
2136
|
+
}
|
|
2137
|
+
if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
|
|
2138
|
+
throw new OwneyError(
|
|
2139
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2140
|
+
`Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2141
|
+
{
|
|
2142
|
+
statusCode: res.status,
|
|
2143
|
+
responseBody: text.slice(0, 500),
|
|
2144
|
+
safeToFallback: true
|
|
2145
|
+
}
|
|
2058
2146
|
);
|
|
2059
|
-
return null;
|
|
2060
2147
|
}
|
|
2148
|
+
return parsed.data;
|
|
2061
2149
|
}
|
|
2062
|
-
async function
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2150
|
+
async function postSponsorTransferAuth(input) {
|
|
2151
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2152
|
+
let res;
|
|
2153
|
+
try {
|
|
2154
|
+
res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2155
|
+
method: "POST",
|
|
2156
|
+
headers: {
|
|
2157
|
+
"content-type": "application/json",
|
|
2158
|
+
"x-owney-api-key": input.apiKey,
|
|
2159
|
+
...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
|
|
2160
|
+
},
|
|
2161
|
+
body: JSON.stringify(input.body)
|
|
2162
|
+
});
|
|
2163
|
+
} catch (networkError) {
|
|
2073
2164
|
throw new OwneyError(
|
|
2074
|
-
"
|
|
2075
|
-
`
|
|
2076
|
-
{
|
|
2165
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2166
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2167
|
+
{ cause: String(networkError) }
|
|
2077
2168
|
);
|
|
2078
2169
|
}
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2170
|
+
const text = await res.text();
|
|
2171
|
+
let parsed = null;
|
|
2172
|
+
try {
|
|
2173
|
+
parsed = JSON.parse(text);
|
|
2174
|
+
} catch {
|
|
2175
|
+
}
|
|
2176
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2081
2177
|
throw new OwneyError(
|
|
2082
|
-
"
|
|
2083
|
-
`
|
|
2084
|
-
{
|
|
2178
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2179
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2180
|
+
{
|
|
2181
|
+
statusCode: res.status,
|
|
2182
|
+
responseBody: text.slice(0, 500),
|
|
2183
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2184
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2185
|
+
safeToFallback: res.status === 503
|
|
2186
|
+
}
|
|
2085
2187
|
);
|
|
2086
2188
|
}
|
|
2087
|
-
return
|
|
2189
|
+
return parsed.data;
|
|
2088
2190
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
2191
|
+
async function postSponsorPermit2Transfer(input) {
|
|
2192
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2193
|
+
let res;
|
|
2093
2194
|
try {
|
|
2094
|
-
await fetch(`${
|
|
2195
|
+
res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
|
|
2095
2196
|
method: "POST",
|
|
2096
2197
|
headers: {
|
|
2097
|
-
"
|
|
2098
|
-
"x-owney-api-key": apiKey
|
|
2198
|
+
"content-type": "application/json",
|
|
2199
|
+
"x-owney-api-key": input.apiKey,
|
|
2200
|
+
...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
|
|
2099
2201
|
},
|
|
2100
|
-
body: JSON.stringify(
|
|
2101
|
-
agent_type: agentType,
|
|
2102
|
-
error_code: errorCode,
|
|
2103
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2104
|
-
})
|
|
2202
|
+
body: JSON.stringify(input.body)
|
|
2105
2203
|
});
|
|
2106
|
-
} catch (
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2204
|
+
} catch (networkError) {
|
|
2205
|
+
throw new OwneyError(
|
|
2206
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2207
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2208
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
2110
2209
|
);
|
|
2111
2210
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
2211
|
+
const text = await res.text();
|
|
2212
|
+
let parsed = null;
|
|
2114
2213
|
try {
|
|
2115
|
-
|
|
2116
|
-
} catch
|
|
2117
|
-
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
2118
|
-
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
2119
|
-
throw err;
|
|
2214
|
+
parsed = JSON.parse(text);
|
|
2215
|
+
} catch {
|
|
2120
2216
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2217
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2218
|
+
throw new OwneyError(
|
|
2219
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2220
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2221
|
+
{
|
|
2222
|
+
statusCode: res.status,
|
|
2223
|
+
responseBody: text.slice(0, 500),
|
|
2224
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2225
|
+
}
|
|
2131
2226
|
);
|
|
2132
|
-
if (!tokenBalance) return { agent, balance: 0n };
|
|
2133
|
-
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
2136
|
-
function planProportionalShares(balances, requested, totalAvailable) {
|
|
2137
|
-
const plans = balances.map(({ agent, balance }) => ({
|
|
2138
|
-
agent,
|
|
2139
|
-
balance,
|
|
2140
|
-
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
2141
|
-
}));
|
|
2142
|
-
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
2143
|
-
let remainder = requested - assigned;
|
|
2144
|
-
const byHeadroom = [...plans].sort((a, b) => {
|
|
2145
|
-
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
2146
|
-
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2147
|
-
});
|
|
2148
|
-
for (const p of byHeadroom) {
|
|
2149
|
-
if (remainder === 0n) break;
|
|
2150
|
-
const headroom = p.balance - p.planned;
|
|
2151
|
-
if (headroom <= 0n) continue;
|
|
2152
|
-
const take = headroom < remainder ? headroom : remainder;
|
|
2153
|
-
p.planned += take;
|
|
2154
|
-
remainder -= take;
|
|
2155
2227
|
}
|
|
2156
|
-
return
|
|
2157
|
-
}
|
|
2158
|
-
function planDisabledDrain(disabled, requested) {
|
|
2159
|
-
const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
2160
|
-
const plans = [];
|
|
2161
|
-
let remaining = requested;
|
|
2162
|
-
for (const { agent, balance } of sorted) {
|
|
2163
|
-
if (remaining === 0n) {
|
|
2164
|
-
plans.push({ agent, balance, planned: 0n });
|
|
2165
|
-
continue;
|
|
2166
|
-
}
|
|
2167
|
-
const take = balance < remaining ? balance : remaining;
|
|
2168
|
-
plans.push({ agent, balance, planned: take });
|
|
2169
|
-
remaining -= take;
|
|
2170
|
-
}
|
|
2171
|
-
return { plans, remaining };
|
|
2172
|
-
}
|
|
2173
|
-
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
2174
|
-
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
2175
|
-
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
2176
|
-
const totalHeadroom = candidates.reduce(
|
|
2177
|
-
(s, c) => s + (c.balance - c.planned),
|
|
2178
|
-
0n
|
|
2179
|
-
);
|
|
2180
|
-
if (totalHeadroom === 0n) return;
|
|
2181
|
-
let distributed = 0n;
|
|
2182
|
-
for (const c of candidates) {
|
|
2183
|
-
const headroom = c.balance - c.planned;
|
|
2184
|
-
const proportional = headroom * amount / totalHeadroom;
|
|
2185
|
-
const give = proportional > headroom ? headroom : proportional;
|
|
2186
|
-
c.planned += give;
|
|
2187
|
-
distributed += give;
|
|
2188
|
-
}
|
|
2189
|
-
let leftover = amount - distributed;
|
|
2190
|
-
for (const c of candidates) {
|
|
2191
|
-
if (leftover === 0n) break;
|
|
2192
|
-
const headroom = c.balance - c.planned;
|
|
2193
|
-
if (headroom <= 0n) continue;
|
|
2194
|
-
const take = headroom < leftover ? headroom : leftover;
|
|
2195
|
-
c.planned += take;
|
|
2196
|
-
leftover -= take;
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
function sumWithdrawnAmount(results) {
|
|
2200
|
-
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
|
-
// src/lib/helpers/account-apy-helper.ts
|
|
2204
|
-
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
2205
|
-
if (!tokenSymbol) {
|
|
2206
|
-
const total = Number(balance.totalBalance);
|
|
2207
|
-
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
2208
|
-
}
|
|
2209
|
-
const normalizedToken = tokenSymbol.toUpperCase();
|
|
2210
|
-
return balance.tokens.reduce((total, token) => {
|
|
2211
|
-
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
2212
|
-
return total;
|
|
2213
|
-
}
|
|
2214
|
-
const amount = Number(token.amount);
|
|
2215
|
-
return Number.isFinite(amount) && amount > 0 ? total + amount : total;
|
|
2216
|
-
}, 0);
|
|
2217
|
-
}
|
|
2218
|
-
function aggregateApyHistory(agentApys) {
|
|
2219
|
-
const byDate = /* @__PURE__ */ new Map();
|
|
2220
|
-
for (const accountApy of Object.values(agentApys)) {
|
|
2221
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2222
|
-
for (const point of accountApy.history ?? []) {
|
|
2223
|
-
if (!point.date || seen.has(point.date) || !Number.isFinite(point.apy)) continue;
|
|
2224
|
-
seen.add(point.date);
|
|
2225
|
-
const points = byDate.get(point.date) ?? [];
|
|
2226
|
-
points.push(point);
|
|
2227
|
-
byDate.set(point.date, points);
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([date, points]) => {
|
|
2231
|
-
if (points.length === 1 && !points[0].historicalBalance) {
|
|
2232
|
-
return [{ date, apy: points[0].apy }];
|
|
2233
|
-
}
|
|
2234
|
-
const unit = points[0].historicalBalance?.unit;
|
|
2235
|
-
if (!unit || points.some(
|
|
2236
|
-
({ historicalBalance: balance }) => !balance || balance.unit !== unit || !Number.isFinite(balance.amount) || balance.amount < 0
|
|
2237
|
-
)) return [];
|
|
2238
|
-
const total = points.reduce((sum, p) => sum + p.historicalBalance.amount, 0);
|
|
2239
|
-
if (total <= 0 || !Number.isFinite(total)) return [];
|
|
2240
|
-
const apy = points.reduce((sum, p) => sum + p.apy * (p.historicalBalance.amount / total), 0);
|
|
2241
|
-
return Number.isFinite(apy) ? [{ date, apy }] : [];
|
|
2242
|
-
});
|
|
2243
|
-
}
|
|
2244
|
-
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
2245
|
-
const sums = {};
|
|
2246
|
-
const weights = {};
|
|
2247
|
-
for (const id of Object.keys(agentApys)) {
|
|
2248
|
-
const cells = agentApys[id].apyByChainAndAsset;
|
|
2249
|
-
const balance = agentBalances[id] ?? 0;
|
|
2250
|
-
if (!cells || balance <= 0) continue;
|
|
2251
|
-
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
2252
|
-
if (!perAsset) continue;
|
|
2253
|
-
const chainId = Number(chainKey);
|
|
2254
|
-
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
2255
|
-
const apy = Number(apyValue ?? 0);
|
|
2256
|
-
if (apy === 0) continue;
|
|
2257
|
-
sums[chainId] ??= {};
|
|
2258
|
-
weights[chainId] ??= {};
|
|
2259
|
-
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
2260
|
-
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
const out = {};
|
|
2265
|
-
for (const chainKey of Object.keys(sums)) {
|
|
2266
|
-
const chainId = Number(chainKey);
|
|
2267
|
-
const perAssetOut = {};
|
|
2268
|
-
for (const asset of Object.keys(sums[chainId])) {
|
|
2269
|
-
const w = weights[chainId][asset];
|
|
2270
|
-
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
2271
|
-
}
|
|
2272
|
-
if (Object.keys(perAssetOut).length > 0) {
|
|
2273
|
-
out[chainId] = perAssetOut;
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
return out;
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
// src/client.ts
|
|
2280
|
-
import {
|
|
2281
|
-
createPublicClient as createPublicClient2,
|
|
2282
|
-
createWalletClient,
|
|
2283
|
-
custom
|
|
2284
|
-
} from "viem";
|
|
2285
|
-
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
2286
|
-
|
|
2287
|
-
// src/lib/transfer-auth.ts
|
|
2288
|
-
import { bytesToHex } from "viem";
|
|
2289
|
-
var ERC20_META_ABI = [
|
|
2290
|
-
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2291
|
-
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2292
|
-
];
|
|
2293
|
-
function buildTransferWithAuthorizationTypedData(input) {
|
|
2294
|
-
return {
|
|
2295
|
-
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2296
|
-
types: {
|
|
2297
|
-
TransferWithAuthorization: [
|
|
2298
|
-
{ name: "from", type: "address" },
|
|
2299
|
-
{ name: "to", type: "address" },
|
|
2300
|
-
{ name: "value", type: "uint256" },
|
|
2301
|
-
{ name: "validAfter", type: "uint256" },
|
|
2302
|
-
{ name: "validBefore", type: "uint256" },
|
|
2303
|
-
{ name: "nonce", type: "bytes32" }
|
|
2304
|
-
]
|
|
2305
|
-
},
|
|
2306
|
-
primaryType: "TransferWithAuthorization",
|
|
2307
|
-
message: input.message
|
|
2308
|
-
};
|
|
2309
|
-
}
|
|
2310
|
-
async function readTokenMeta(publicClient, token) {
|
|
2311
|
-
const [tokenName, tokenVersion] = await Promise.all([
|
|
2312
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2313
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2314
|
-
]);
|
|
2315
|
-
return { tokenName, tokenVersion };
|
|
2316
|
-
}
|
|
2317
|
-
function randomAuthNonce() {
|
|
2318
|
-
const bytes = new Uint8Array(32);
|
|
2319
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2320
|
-
return bytesToHex(bytes);
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
// src/lib/sponsor-client.ts
|
|
2324
|
-
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2325
|
-
async function postSponsorTransferAuth(input) {
|
|
2326
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2327
|
-
let res;
|
|
2328
|
-
try {
|
|
2329
|
-
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2330
|
-
method: "POST",
|
|
2331
|
-
headers: {
|
|
2332
|
-
"content-type": "application/json",
|
|
2333
|
-
"x-owney-api-key": input.apiKey
|
|
2334
|
-
},
|
|
2335
|
-
body: JSON.stringify(input.body)
|
|
2336
|
-
});
|
|
2337
|
-
} catch (networkError) {
|
|
2338
|
-
throw new OwneyError(
|
|
2339
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2340
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2341
|
-
{ cause: String(networkError) }
|
|
2342
|
-
);
|
|
2343
|
-
}
|
|
2344
|
-
const text = await res.text();
|
|
2345
|
-
let parsed = null;
|
|
2346
|
-
try {
|
|
2347
|
-
parsed = JSON.parse(text);
|
|
2348
|
-
} catch {
|
|
2349
|
-
}
|
|
2350
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2351
|
-
throw new OwneyError(
|
|
2352
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2353
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2354
|
-
{
|
|
2355
|
-
statusCode: res.status,
|
|
2356
|
-
responseBody: text.slice(0, 500),
|
|
2357
|
-
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2358
|
-
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2359
|
-
safeToFallback: res.status === 503
|
|
2360
|
-
}
|
|
2361
|
-
);
|
|
2362
|
-
}
|
|
2363
|
-
return parsed.data;
|
|
2364
|
-
}
|
|
2365
|
-
async function postSponsorPermit2Transfer(input) {
|
|
2366
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2367
|
-
let res;
|
|
2368
|
-
try {
|
|
2369
|
-
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
2370
|
-
method: "POST",
|
|
2371
|
-
headers: {
|
|
2372
|
-
"content-type": "application/json",
|
|
2373
|
-
"x-owney-api-key": input.apiKey
|
|
2374
|
-
},
|
|
2375
|
-
body: JSON.stringify(input.body)
|
|
2376
|
-
});
|
|
2377
|
-
} catch (networkError) {
|
|
2378
|
-
throw new OwneyError(
|
|
2379
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2380
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2381
|
-
{ cause: String(networkError), safeToFallback: false }
|
|
2382
|
-
);
|
|
2383
|
-
}
|
|
2384
|
-
const text = await res.text();
|
|
2385
|
-
let parsed = null;
|
|
2386
|
-
try {
|
|
2387
|
-
parsed = JSON.parse(text);
|
|
2388
|
-
} catch {
|
|
2389
|
-
}
|
|
2390
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2391
|
-
throw new OwneyError(
|
|
2392
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2393
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2394
|
-
{
|
|
2395
|
-
statusCode: res.status,
|
|
2396
|
-
responseBody: text.slice(0, 500),
|
|
2397
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2398
|
-
}
|
|
2399
|
-
);
|
|
2400
|
-
}
|
|
2401
|
-
return parsed.data;
|
|
2228
|
+
return parsed.data;
|
|
2402
2229
|
}
|
|
2403
2230
|
async function getSponsorRelayerAddress(input) {
|
|
2404
|
-
const
|
|
2231
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2405
2232
|
let res;
|
|
2406
2233
|
try {
|
|
2407
2234
|
res = await fetch(
|
|
2408
|
-
`${
|
|
2235
|
+
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2409
2236
|
{
|
|
2410
2237
|
headers: { "x-owney-api-key": input.apiKey }
|
|
2411
2238
|
}
|
|
@@ -2506,213 +2333,2041 @@ async function readPermit2Allowance(publicClient, token, owner) {
|
|
|
2506
2333
|
args: [owner, PERMIT2_ADDRESS]
|
|
2507
2334
|
});
|
|
2508
2335
|
}
|
|
2509
|
-
async function readErc20Balance(publicClient, token, owner) {
|
|
2510
|
-
return publicClient.readContract({
|
|
2511
|
-
address: token,
|
|
2512
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2513
|
-
functionName: "balanceOf",
|
|
2514
|
-
args: [owner]
|
|
2515
|
-
});
|
|
2336
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2337
|
+
return publicClient.readContract({
|
|
2338
|
+
address: token,
|
|
2339
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2340
|
+
functionName: "balanceOf",
|
|
2341
|
+
args: [owner]
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// src/lib/sponsored-deposit.ts
|
|
2346
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2347
|
+
var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
|
|
2348
|
+
function provideDepositVerificationContext(callback, context) {
|
|
2349
|
+
callback[verificationSetter]?.(context);
|
|
2350
|
+
}
|
|
2351
|
+
function makeVerificationAwareDepositCallback(implementation) {
|
|
2352
|
+
let nextVerification;
|
|
2353
|
+
const callback = async (smartWallet, chainId, amount) => {
|
|
2354
|
+
const verification = nextVerification;
|
|
2355
|
+
nextVerification = void 0;
|
|
2356
|
+
return implementation(smartWallet, chainId, amount, verification);
|
|
2357
|
+
};
|
|
2358
|
+
Object.defineProperty(callback, verificationSetter, {
|
|
2359
|
+
value: (context) => {
|
|
2360
|
+
nextVerification = context;
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
return callback;
|
|
2364
|
+
}
|
|
2365
|
+
function makeSponsoredDepositCallback(deps) {
|
|
2366
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
2367
|
+
return makeVerificationAwareDepositCallback(
|
|
2368
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
2369
|
+
const cid = chainId;
|
|
2370
|
+
const token = deps.tokenAddressByChain[cid];
|
|
2371
|
+
if (!token) {
|
|
2372
|
+
throw new OwneyError(
|
|
2373
|
+
"CHAIN_UNSUPPORTED",
|
|
2374
|
+
`No sponsored token configured for chain ${chainId}`
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
const pub = deps.getPublicClient(cid);
|
|
2378
|
+
const wallet = deps.getWalletClient(cid);
|
|
2379
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2380
|
+
try {
|
|
2381
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2382
|
+
if (balance < BigInt(amount)) {
|
|
2383
|
+
throw new OwneyError(
|
|
2384
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2385
|
+
"Insufficient balance for this deposit.",
|
|
2386
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
} catch (err) {
|
|
2390
|
+
if (err instanceof OwneyError) throw err;
|
|
2391
|
+
console.warn(
|
|
2392
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2393
|
+
err instanceof Error ? err.message : String(err)
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
2397
|
+
const validAfter = 0n;
|
|
2398
|
+
const validBefore = BigInt(
|
|
2399
|
+
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2400
|
+
);
|
|
2401
|
+
const nonce = randomAuthNonce();
|
|
2402
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
2403
|
+
token,
|
|
2404
|
+
chainId: cid,
|
|
2405
|
+
tokenName,
|
|
2406
|
+
tokenVersion,
|
|
2407
|
+
message: {
|
|
2408
|
+
from: deps.ownerAddress,
|
|
2409
|
+
to: smartWallet,
|
|
2410
|
+
value: BigInt(amount),
|
|
2411
|
+
validAfter,
|
|
2412
|
+
validBefore,
|
|
2413
|
+
nonce
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
const authSignature = await wallet.signTypedData({
|
|
2417
|
+
account: deps.ownerAddress,
|
|
2418
|
+
...typedData
|
|
2419
|
+
});
|
|
2420
|
+
deps.onApproved?.();
|
|
2421
|
+
const result = await post({
|
|
2422
|
+
baseUrl: deps.baseUrl,
|
|
2423
|
+
apiKey: deps.apiKey,
|
|
2424
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
2425
|
+
body: {
|
|
2426
|
+
chainId: cid,
|
|
2427
|
+
token,
|
|
2428
|
+
from: deps.ownerAddress,
|
|
2429
|
+
to: smartWallet,
|
|
2430
|
+
value: amount,
|
|
2431
|
+
validAfter: validAfter.toString(),
|
|
2432
|
+
validBefore: validBefore.toString(),
|
|
2433
|
+
nonce,
|
|
2434
|
+
authSignature,
|
|
2435
|
+
tokenName,
|
|
2436
|
+
tokenVersion,
|
|
2437
|
+
...verification?.agentId === "yieldseeker" ? {
|
|
2438
|
+
yieldseekerUserId: verification.userId,
|
|
2439
|
+
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
2440
|
+
} : {}
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
return result.txHash;
|
|
2444
|
+
}
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2449
|
+
import { SiweMessage, generateNonce } from "siwe";
|
|
2450
|
+
import {
|
|
2451
|
+
createPublicClient as createPublicClient2,
|
|
2452
|
+
createWalletClient,
|
|
2453
|
+
custom,
|
|
2454
|
+
getAddress
|
|
2455
|
+
} from "viem";
|
|
2456
|
+
import { base as base2 } from "viem/chains";
|
|
2457
|
+
|
|
2458
|
+
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
2459
|
+
var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
|
|
2460
|
+
var INVALIDATED_KEY_PREFIXES = [
|
|
2461
|
+
"owney.yieldseeker.session",
|
|
2462
|
+
"owney.yieldseeker.session.v3",
|
|
2463
|
+
"owney.yieldseeker.session.v4"
|
|
2464
|
+
];
|
|
2465
|
+
var storage2 = () => {
|
|
2466
|
+
if (typeof window === "undefined") return null;
|
|
2467
|
+
try {
|
|
2468
|
+
return window.localStorage;
|
|
2469
|
+
} catch {
|
|
2470
|
+
return null;
|
|
2471
|
+
}
|
|
2472
|
+
};
|
|
2473
|
+
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2474
|
+
var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
|
|
2475
|
+
(prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
|
|
2476
|
+
);
|
|
2477
|
+
var clearInvalidatedSessions = (store, address, chainId) => {
|
|
2478
|
+
for (const key2 of invalidatedKeys(address, chainId)) {
|
|
2479
|
+
memorySessions2.delete(key2);
|
|
2480
|
+
try {
|
|
2481
|
+
store?.removeItem(key2);
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2487
|
+
var isValidSession = (session) => {
|
|
2488
|
+
if (!session?.token) return false;
|
|
2489
|
+
try {
|
|
2490
|
+
const parsed = JSON.parse(atob(session.token));
|
|
2491
|
+
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2492
|
+
} catch {
|
|
2493
|
+
return false;
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
var readYieldseekerSession = (address, chainId) => {
|
|
2497
|
+
if (typeof window === "undefined") return null;
|
|
2498
|
+
const key2 = buildKey2(address, chainId);
|
|
2499
|
+
const store = storage2();
|
|
2500
|
+
clearInvalidatedSessions(store, address, chainId);
|
|
2501
|
+
let raw2 = null;
|
|
2502
|
+
try {
|
|
2503
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
2504
|
+
} catch {
|
|
2505
|
+
raw2 = null;
|
|
2506
|
+
}
|
|
2507
|
+
if (raw2) {
|
|
2508
|
+
try {
|
|
2509
|
+
const parsed = JSON.parse(raw2);
|
|
2510
|
+
if (isValidSession(parsed)) return parsed.token;
|
|
2511
|
+
} catch {
|
|
2512
|
+
}
|
|
2513
|
+
memorySessions2.delete(key2);
|
|
2514
|
+
try {
|
|
2515
|
+
store?.removeItem(key2);
|
|
2516
|
+
} catch {
|
|
2517
|
+
}
|
|
2518
|
+
return null;
|
|
2519
|
+
}
|
|
2520
|
+
const cached = memorySessions2.get(key2);
|
|
2521
|
+
if (isValidSession(cached)) return cached.token;
|
|
2522
|
+
if (cached) memorySessions2.delete(key2);
|
|
2523
|
+
return null;
|
|
2524
|
+
};
|
|
2525
|
+
var writeYieldseekerSession = (address, chainId, token) => {
|
|
2526
|
+
if (typeof window === "undefined") return;
|
|
2527
|
+
const session = { token };
|
|
2528
|
+
if (!isValidSession(session)) return;
|
|
2529
|
+
const key2 = buildKey2(address, chainId);
|
|
2530
|
+
memorySessions2.set(key2, session);
|
|
2531
|
+
const store = storage2();
|
|
2532
|
+
try {
|
|
2533
|
+
store?.setItem(key2, JSON.stringify(session));
|
|
2534
|
+
} catch {
|
|
2535
|
+
}
|
|
2536
|
+
};
|
|
2537
|
+
var clearYieldseekerSession = (address, chainId) => {
|
|
2538
|
+
const key2 = buildKey2(address, chainId);
|
|
2539
|
+
memorySessions2.delete(key2);
|
|
2540
|
+
const store = storage2();
|
|
2541
|
+
clearInvalidatedSessions(store, address, chainId);
|
|
2542
|
+
try {
|
|
2543
|
+
store?.removeItem(key2);
|
|
2544
|
+
} catch {
|
|
2545
|
+
}
|
|
2546
|
+
};
|
|
2547
|
+
|
|
2548
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2549
|
+
function resolveSiweOrigin(override) {
|
|
2550
|
+
const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
|
|
2551
|
+
if (!origin || origin === "null") {
|
|
2552
|
+
throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
|
|
2553
|
+
}
|
|
2554
|
+
const url = new URL(origin);
|
|
2555
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2556
|
+
throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
|
|
2557
|
+
}
|
|
2558
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
2559
|
+
throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
|
|
2560
|
+
}
|
|
2561
|
+
return url;
|
|
2562
|
+
}
|
|
2563
|
+
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2564
|
+
const url = resolveSiweOrigin(dependencies.origin);
|
|
2565
|
+
return new SiweMessage({
|
|
2566
|
+
scheme: url.protocol.slice(0, -1),
|
|
2567
|
+
domain: url.host,
|
|
2568
|
+
address: getAddress(address),
|
|
2569
|
+
uri: url.origin,
|
|
2570
|
+
version: "1",
|
|
2571
|
+
chainId,
|
|
2572
|
+
nonce: (dependencies.nonce ?? generateNonce)(),
|
|
2573
|
+
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2574
|
+
}).prepareMessage();
|
|
2575
|
+
}
|
|
2576
|
+
function encodeYieldseekerAuthToken(token) {
|
|
2577
|
+
const bytes = new TextEncoder().encode(JSON.stringify(token));
|
|
2578
|
+
let binary = "";
|
|
2579
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2580
|
+
return btoa(binary);
|
|
2581
|
+
}
|
|
2582
|
+
var YieldseekerAuth = class {
|
|
2583
|
+
constructor(dependencies = {}) {
|
|
2584
|
+
this.dependencies = dependencies;
|
|
2585
|
+
}
|
|
2586
|
+
dependencies;
|
|
2587
|
+
tokens = /* @__PURE__ */ new Map();
|
|
2588
|
+
pending = /* @__PURE__ */ new Map();
|
|
2589
|
+
scopes = /* @__PURE__ */ new Map();
|
|
2590
|
+
key(state, chainId) {
|
|
2591
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
2592
|
+
}
|
|
2593
|
+
async getToken(state, chainId) {
|
|
2594
|
+
const key2 = this.key(state, chainId);
|
|
2595
|
+
const scope = { address: state.walletAddress, chainId };
|
|
2596
|
+
this.scopes.set(key2, scope);
|
|
2597
|
+
const cached = this.tokens.get(key2);
|
|
2598
|
+
if (cached) return cached;
|
|
2599
|
+
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2600
|
+
if (persisted && this.matchesOrigin(persisted)) {
|
|
2601
|
+
this.tokens.set(key2, persisted);
|
|
2602
|
+
return persisted;
|
|
2603
|
+
}
|
|
2604
|
+
if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
|
|
2605
|
+
const inFlight = this.pending.get(key2);
|
|
2606
|
+
if (inFlight) return inFlight;
|
|
2607
|
+
const request = this.sign(state, chainId).then((token) => {
|
|
2608
|
+
this.tokens.set(key2, token);
|
|
2609
|
+
writeYieldseekerSession(scope.address, scope.chainId, token);
|
|
2610
|
+
return token;
|
|
2611
|
+
});
|
|
2612
|
+
this.pending.set(key2, request);
|
|
2613
|
+
try {
|
|
2614
|
+
return await request;
|
|
2615
|
+
} finally {
|
|
2616
|
+
this.pending.delete(key2);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
matchesOrigin(token) {
|
|
2620
|
+
try {
|
|
2621
|
+
const message = new SiweMessage(JSON.parse(atob(token)).message);
|
|
2622
|
+
const url = resolveSiweOrigin(this.dependencies.origin);
|
|
2623
|
+
return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
|
|
2624
|
+
} catch {
|
|
2625
|
+
return false;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
clear(state, chainId) {
|
|
2629
|
+
if (!state || chainId === void 0) {
|
|
2630
|
+
for (const scope of this.scopes.values()) {
|
|
2631
|
+
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2632
|
+
}
|
|
2633
|
+
this.tokens.clear();
|
|
2634
|
+
this.pending.clear();
|
|
2635
|
+
this.scopes.clear();
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
const key2 = this.key(state, chainId);
|
|
2639
|
+
this.tokens.delete(key2);
|
|
2640
|
+
this.pending.delete(key2);
|
|
2641
|
+
this.scopes.delete(key2);
|
|
2642
|
+
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2643
|
+
}
|
|
2644
|
+
async sign(state, chainId) {
|
|
2645
|
+
const account = getAddress(state.walletAddress);
|
|
2646
|
+
const publicClient = createPublicClient2({
|
|
2647
|
+
chain: base2,
|
|
2648
|
+
transport: custom(state.provider)
|
|
2649
|
+
});
|
|
2650
|
+
const walletClient = createWalletClient({
|
|
2651
|
+
account,
|
|
2652
|
+
chain: base2,
|
|
2653
|
+
transport: custom(state.provider)
|
|
2654
|
+
});
|
|
2655
|
+
await ensureWalletOnChain(
|
|
2656
|
+
publicClient,
|
|
2657
|
+
walletClient,
|
|
2658
|
+
8453
|
|
2659
|
+
);
|
|
2660
|
+
const message = createYieldseekerSiweMessage(
|
|
2661
|
+
account,
|
|
2662
|
+
chainId,
|
|
2663
|
+
this.dependencies
|
|
2664
|
+
);
|
|
2665
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
2666
|
+
return encodeYieldseekerAuthToken({ message, signature });
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
|
|
2670
|
+
// src/agents/yieldseeker/yieldseeker.identity-cache.ts
|
|
2671
|
+
var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
|
|
2672
|
+
var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2673
|
+
var memoryIdentities = /* @__PURE__ */ new Map();
|
|
2674
|
+
var storage3 = () => {
|
|
2675
|
+
if (typeof window === "undefined") return null;
|
|
2676
|
+
try {
|
|
2677
|
+
return window.localStorage;
|
|
2678
|
+
} catch {
|
|
2679
|
+
return null;
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
|
|
2683
|
+
function valid(value, walletAddress, chainId, now) {
|
|
2684
|
+
return Boolean(
|
|
2685
|
+
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
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
|
|
2689
|
+
if (typeof window === "undefined") return null;
|
|
2690
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2691
|
+
const store = storage3();
|
|
2692
|
+
let parsed = null;
|
|
2693
|
+
try {
|
|
2694
|
+
const raw2 = store?.getItem(key2);
|
|
2695
|
+
parsed = raw2 ? JSON.parse(raw2) : null;
|
|
2696
|
+
} catch {
|
|
2697
|
+
parsed = null;
|
|
2698
|
+
}
|
|
2699
|
+
const candidate = parsed ?? memoryIdentities.get(key2);
|
|
2700
|
+
if (valid(candidate, walletAddress, chainId, now)) {
|
|
2701
|
+
memoryIdentities.set(key2, candidate);
|
|
2702
|
+
return { userId: candidate.userId };
|
|
2703
|
+
}
|
|
2704
|
+
memoryIdentities.delete(key2);
|
|
2705
|
+
try {
|
|
2706
|
+
store?.removeItem(key2);
|
|
2707
|
+
} catch {
|
|
2708
|
+
}
|
|
2709
|
+
return null;
|
|
2710
|
+
}
|
|
2711
|
+
function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
|
|
2712
|
+
if (typeof window === "undefined") return;
|
|
2713
|
+
const identity = {
|
|
2714
|
+
userId,
|
|
2715
|
+
walletAddress,
|
|
2716
|
+
chainId,
|
|
2717
|
+
expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
|
|
2718
|
+
};
|
|
2719
|
+
if (!valid(identity, walletAddress, chainId, now)) return;
|
|
2720
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2721
|
+
memoryIdentities.set(key2, identity);
|
|
2722
|
+
try {
|
|
2723
|
+
storage3()?.setItem(key2, JSON.stringify(identity));
|
|
2724
|
+
} catch {
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
function clearYieldseekerIdentity(walletAddress, chainId) {
|
|
2728
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2729
|
+
memoryIdentities.delete(key2);
|
|
2730
|
+
try {
|
|
2731
|
+
storage3()?.removeItem(key2);
|
|
2732
|
+
} catch {
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2737
|
+
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2738
|
+
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2739
|
+
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2740
|
+
}
|
|
2741
|
+
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2742
|
+
var YieldseekerApiError = class extends Error {
|
|
2743
|
+
constructor(status, providerCode, responseFields) {
|
|
2744
|
+
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2745
|
+
this.status = status;
|
|
2746
|
+
this.providerCode = providerCode;
|
|
2747
|
+
this.responseFields = responseFields;
|
|
2748
|
+
this.name = "YieldseekerApiError";
|
|
2749
|
+
}
|
|
2750
|
+
status;
|
|
2751
|
+
providerCode;
|
|
2752
|
+
responseFields;
|
|
2753
|
+
get isAuthenticationError() {
|
|
2754
|
+
return this.status === 401 || this.status === 403;
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
function providerError(body, fallback) {
|
|
2758
|
+
if (!body || typeof body !== "object") return { code: fallback };
|
|
2759
|
+
const record = body;
|
|
2760
|
+
return {
|
|
2761
|
+
code: typeof record.message === "string" ? record.message : fallback,
|
|
2762
|
+
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
var YieldseekerApiClient = class {
|
|
2766
|
+
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
|
|
2767
|
+
this.owneyApiKey = owneyApiKey;
|
|
2768
|
+
this.baseUrl = baseUrl;
|
|
2769
|
+
this.fetchFn = fetchFn;
|
|
2770
|
+
}
|
|
2771
|
+
owneyApiKey;
|
|
2772
|
+
baseUrl;
|
|
2773
|
+
fetchFn;
|
|
2774
|
+
async request(path, options = {}) {
|
|
2775
|
+
const controller = new AbortController();
|
|
2776
|
+
const timer = setTimeout(
|
|
2777
|
+
() => controller.abort(),
|
|
2778
|
+
options.timeoutMs ?? 15e3
|
|
2779
|
+
);
|
|
2780
|
+
try {
|
|
2781
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2782
|
+
method: options.method ?? "GET",
|
|
2783
|
+
headers: {
|
|
2784
|
+
"Content-Type": "application/json",
|
|
2785
|
+
"x-owney-api-key": this.owneyApiKey,
|
|
2786
|
+
...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
|
|
2787
|
+
},
|
|
2788
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2789
|
+
signal: controller.signal
|
|
2790
|
+
});
|
|
2791
|
+
const payload = await response.json().catch(() => null);
|
|
2792
|
+
if (!response.ok) {
|
|
2793
|
+
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2794
|
+
throw new YieldseekerApiError(
|
|
2795
|
+
response.status,
|
|
2796
|
+
error.code,
|
|
2797
|
+
error.fields
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2801
|
+
return payload.data;
|
|
2802
|
+
}
|
|
2803
|
+
return payload;
|
|
2804
|
+
} catch (error) {
|
|
2805
|
+
if (error instanceof YieldseekerApiError) throw error;
|
|
2806
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2807
|
+
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2808
|
+
}
|
|
2809
|
+
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2810
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2811
|
+
});
|
|
2812
|
+
} finally {
|
|
2813
|
+
clearTimeout(timer);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2817
|
+
|
|
2818
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2819
|
+
import { formatUnits, isAddress } from "viem";
|
|
2820
|
+
|
|
2821
|
+
// src/lib/helpers/snapshot-apy.ts
|
|
2822
|
+
var DAY_MS = 864e5;
|
|
2823
|
+
function snapshotTime(date) {
|
|
2824
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
|
|
2825
|
+
const time = Date.parse(date);
|
|
2826
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
|
|
2827
|
+
}
|
|
2828
|
+
function returnFactor(value) {
|
|
2829
|
+
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
2830
|
+
if (typeof value === "string" && value.trim() === "") return void 0;
|
|
2831
|
+
const factor = Number(value);
|
|
2832
|
+
return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
|
|
2833
|
+
}
|
|
2834
|
+
function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
|
|
2835
|
+
if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
|
|
2836
|
+
return void 0;
|
|
2837
|
+
}
|
|
2838
|
+
const points = snapshots.flatMap((snapshot) => {
|
|
2839
|
+
const time = snapshotTime(snapshot.date);
|
|
2840
|
+
return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
|
|
2841
|
+
}).sort((a, b) => a.time - b.time);
|
|
2842
|
+
const end = points.at(-1);
|
|
2843
|
+
if (!end) return void 0;
|
|
2844
|
+
const cutoff = end.time - lookbackDays * DAY_MS;
|
|
2845
|
+
const start = points.find((point) => point.time >= cutoff);
|
|
2846
|
+
const actualDays = (end.time - start.time) / DAY_MS;
|
|
2847
|
+
if (actualDays <= 0) return void 0;
|
|
2848
|
+
const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
|
|
2849
|
+
const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
|
|
2850
|
+
if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
|
|
2851
|
+
return void 0;
|
|
2852
|
+
}
|
|
2853
|
+
const periodReturn = endFactor / startFactor - 1;
|
|
2854
|
+
const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
|
|
2855
|
+
return Number.isFinite(apy) ? apy : void 0;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// src/agents/yieldseeker/yieldseeker.types.ts
|
|
2859
|
+
var YIELDSEEKER_ASSET_METADATA = {
|
|
2860
|
+
USDC: {
|
|
2861
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2862
|
+
decimals: 6
|
|
2863
|
+
},
|
|
2864
|
+
WETH: {
|
|
2865
|
+
address: "0x4200000000000000000000000000000000000006",
|
|
2866
|
+
decimals: 18
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
|
|
2870
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2871
|
+
function invalid(endpoint, detail) {
|
|
2872
|
+
throw new OwneyError(
|
|
2873
|
+
"AGENT_INVALID_RESPONSE",
|
|
2874
|
+
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2875
|
+
{ endpoint, detail },
|
|
2876
|
+
"yieldseeker"
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
function raw(value, endpoint) {
|
|
2880
|
+
if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
|
|
2881
|
+
return invalid(endpoint, "expected a base-10 integer string");
|
|
2882
|
+
}
|
|
2883
|
+
return BigInt(value);
|
|
2884
|
+
}
|
|
2885
|
+
function decimal(value, decimals, endpoint) {
|
|
2886
|
+
return formatUnits(raw(value, endpoint), decimals);
|
|
2887
|
+
}
|
|
2888
|
+
function usd(rawAmount, decimals, price) {
|
|
2889
|
+
return Number(formatUnits(rawAmount, decimals)) * price;
|
|
2890
|
+
}
|
|
2891
|
+
function percent(value) {
|
|
2892
|
+
const result = Number(value);
|
|
2893
|
+
return Number.isFinite(result) ? result * 100 : 0;
|
|
2894
|
+
}
|
|
2895
|
+
var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
|
|
2896
|
+
function publicApyAfterYieldseekerFee(value) {
|
|
2897
|
+
const grossPercent = percent(value);
|
|
2898
|
+
if (grossPercent <= 0) return grossPercent;
|
|
2899
|
+
const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
|
|
2900
|
+
return Math.round(netPercent * 1e12) / 1e12;
|
|
2901
|
+
}
|
|
2902
|
+
function riskAdjustedApyForDays(option, days) {
|
|
2903
|
+
if (days === "7D") return option.riskAdjustedApy7dAverage;
|
|
2904
|
+
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
2905
|
+
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
2906
|
+
}
|
|
2907
|
+
function assetAddressValue(record, address) {
|
|
2908
|
+
const entry = Object.entries(record).find(
|
|
2909
|
+
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
2910
|
+
);
|
|
2911
|
+
return entry?.[1] ?? "0";
|
|
2912
|
+
}
|
|
2913
|
+
function position(value, asset, baseAssetDecimals) {
|
|
2914
|
+
const option = value?.yieldOption;
|
|
2915
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
|
|
2916
|
+
return invalid("yield positions", "missing vault metadata");
|
|
2917
|
+
}
|
|
2918
|
+
return {
|
|
2919
|
+
chain: "BASE",
|
|
2920
|
+
protocol: option.provider,
|
|
2921
|
+
protocolId: option.address,
|
|
2922
|
+
pool: option.name,
|
|
2923
|
+
asset,
|
|
2924
|
+
// `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
|
|
2925
|
+
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2926
|
+
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2927
|
+
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2928
|
+
// share quantity separately because withdraw-from-position expects it.
|
|
2929
|
+
amount: decimal(
|
|
2930
|
+
value.assetsBase,
|
|
2931
|
+
baseAssetDecimals,
|
|
2932
|
+
"yield positions"
|
|
2933
|
+
),
|
|
2934
|
+
amountRaw: String(value.assetsRaw),
|
|
2935
|
+
apy: percent(option.riskAdjustedApy),
|
|
2936
|
+
tvl: Number(option.totalDepositsUsd),
|
|
2937
|
+
liquidity: Number(option.withdrawableDepositsUsd)
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
function mapYieldseekerBalances(contexts) {
|
|
2941
|
+
const tokens = [];
|
|
2942
|
+
const assetBalances = [];
|
|
2943
|
+
const positions = [];
|
|
2944
|
+
let totalUsd = 0;
|
|
2945
|
+
for (const context of contexts) {
|
|
2946
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
2947
|
+
assetBalances.push({
|
|
2948
|
+
chain: "BASE",
|
|
2949
|
+
chainId: 8453,
|
|
2950
|
+
asset: context.asset,
|
|
2951
|
+
amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
|
|
2952
|
+
});
|
|
2953
|
+
const idle = assetAddressValue(
|
|
2954
|
+
context.snapshot.tokenBalances,
|
|
2955
|
+
metadata.address
|
|
2956
|
+
);
|
|
2957
|
+
tokens.push({
|
|
2958
|
+
chain: "BASE",
|
|
2959
|
+
chainId: 8453,
|
|
2960
|
+
asset: context.asset,
|
|
2961
|
+
amount: decimal(idle, metadata.decimals, "snapshot")
|
|
2962
|
+
});
|
|
2963
|
+
positions.push(
|
|
2964
|
+
...context.positions.map(
|
|
2965
|
+
(entry) => position(
|
|
2966
|
+
entry,
|
|
2967
|
+
context.asset,
|
|
2968
|
+
context.snapshot.baseAssetDecimals
|
|
2969
|
+
)
|
|
2970
|
+
)
|
|
2971
|
+
);
|
|
2972
|
+
totalUsd += usd(
|
|
2973
|
+
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
2974
|
+
context.snapshot.baseAssetDecimals,
|
|
2975
|
+
context.snapshot.baseAssetPriceUsd
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
return {
|
|
2979
|
+
...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
|
|
2980
|
+
totalBalance: String(totalUsd),
|
|
2981
|
+
totalBalanceAsset: "usdc",
|
|
2982
|
+
assetBalances,
|
|
2983
|
+
tokens,
|
|
2984
|
+
positions
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
function mapYieldseekerEarnings(contexts) {
|
|
2988
|
+
const tokens = [];
|
|
2989
|
+
let lifetimeEarnings = 0;
|
|
2990
|
+
for (const context of contexts) {
|
|
2991
|
+
const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
|
|
2992
|
+
tokens.push({
|
|
2993
|
+
chain: "BASE",
|
|
2994
|
+
chainId: 8453,
|
|
2995
|
+
asset: context.asset,
|
|
2996
|
+
amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
|
|
2997
|
+
});
|
|
2998
|
+
lifetimeEarnings += usd(
|
|
2999
|
+
amount,
|
|
3000
|
+
context.snapshot.baseAssetDecimals,
|
|
3001
|
+
context.snapshot.baseAssetPriceUsd
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
return {
|
|
3005
|
+
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3006
|
+
lifetimeEarnings,
|
|
3007
|
+
tokens
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
function apyForDays(context, days, now) {
|
|
3011
|
+
if (days === "7D") return percent(context.snapshot.apy7d);
|
|
3012
|
+
if (days === "30D") return percent(context.snapshot.apy30d);
|
|
3013
|
+
const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
|
|
3014
|
+
const apyPercent = apy === void 0 ? void 0 : apy * 100;
|
|
3015
|
+
return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
|
|
3016
|
+
}
|
|
3017
|
+
function dailyApy(point) {
|
|
3018
|
+
const total = raw(point.totalValueBase, "historic position");
|
|
3019
|
+
const earned = raw(point.dailyYieldBase, "historic position");
|
|
3020
|
+
const principal = total - earned;
|
|
3021
|
+
if (principal <= 0n || earned === 0n) return 0;
|
|
3022
|
+
return Number(earned) / Number(principal) * 365 * 100;
|
|
3023
|
+
}
|
|
3024
|
+
function aggregateHistory(contexts, dayCount, now) {
|
|
3025
|
+
const today = new Date(now).toISOString().slice(0, 10);
|
|
3026
|
+
const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
|
|
3027
|
+
const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
|
|
3028
|
+
const unit = assets.size === 1 ? [...assets][0] : "USD";
|
|
3029
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
3030
|
+
for (const context of contexts) {
|
|
3031
|
+
const points = context.historic?.dailyYieldSnapshots ?? [];
|
|
3032
|
+
for (const point of points) {
|
|
3033
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
|
|
3034
|
+
const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
|
|
3035
|
+
const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
|
|
3036
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
3037
|
+
invalid("historic position", "expected a finite non-negative balance");
|
|
3038
|
+
}
|
|
3039
|
+
const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
|
|
3040
|
+
current.weighted += dailyApy(point) * amount;
|
|
3041
|
+
current.amount += amount;
|
|
3042
|
+
byDate.set(point.date, current);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
3046
|
+
date,
|
|
3047
|
+
apy: value.amount > 0 ? value.weighted / value.amount : 0,
|
|
3048
|
+
historicalBalance: { amount: value.amount, unit }
|
|
3049
|
+
}));
|
|
3050
|
+
}
|
|
3051
|
+
function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
|
|
3052
|
+
let weighted = 0;
|
|
3053
|
+
let totalUsd = 0;
|
|
3054
|
+
const byAsset = {};
|
|
3055
|
+
for (const context of contexts) {
|
|
3056
|
+
const valueUsd = usd(
|
|
3057
|
+
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
3058
|
+
context.snapshot.baseAssetDecimals,
|
|
3059
|
+
context.snapshot.baseAssetPriceUsd
|
|
3060
|
+
);
|
|
3061
|
+
const apy = apyForDays(context, days, now);
|
|
3062
|
+
if (apy === void 0) continue;
|
|
3063
|
+
weighted += apy * valueUsd;
|
|
3064
|
+
totalUsd += valueUsd;
|
|
3065
|
+
byAsset[context.asset] = apy;
|
|
3066
|
+
}
|
|
3067
|
+
const dayCount = Number(days.slice(0, -1));
|
|
3068
|
+
return {
|
|
3069
|
+
walletAddress,
|
|
3070
|
+
...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
|
|
3071
|
+
apyByChainAndAsset: { 8453: byAsset },
|
|
3072
|
+
history: aggregateHistory(contexts, dayCount, now)
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
function actionType(value) {
|
|
3076
|
+
const normalized = value.toLowerCase();
|
|
3077
|
+
if (normalized.includes("deposit")) return "Deposit";
|
|
3078
|
+
if (normalized.includes("withdraw")) return "Withdraw";
|
|
3079
|
+
if (normalized.includes("yield") || normalized.includes("earn"))
|
|
3080
|
+
return "Earned";
|
|
3081
|
+
return "Rebalance";
|
|
3082
|
+
}
|
|
3083
|
+
function transactionHashes(details) {
|
|
3084
|
+
if (!details) return [];
|
|
3085
|
+
const values = [
|
|
3086
|
+
details.transactionHash,
|
|
3087
|
+
details.txHash,
|
|
3088
|
+
...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
|
|
3089
|
+
...Array.isArray(details.txHashes) ? details.txHashes : []
|
|
3090
|
+
];
|
|
3091
|
+
return values.filter(
|
|
3092
|
+
(value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
|
|
3093
|
+
).filter((value, index, all) => all.indexOf(value) === index);
|
|
3094
|
+
}
|
|
3095
|
+
function actionEntry(action) {
|
|
3096
|
+
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3097
|
+
return {
|
|
3098
|
+
agent: "yieldseeker",
|
|
3099
|
+
action: actionType(action.actionType),
|
|
3100
|
+
date: action.createdDate,
|
|
3101
|
+
oldApy: null,
|
|
3102
|
+
newApy: null,
|
|
3103
|
+
transactions: [
|
|
3104
|
+
{
|
|
3105
|
+
txHashes: transactionHashes(action.details),
|
|
3106
|
+
chainId: 8453
|
|
3107
|
+
}
|
|
3108
|
+
],
|
|
3109
|
+
rebalanceLog: []
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses) {
|
|
3113
|
+
const from = movement.fromAddress.toLowerCase();
|
|
3114
|
+
const to = movement.toAddress.toLowerCase();
|
|
3115
|
+
const owner = ownerAddress.toLowerCase();
|
|
3116
|
+
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3117
|
+
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3118
|
+
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3119
|
+
return void 0;
|
|
3120
|
+
}
|
|
3121
|
+
let action;
|
|
3122
|
+
if (from === owner && to === agentWallet) {
|
|
3123
|
+
action = "Top up";
|
|
3124
|
+
} else if (from === agentWallet && to === owner) {
|
|
3125
|
+
action = "Withdraw";
|
|
3126
|
+
} else if (from === agentWallet && vaultAddresses.has(to)) {
|
|
3127
|
+
action = "Deposit";
|
|
3128
|
+
}
|
|
3129
|
+
if (!action) return void 0;
|
|
3130
|
+
return {
|
|
3131
|
+
agent: "yieldseeker",
|
|
3132
|
+
action,
|
|
3133
|
+
date: movement.blockDate,
|
|
3134
|
+
oldApy: null,
|
|
3135
|
+
newApy: null,
|
|
3136
|
+
transactions: [
|
|
3137
|
+
{
|
|
3138
|
+
txHashes: [movement.transactionHash],
|
|
3139
|
+
chainId: agent.chainId,
|
|
3140
|
+
tokenSymbol: asset,
|
|
3141
|
+
amount: decimal(
|
|
3142
|
+
movement.assetAmount,
|
|
3143
|
+
YIELDSEEKER_ASSET_METADATA[asset].decimals,
|
|
3144
|
+
"historic position"
|
|
3145
|
+
)
|
|
3146
|
+
}
|
|
3147
|
+
],
|
|
3148
|
+
rebalanceLog: []
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
function mapYieldseekerHistory(contexts, options) {
|
|
3152
|
+
const entries = contexts.flatMap((context) => {
|
|
3153
|
+
const vaultAddresses = new Set(
|
|
3154
|
+
context.positions.map(
|
|
3155
|
+
(position2) => position2.yieldOption.address.toLowerCase()
|
|
3156
|
+
)
|
|
3157
|
+
);
|
|
3158
|
+
return [
|
|
3159
|
+
...(context.historic?.movements ?? []).map(
|
|
3160
|
+
(movement) => movementEntry(
|
|
3161
|
+
movement,
|
|
3162
|
+
context.wallet,
|
|
3163
|
+
context.agent,
|
|
3164
|
+
context.asset,
|
|
3165
|
+
options.ownerAddress,
|
|
3166
|
+
vaultAddresses
|
|
3167
|
+
)
|
|
3168
|
+
),
|
|
3169
|
+
...(context.actions ?? []).map(actionEntry)
|
|
3170
|
+
].filter((entry) => entry !== void 0);
|
|
3171
|
+
});
|
|
3172
|
+
const filtered = entries.filter(
|
|
3173
|
+
(entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)
|
|
3174
|
+
).filter((entry, index, all) => {
|
|
3175
|
+
const transactionHash = entry.transactions[0]?.txHashes[0];
|
|
3176
|
+
if (!transactionHash) return true;
|
|
3177
|
+
return all.findIndex(
|
|
3178
|
+
(candidate) => candidate.action === entry.action && candidate.transactions[0]?.txHashes[0] === transactionHash
|
|
3179
|
+
) === index;
|
|
3180
|
+
}).sort((left, right) => right.date.localeCompare(left.date));
|
|
3181
|
+
return {
|
|
3182
|
+
data: filtered.slice(0, options.limit),
|
|
3183
|
+
// v1 returns the whole action/movement collection and defines no cursor.
|
|
3184
|
+
// Report a terminal page so callers never loop over the same prefix.
|
|
3185
|
+
hasMore: false
|
|
3186
|
+
};
|
|
3187
|
+
}
|
|
3188
|
+
function mapYieldseekerProfile(address, contexts) {
|
|
3189
|
+
const protocols = /* @__PURE__ */ new Set();
|
|
3190
|
+
for (const context of contexts) {
|
|
3191
|
+
for (const current of context.positions) {
|
|
3192
|
+
if (current.yieldOption?.provider) {
|
|
3193
|
+
protocols.add(String(current.yieldOption.provider));
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
return {
|
|
3198
|
+
address,
|
|
3199
|
+
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3200
|
+
chains: contexts.length > 0 ? [8453] : [],
|
|
3201
|
+
hasActiveSessionKey: contexts.some(
|
|
3202
|
+
(context) => context.wallet.initializedDate != null
|
|
3203
|
+
),
|
|
3204
|
+
protocols: [...protocols]
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
function mapYieldseekerAgentApy(options, days) {
|
|
3208
|
+
const perAsset = {};
|
|
3209
|
+
const all = [];
|
|
3210
|
+
for (const entry of options) {
|
|
3211
|
+
const apys = entry.yieldOptions.map(
|
|
3212
|
+
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
3213
|
+
).filter(Number.isFinite);
|
|
3214
|
+
if (apys.length === 0) continue;
|
|
3215
|
+
const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
|
|
3216
|
+
perAsset[entry.asset] = average;
|
|
3217
|
+
all.push(average);
|
|
3218
|
+
}
|
|
3219
|
+
return {
|
|
3220
|
+
averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
|
|
3221
|
+
detailedApys: { apyPerAsset: { 8453: perAsset } }
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3226
|
+
var OWNEY_AGENT_NAME = "owney";
|
|
3227
|
+
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3228
|
+
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3229
|
+
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3230
|
+
function generateYieldseekerUsername() {
|
|
3231
|
+
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3232
|
+
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
3233
|
+
}
|
|
3234
|
+
function isUsernameConflict(error) {
|
|
3235
|
+
if (!(error instanceof YieldseekerApiError)) return false;
|
|
3236
|
+
const code = error.providerCode.toUpperCase();
|
|
3237
|
+
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
3238
|
+
}
|
|
3239
|
+
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
3240
|
+
{
|
|
3241
|
+
type: "function",
|
|
3242
|
+
name: "withdrawAssetToUser",
|
|
3243
|
+
stateMutability: "nonpayable",
|
|
3244
|
+
inputs: [
|
|
3245
|
+
{ name: "recipient", type: "address" },
|
|
3246
|
+
{ name: "asset", type: "address" },
|
|
3247
|
+
{ name: "amount", type: "uint256" }
|
|
3248
|
+
],
|
|
3249
|
+
outputs: []
|
|
3250
|
+
},
|
|
3251
|
+
{
|
|
3252
|
+
type: "function",
|
|
3253
|
+
name: "withdrawAllAssetToUser",
|
|
3254
|
+
stateMutability: "nonpayable",
|
|
3255
|
+
inputs: [
|
|
3256
|
+
{ name: "recipient", type: "address" },
|
|
3257
|
+
{ name: "asset", type: "address" }
|
|
3258
|
+
],
|
|
3259
|
+
outputs: []
|
|
3260
|
+
}
|
|
3261
|
+
];
|
|
3262
|
+
function query(params) {
|
|
3263
|
+
const search = new URLSearchParams();
|
|
3264
|
+
for (const [key2, value] of Object.entries(params)) {
|
|
3265
|
+
if (value !== void 0) search.set(key2, String(value));
|
|
3266
|
+
}
|
|
3267
|
+
const encoded = search.toString();
|
|
3268
|
+
return encoded ? `?${encoded}` : "";
|
|
3269
|
+
}
|
|
3270
|
+
var YieldseekerAgent = class {
|
|
3271
|
+
id = "yieldseeker";
|
|
3272
|
+
balanceComposition = "tokens-plus-positions";
|
|
3273
|
+
supportedChainIds = [8453];
|
|
3274
|
+
supportedAssets = [
|
|
3275
|
+
{
|
|
3276
|
+
chainId: 8453,
|
|
3277
|
+
chain: "BASE",
|
|
3278
|
+
assets: [
|
|
3279
|
+
{ symbol: "USDC", minDepositAmount: "10000000" },
|
|
3280
|
+
{ symbol: "WETH", minDepositAmount: "1" }
|
|
3281
|
+
]
|
|
3282
|
+
}
|
|
3283
|
+
];
|
|
3284
|
+
api;
|
|
3285
|
+
auth;
|
|
3286
|
+
transactionExecutor;
|
|
3287
|
+
unwindReceiptWaiter;
|
|
3288
|
+
agentContexts = /* @__PURE__ */ new Map();
|
|
3289
|
+
users = /* @__PURE__ */ new Map();
|
|
3290
|
+
pendingAgents = /* @__PURE__ */ new Map();
|
|
3291
|
+
constructor(owneyApiKey, options = {}) {
|
|
3292
|
+
this.api = new YieldseekerApiClient(
|
|
3293
|
+
owneyApiKey,
|
|
3294
|
+
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
3295
|
+
options.fetchFn
|
|
3296
|
+
);
|
|
3297
|
+
this.auth = new YieldseekerAuth(options.auth);
|
|
3298
|
+
this.transactionExecutor = options.transactionExecutor;
|
|
3299
|
+
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3300
|
+
}
|
|
3301
|
+
async disconnect() {
|
|
3302
|
+
this.auth.clear();
|
|
3303
|
+
for (const key2 of this.users.keys()) {
|
|
3304
|
+
const [walletAddress, chainId] = key2.split(":");
|
|
3305
|
+
clearYieldseekerIdentity(walletAddress, Number(chainId));
|
|
3306
|
+
}
|
|
3307
|
+
this.users.clear();
|
|
3308
|
+
this.agentContexts.clear();
|
|
3309
|
+
this.pendingAgents.clear();
|
|
3310
|
+
}
|
|
3311
|
+
async activateAgent(state, chainId, asset) {
|
|
3312
|
+
this.assertChain(chainId);
|
|
3313
|
+
const targetAsset = asset ?? "USDC";
|
|
3314
|
+
this.assertAsset(targetAsset);
|
|
3315
|
+
await this.ensureAgent(state, chainId, targetAsset);
|
|
3316
|
+
}
|
|
3317
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3318
|
+
this.assertChain(chainId);
|
|
3319
|
+
this.assertAsset(asset);
|
|
3320
|
+
if (BigInt(amount) <= 0n) {
|
|
3321
|
+
throw new OwneyError(
|
|
3322
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
3323
|
+
"Yieldseeker deposits must be greater than zero.",
|
|
3324
|
+
{ amount, minDepositAmount: "1" },
|
|
3325
|
+
this.id
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
const context = await this.ensureAgent(state, chainId, asset);
|
|
3329
|
+
let txHash;
|
|
3330
|
+
try {
|
|
3331
|
+
if (depositCallback) {
|
|
3332
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3333
|
+
agentId: "yieldseeker",
|
|
3334
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3335
|
+
userId: context.user.userId,
|
|
3336
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3337
|
+
});
|
|
3338
|
+
txHash = await depositCallback(
|
|
3339
|
+
context.wallet.walletAddress,
|
|
3340
|
+
chainId,
|
|
3341
|
+
amount
|
|
3342
|
+
);
|
|
3343
|
+
await this.waitForReceipt(state, chainId, txHash);
|
|
3344
|
+
} else {
|
|
3345
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3346
|
+
from: getAddress2(state.walletAddress),
|
|
3347
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3348
|
+
data: encodeFunctionData({
|
|
3349
|
+
abi: erc20Abi,
|
|
3350
|
+
functionName: "transfer",
|
|
3351
|
+
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3352
|
+
}),
|
|
3353
|
+
value: "0",
|
|
3354
|
+
chainId
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
await this.deployAfterFunding(state, chainId, context);
|
|
3358
|
+
} finally {
|
|
3359
|
+
await this.refreshSnapshotAfterMovement(
|
|
3360
|
+
state,
|
|
3361
|
+
chainId,
|
|
3362
|
+
context,
|
|
3363
|
+
"deposit"
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
return {
|
|
3367
|
+
txHash,
|
|
3368
|
+
smartWallet: context.wallet.walletAddress,
|
|
3369
|
+
amount
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
async withdraw(state, chainId, asset, amount) {
|
|
3373
|
+
this.assertChain(chainId);
|
|
3374
|
+
this.assertAsset(asset);
|
|
3375
|
+
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
3376
|
+
throw new OwneyError(
|
|
3377
|
+
"WITHDRAW_FAILED",
|
|
3378
|
+
"Yieldseeker withdrawals must be greater than zero.",
|
|
3379
|
+
{ amount },
|
|
3380
|
+
this.id
|
|
3381
|
+
);
|
|
3382
|
+
}
|
|
3383
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
3384
|
+
if (!context) {
|
|
3385
|
+
throw new OwneyError(
|
|
3386
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3387
|
+
`No Yieldseeker ${asset} agent exists for this wallet.`,
|
|
3388
|
+
{ asset, available: "0" },
|
|
3389
|
+
this.id
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
try {
|
|
3393
|
+
const portfolio = await this.loadPortfolioContext(
|
|
3394
|
+
state,
|
|
3395
|
+
chainId,
|
|
3396
|
+
context
|
|
3397
|
+
);
|
|
3398
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3399
|
+
const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
|
|
3400
|
+
([address]) => address.toLowerCase() === metadata.address.toLowerCase()
|
|
3401
|
+
);
|
|
3402
|
+
const idle = BigInt(idleEntry?.[1] ?? "0");
|
|
3403
|
+
const deployed = portfolio.positions.reduce(
|
|
3404
|
+
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3405
|
+
0n
|
|
3406
|
+
);
|
|
3407
|
+
const totalAvailable = idle + deployed;
|
|
3408
|
+
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3409
|
+
if (requested > totalAvailable) {
|
|
3410
|
+
throw new OwneyError(
|
|
3411
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3412
|
+
`Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
|
|
3413
|
+
{
|
|
3414
|
+
asset,
|
|
3415
|
+
requested: requested.toString(),
|
|
3416
|
+
available: totalAvailable.toString()
|
|
3417
|
+
},
|
|
3418
|
+
this.id
|
|
3419
|
+
);
|
|
3420
|
+
}
|
|
3421
|
+
let remaining = requested > idle ? requested - idle : 0n;
|
|
3422
|
+
for (const position2 of portfolio.positions) {
|
|
3423
|
+
if (remaining === 0n) break;
|
|
3424
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
3425
|
+
if (available <= 0n) continue;
|
|
3426
|
+
const assetsRaw = available < remaining ? available : remaining;
|
|
3427
|
+
const response = await this.walletRequest(
|
|
3428
|
+
state,
|
|
3429
|
+
chainId,
|
|
3430
|
+
this.agentPath(context, "withdraw-from-position"),
|
|
3431
|
+
{
|
|
3432
|
+
method: "POST",
|
|
3433
|
+
body: {
|
|
3434
|
+
chainId,
|
|
3435
|
+
vaultAddress: position2.yieldOption.address,
|
|
3436
|
+
assetsRaw: assetsRaw.toString()
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
);
|
|
3440
|
+
if (!this.isTransactionHash(response?.transactionHash)) {
|
|
3441
|
+
throw this.invalidResponse("position withdrawal");
|
|
3442
|
+
}
|
|
3443
|
+
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3444
|
+
remaining -= assetsRaw;
|
|
3445
|
+
}
|
|
3446
|
+
if (remaining > 0n) {
|
|
3447
|
+
throw this.invalidResponse("yield positions", {
|
|
3448
|
+
reason: "Withdrawable positions could not cover the request.",
|
|
3449
|
+
remaining: remaining.toString()
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
const account = getAddress2(state.walletAddress);
|
|
3453
|
+
const txHash = await this.submitTransaction(state, chainId, {
|
|
3454
|
+
from: account,
|
|
3455
|
+
to: getAddress2(context.wallet.walletAddress),
|
|
3456
|
+
data: amount === void 0 ? encodeFunctionData({
|
|
3457
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3458
|
+
functionName: "withdrawAllAssetToUser",
|
|
3459
|
+
args: [account, metadata.address]
|
|
3460
|
+
}) : encodeFunctionData({
|
|
3461
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3462
|
+
functionName: "withdrawAssetToUser",
|
|
3463
|
+
args: [account, metadata.address, requested]
|
|
3464
|
+
}),
|
|
3465
|
+
value: "0",
|
|
3466
|
+
chainId
|
|
3467
|
+
});
|
|
3468
|
+
return {
|
|
3469
|
+
txHash,
|
|
3470
|
+
type: amount === void 0 ? "full" : "partial",
|
|
3471
|
+
amount: requested.toString()
|
|
3472
|
+
};
|
|
3473
|
+
} finally {
|
|
3474
|
+
await this.refreshSnapshotAfterMovement(
|
|
3475
|
+
state,
|
|
3476
|
+
chainId,
|
|
3477
|
+
context,
|
|
3478
|
+
"withdrawal"
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
async getBalances(state, chainId) {
|
|
3483
|
+
this.assertChain(chainId);
|
|
3484
|
+
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
|
|
3485
|
+
}
|
|
3486
|
+
async getEarnings(state, chainId) {
|
|
3487
|
+
this.assertChain(chainId);
|
|
3488
|
+
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
3489
|
+
}
|
|
3490
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
3491
|
+
this.assertChain(chainId);
|
|
3492
|
+
const asset = tokenSymbol?.toUpperCase();
|
|
3493
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
3494
|
+
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3495
|
+
...asset ? { asset } : {},
|
|
3496
|
+
historic: true
|
|
3497
|
+
});
|
|
3498
|
+
return mapYieldseekerApy(state.walletAddress, contexts, days);
|
|
3499
|
+
}
|
|
3500
|
+
async getHistory(state, chainId, options) {
|
|
3501
|
+
this.assertChain(chainId);
|
|
3502
|
+
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3503
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
3504
|
+
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3505
|
+
...asset ? { asset } : {},
|
|
3506
|
+
historic: true,
|
|
3507
|
+
actions: true
|
|
3508
|
+
});
|
|
3509
|
+
return mapYieldseekerHistory(contexts, {
|
|
3510
|
+
limit: options?.limit ?? 10,
|
|
3511
|
+
ownerAddress: state.walletAddress,
|
|
3512
|
+
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
3513
|
+
...options?.toDate ? { toDate: options.toDate } : {}
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
async getUserProfile(state, chainId) {
|
|
3517
|
+
this.assertChain(chainId);
|
|
3518
|
+
return mapYieldseekerProfile(
|
|
3519
|
+
state.walletAddress,
|
|
3520
|
+
await this.loadPortfolio(state, chainId, {})
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
async getAgentApy(days, options) {
|
|
3524
|
+
this.assertOptionalChain(options?.chainId);
|
|
3525
|
+
const requested = options?.tokenSymbol?.toUpperCase();
|
|
3526
|
+
if (requested !== void 0) this.assertAsset(requested);
|
|
3527
|
+
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
3528
|
+
const values = await Promise.all(
|
|
3529
|
+
assets.map(async (asset) => {
|
|
3530
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3531
|
+
const response = await this.api.request(
|
|
3532
|
+
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3533
|
+
);
|
|
3534
|
+
if (!Array.isArray(response?.yieldOptions)) {
|
|
3535
|
+
throw this.invalidResponse("yield options");
|
|
3536
|
+
}
|
|
3537
|
+
return { asset, yieldOptions: response.yieldOptions };
|
|
3538
|
+
})
|
|
3539
|
+
);
|
|
3540
|
+
return mapYieldseekerAgentApy(values, days);
|
|
3541
|
+
}
|
|
3542
|
+
userKey(state, chainId) {
|
|
3543
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
3544
|
+
}
|
|
3545
|
+
contextKey(state, chainId, asset) {
|
|
3546
|
+
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3547
|
+
}
|
|
3548
|
+
async resolveUser(state, chainId) {
|
|
3549
|
+
const key2 = this.userKey(state, chainId);
|
|
3550
|
+
const inMemory = this.users.get(key2);
|
|
3551
|
+
if (inMemory) return inMemory;
|
|
3552
|
+
const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
|
|
3553
|
+
if (persisted) {
|
|
3554
|
+
this.users.set(key2, persisted);
|
|
3555
|
+
return persisted;
|
|
3556
|
+
}
|
|
3557
|
+
const walletAddress = getAddress2(state.walletAddress);
|
|
3558
|
+
let user = null;
|
|
3559
|
+
try {
|
|
3560
|
+
const login = await this.providerRequest(
|
|
3561
|
+
state,
|
|
3562
|
+
chainId,
|
|
3563
|
+
"/users/login-with-wallet",
|
|
3564
|
+
{ method: "POST", body: { walletAddress } }
|
|
3565
|
+
);
|
|
3566
|
+
user = login?.user ?? null;
|
|
3567
|
+
if (!user) {
|
|
3568
|
+
throw this.invalidResponse("wallet login", {
|
|
3569
|
+
reason: "A successful login returned no user."
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
} catch (error) {
|
|
3573
|
+
if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
|
|
3574
|
+
if (error instanceof OwneyError) throw error;
|
|
3575
|
+
throw this.mapApiError(error);
|
|
3576
|
+
}
|
|
3577
|
+
let created;
|
|
3578
|
+
for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
|
|
3579
|
+
try {
|
|
3580
|
+
created = await this.providerRequest(
|
|
3581
|
+
state,
|
|
3582
|
+
chainId,
|
|
3583
|
+
"/users",
|
|
3584
|
+
{
|
|
3585
|
+
method: "POST",
|
|
3586
|
+
body: {
|
|
3587
|
+
walletAddress,
|
|
3588
|
+
username: generateYieldseekerUsername()
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
);
|
|
3592
|
+
break;
|
|
3593
|
+
} catch (createError) {
|
|
3594
|
+
const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
|
|
3595
|
+
if (canRetry) continue;
|
|
3596
|
+
throw this.mapApiError(createError);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
user = created?.user ?? null;
|
|
3600
|
+
}
|
|
3601
|
+
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3602
|
+
throw this.invalidResponse("wallet identity");
|
|
3603
|
+
}
|
|
3604
|
+
const resolved = { userId: user.userId };
|
|
3605
|
+
this.users.set(key2, resolved);
|
|
3606
|
+
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
3607
|
+
return resolved;
|
|
3608
|
+
}
|
|
3609
|
+
forgetUser(state, chainId) {
|
|
3610
|
+
this.users.delete(this.userKey(state, chainId));
|
|
3611
|
+
clearYieldseekerIdentity(state.walletAddress, chainId);
|
|
3612
|
+
}
|
|
3613
|
+
async ensureAgent(state, chainId, asset) {
|
|
3614
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3615
|
+
const cached = this.agentContexts.get(key2);
|
|
3616
|
+
if (cached) return cached;
|
|
3617
|
+
const pending = this.pendingAgents.get(key2);
|
|
3618
|
+
if (pending) return pending;
|
|
3619
|
+
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3620
|
+
(context) => {
|
|
3621
|
+
if (!context) throw this.invalidResponse("agent creation");
|
|
3622
|
+
this.agentContexts.set(key2, context);
|
|
3623
|
+
return context;
|
|
3624
|
+
}
|
|
3625
|
+
);
|
|
3626
|
+
this.pendingAgents.set(key2, request);
|
|
3627
|
+
try {
|
|
3628
|
+
return await request;
|
|
3629
|
+
} finally {
|
|
3630
|
+
this.pendingAgents.delete(key2);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
async findAgent(state, chainId, asset) {
|
|
3634
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3635
|
+
const cached = this.agentContexts.get(key2);
|
|
3636
|
+
if (cached) return cached;
|
|
3637
|
+
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3638
|
+
if (context) this.agentContexts.set(key2, context);
|
|
3639
|
+
return context;
|
|
3640
|
+
}
|
|
3641
|
+
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3642
|
+
const user = await this.resolveUser(state, chainId);
|
|
3643
|
+
const response = await this.walletRequest(
|
|
3644
|
+
state,
|
|
3645
|
+
chainId,
|
|
3646
|
+
`/users/${user.userId}/agents`
|
|
3647
|
+
);
|
|
3648
|
+
if (!Array.isArray(response?.agents)) {
|
|
3649
|
+
throw this.invalidResponse("agent list");
|
|
3650
|
+
}
|
|
3651
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3652
|
+
let agent = response.agents.find(
|
|
3653
|
+
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3654
|
+
);
|
|
3655
|
+
if (!agent && createIfMissing) {
|
|
3656
|
+
const created = await this.walletRequest(
|
|
3657
|
+
state,
|
|
3658
|
+
chainId,
|
|
3659
|
+
`/users/${user.userId}/agents`,
|
|
3660
|
+
{
|
|
3661
|
+
method: "POST",
|
|
3662
|
+
body: {
|
|
3663
|
+
name: OWNEY_AGENT_NAME,
|
|
3664
|
+
emoji: "\u{1F989}",
|
|
3665
|
+
chainId,
|
|
3666
|
+
assetAddress: metadata.address,
|
|
3667
|
+
type: "vault",
|
|
3668
|
+
rulePreset: null
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
);
|
|
3672
|
+
agent = created?.agent;
|
|
3673
|
+
}
|
|
3674
|
+
if (!agent) return null;
|
|
3675
|
+
this.assertAgent(agent);
|
|
3676
|
+
const walletResponse = await this.walletRequest(
|
|
3677
|
+
state,
|
|
3678
|
+
chainId,
|
|
3679
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3680
|
+
);
|
|
3681
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3682
|
+
throw this.invalidResponse("agent wallet");
|
|
3683
|
+
}
|
|
3684
|
+
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
3685
|
+
}
|
|
3686
|
+
async loadPortfolio(state, chainId, options) {
|
|
3687
|
+
const user = await this.resolveUser(state, chainId);
|
|
3688
|
+
const response = await this.walletRequest(
|
|
3689
|
+
state,
|
|
3690
|
+
chainId,
|
|
3691
|
+
`/users/${user.userId}/agents`
|
|
3692
|
+
);
|
|
3693
|
+
if (!Array.isArray(response?.agents)) {
|
|
3694
|
+
throw this.invalidResponse("agent list");
|
|
3695
|
+
}
|
|
3696
|
+
const contexts = [];
|
|
3697
|
+
for (const agent of response.agents) {
|
|
3698
|
+
const asset = this.assetForAgent(agent);
|
|
3699
|
+
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
|
|
3700
|
+
continue;
|
|
3701
|
+
}
|
|
3702
|
+
this.assertAgent(agent);
|
|
3703
|
+
const walletResponse = await this.walletRequest(
|
|
3704
|
+
state,
|
|
3705
|
+
chainId,
|
|
3706
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3707
|
+
);
|
|
3708
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3709
|
+
throw this.invalidResponse("agent wallet");
|
|
3710
|
+
}
|
|
3711
|
+
const context = {
|
|
3712
|
+
user,
|
|
3713
|
+
agent,
|
|
3714
|
+
wallet: walletResponse.agentWallet,
|
|
3715
|
+
asset
|
|
3716
|
+
};
|
|
3717
|
+
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3718
|
+
contexts.push(context);
|
|
3719
|
+
}
|
|
3720
|
+
return Promise.all(
|
|
3721
|
+
contexts.map(
|
|
3722
|
+
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3723
|
+
)
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3727
|
+
const [snapshot, positions, historic, actions] = await Promise.all([
|
|
3728
|
+
this.walletRequest(
|
|
3729
|
+
state,
|
|
3730
|
+
chainId,
|
|
3731
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3732
|
+
shouldOnlyUseRecentValue: true,
|
|
3733
|
+
shouldAllowStaleOnError: true
|
|
3734
|
+
})}`
|
|
3735
|
+
),
|
|
3736
|
+
this.walletRequest(
|
|
3737
|
+
state,
|
|
3738
|
+
chainId,
|
|
3739
|
+
this.agentPath(context, "yield-positions")
|
|
3740
|
+
),
|
|
3741
|
+
options.historic ? this.walletRequest(
|
|
3742
|
+
state,
|
|
3743
|
+
chainId,
|
|
3744
|
+
this.agentPath(context, "wallet/historic-position")
|
|
3745
|
+
) : Promise.resolve(void 0),
|
|
3746
|
+
options.actions ? this.walletRequest(
|
|
3747
|
+
state,
|
|
3748
|
+
chainId,
|
|
3749
|
+
this.agentPath(context, "actions")
|
|
3750
|
+
) : Promise.resolve(void 0)
|
|
3751
|
+
]);
|
|
3752
|
+
if (!snapshot?.agentSnapshot) {
|
|
3753
|
+
throw this.invalidResponse("agent snapshot");
|
|
3754
|
+
}
|
|
3755
|
+
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3756
|
+
throw this.invalidResponse("yield positions");
|
|
3757
|
+
}
|
|
3758
|
+
return {
|
|
3759
|
+
...context,
|
|
3760
|
+
snapshot: snapshot.agentSnapshot,
|
|
3761
|
+
positions: positions.yieldPositions,
|
|
3762
|
+
...historic?.position ? { historic: historic.position } : {},
|
|
3763
|
+
...actions?.actions ? { actions: actions.actions } : {}
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
async deployAfterFunding(state, chainId, context) {
|
|
3767
|
+
if (context.wallet.initializedDate != null) return;
|
|
3768
|
+
try {
|
|
3769
|
+
const deployed = await this.walletRequest(
|
|
3770
|
+
state,
|
|
3771
|
+
chainId,
|
|
3772
|
+
this.agentPath(context, "deploy"),
|
|
3773
|
+
{ method: "POST", body: {} }
|
|
3774
|
+
);
|
|
3775
|
+
if (deployed?.agentWallet) {
|
|
3776
|
+
context.wallet = deployed.agentWallet;
|
|
3777
|
+
}
|
|
3778
|
+
} catch (error) {
|
|
3779
|
+
console.warn(
|
|
3780
|
+
"[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
|
|
3781
|
+
error
|
|
3782
|
+
);
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
3786
|
+
try {
|
|
3787
|
+
const response = await this.walletRequest(
|
|
3788
|
+
state,
|
|
3789
|
+
chainId,
|
|
3790
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3791
|
+
shouldForceRefresh: true
|
|
3792
|
+
})}`
|
|
3793
|
+
);
|
|
3794
|
+
if (!response?.agentSnapshot) {
|
|
3795
|
+
throw this.invalidResponse("agent snapshot refresh");
|
|
3796
|
+
}
|
|
3797
|
+
} catch (error) {
|
|
3798
|
+
console.warn(
|
|
3799
|
+
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
3800
|
+
error
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
agentPath(context, suffix) {
|
|
3805
|
+
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3806
|
+
}
|
|
3807
|
+
async walletRequest(state, chainId, path, options = {}) {
|
|
3808
|
+
try {
|
|
3809
|
+
return await this.providerRequest(state, chainId, path, options);
|
|
3810
|
+
} catch (error) {
|
|
3811
|
+
throw this.mapApiError(error);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
async providerRequest(state, chainId, path, options = {}) {
|
|
3815
|
+
this.assertChain(chainId);
|
|
3816
|
+
const request = (signature2) => this.api.request(path, {
|
|
3817
|
+
...options,
|
|
3818
|
+
signature: signature2
|
|
3819
|
+
});
|
|
3820
|
+
let signature = await this.auth.getToken(state, chainId);
|
|
3821
|
+
try {
|
|
3822
|
+
return await request(signature);
|
|
3823
|
+
} catch (error) {
|
|
3824
|
+
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
3825
|
+
if (error.providerCode === "NO_USER") throw error;
|
|
3826
|
+
if (!error.isAuthenticationError) throw error;
|
|
3827
|
+
this.auth.clear(state, chainId);
|
|
3828
|
+
signature = await this.auth.getToken(state, chainId);
|
|
3829
|
+
try {
|
|
3830
|
+
return await request(signature);
|
|
3831
|
+
} catch (retryError) {
|
|
3832
|
+
if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
|
|
3833
|
+
this.forgetUser(state, chainId);
|
|
3834
|
+
}
|
|
3835
|
+
throw retryError;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
mapApiError(error) {
|
|
3840
|
+
if (!(error instanceof YieldseekerApiError)) {
|
|
3841
|
+
return new OwneyError(
|
|
3842
|
+
"AGENT_API_ERROR",
|
|
3843
|
+
"Yieldseeker request failed.",
|
|
3844
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
3845
|
+
this.id
|
|
3846
|
+
);
|
|
3847
|
+
}
|
|
3848
|
+
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
3849
|
+
return new OwneyError(
|
|
3850
|
+
code,
|
|
3851
|
+
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3852
|
+
{
|
|
3853
|
+
statusCode: error.status,
|
|
3854
|
+
providerCode: error.providerCode,
|
|
3855
|
+
...error.responseFields ? { fields: error.responseFields } : {}
|
|
3856
|
+
},
|
|
3857
|
+
this.id
|
|
3858
|
+
);
|
|
3859
|
+
}
|
|
3860
|
+
async submitTransaction(state, chainId, transaction) {
|
|
3861
|
+
if (this.transactionExecutor) {
|
|
3862
|
+
return this.transactionExecutor(state, chainId, transaction);
|
|
3863
|
+
}
|
|
3864
|
+
this.assertTransaction(transaction, state, chainId);
|
|
3865
|
+
const account = getAddress2(state.walletAddress);
|
|
3866
|
+
const walletClient = createWalletClient2({
|
|
3867
|
+
account,
|
|
3868
|
+
chain: base3,
|
|
3869
|
+
transport: custom2(state.provider)
|
|
3870
|
+
});
|
|
3871
|
+
const publicClient = createPublicClient3({
|
|
3872
|
+
chain: base3,
|
|
3873
|
+
transport: custom2(state.provider)
|
|
3874
|
+
});
|
|
3875
|
+
await ensureWalletOnChain(
|
|
3876
|
+
publicClient,
|
|
3877
|
+
walletClient,
|
|
3878
|
+
8453
|
|
3879
|
+
);
|
|
3880
|
+
const hash = await walletClient.sendTransaction({
|
|
3881
|
+
account,
|
|
3882
|
+
chain: base3,
|
|
3883
|
+
to: getAddress2(transaction.to),
|
|
3884
|
+
data: transaction.data,
|
|
3885
|
+
value: BigInt(transaction.value)
|
|
3886
|
+
});
|
|
3887
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3888
|
+
hash,
|
|
3889
|
+
confirmations: 1
|
|
3890
|
+
});
|
|
3891
|
+
if (receipt.status !== "success") {
|
|
3892
|
+
throw new OwneyError(
|
|
3893
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3894
|
+
`Yieldseeker transaction reverted (${hash}).`,
|
|
3895
|
+
{ transactionHash: hash },
|
|
3896
|
+
this.id
|
|
3897
|
+
);
|
|
3898
|
+
}
|
|
3899
|
+
return hash;
|
|
3900
|
+
}
|
|
3901
|
+
async waitForReceipt(state, chainId, transactionHash) {
|
|
3902
|
+
if (this.unwindReceiptWaiter) {
|
|
3903
|
+
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
const publicClient = createPublicClient3({
|
|
3907
|
+
chain: base3,
|
|
3908
|
+
transport: custom2(state.provider)
|
|
3909
|
+
});
|
|
3910
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3911
|
+
hash: transactionHash,
|
|
3912
|
+
confirmations: 1
|
|
3913
|
+
});
|
|
3914
|
+
if (receipt.status !== "success") {
|
|
3915
|
+
throw new OwneyError(
|
|
3916
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3917
|
+
`Yieldseeker transaction reverted (${transactionHash}).`,
|
|
3918
|
+
{ transactionHash },
|
|
3919
|
+
this.id
|
|
3920
|
+
);
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
assertTransaction(transaction, state, chainId) {
|
|
3924
|
+
if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(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 || getAddress2(transaction.from) !== getAddress2(state.walletAddress)) {
|
|
3925
|
+
throw this.invalidResponse("transaction");
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
assertAgent(agent) {
|
|
3929
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
|
|
3930
|
+
throw this.invalidResponse("agent");
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
isOwneyAgent(agent) {
|
|
3934
|
+
return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
|
|
3935
|
+
}
|
|
3936
|
+
assetForAgent(agent) {
|
|
3937
|
+
for (const asset of ["USDC", "WETH"]) {
|
|
3938
|
+
if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
|
|
3939
|
+
return asset;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
return null;
|
|
3943
|
+
}
|
|
3944
|
+
isTransactionHash(value) {
|
|
3945
|
+
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3946
|
+
}
|
|
3947
|
+
assertChain(chainId) {
|
|
3948
|
+
if (chainId !== 8453) {
|
|
3949
|
+
throw new OwneyError(
|
|
3950
|
+
"CHAIN_UNSUPPORTED",
|
|
3951
|
+
`Yieldseeker does not support chain ${chainId}.`,
|
|
3952
|
+
{ chainId, supportedChainIds: [8453] },
|
|
3953
|
+
this.id
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
assertOptionalChain(chainId) {
|
|
3958
|
+
if (chainId !== void 0) this.assertChain(chainId);
|
|
3959
|
+
}
|
|
3960
|
+
assertAsset(asset) {
|
|
3961
|
+
if (asset !== "USDC" && asset !== "WETH") {
|
|
3962
|
+
throw new OwneyError(
|
|
3963
|
+
"ASSET_UNSUPPORTED",
|
|
3964
|
+
`Yieldseeker does not support asset ${asset} in the Owney rollout.`,
|
|
3965
|
+
{
|
|
3966
|
+
asset,
|
|
3967
|
+
supportedAssets: ["USDC", "WETH"],
|
|
3968
|
+
providerAlsoAdvertises: ["cbBTC"]
|
|
3969
|
+
},
|
|
3970
|
+
this.id
|
|
3971
|
+
);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
invalidResponse(operation, details = {}) {
|
|
3975
|
+
return new OwneyError(
|
|
3976
|
+
"AGENT_INVALID_RESPONSE",
|
|
3977
|
+
`Yieldseeker returned an invalid ${operation} response.`,
|
|
3978
|
+
details,
|
|
3979
|
+
this.id
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
};
|
|
3983
|
+
|
|
3984
|
+
// src/lib/routing-api.ts
|
|
3985
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3986
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3987
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
3988
|
+
try {
|
|
3989
|
+
const res = await fetch(url, {
|
|
3990
|
+
method: "GET",
|
|
3991
|
+
headers: {
|
|
3992
|
+
"Content-Type": "application/json",
|
|
3993
|
+
"x-owney-api-key": `${apiKey}`
|
|
3994
|
+
}
|
|
3995
|
+
});
|
|
3996
|
+
if (!res.ok) {
|
|
3997
|
+
if (res.status !== 404) {
|
|
3998
|
+
console.warn(
|
|
3999
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
4000
|
+
);
|
|
4001
|
+
}
|
|
4002
|
+
return null;
|
|
4003
|
+
}
|
|
4004
|
+
const json = await res.json();
|
|
4005
|
+
const policy = json.success ? json.data ?? null : null;
|
|
4006
|
+
debugLog(
|
|
4007
|
+
"owney-sdk",
|
|
4008
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
4009
|
+
policy ?? void 0
|
|
4010
|
+
);
|
|
4011
|
+
return policy;
|
|
4012
|
+
} catch (error) {
|
|
4013
|
+
console.warn(
|
|
4014
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
4015
|
+
error instanceof Error ? error.message : String(error)
|
|
4016
|
+
);
|
|
4017
|
+
return null;
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4021
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
4022
|
+
const res = await fetch(url, {
|
|
4023
|
+
method: "GET",
|
|
4024
|
+
headers: {
|
|
4025
|
+
"Content-Type": "application/json",
|
|
4026
|
+
"x-owney-api-key": `${apiKey}`
|
|
4027
|
+
}
|
|
4028
|
+
});
|
|
4029
|
+
if (!res.ok) {
|
|
4030
|
+
const text = await res.text().catch(() => "");
|
|
4031
|
+
throw new OwneyError(
|
|
4032
|
+
"API_ROUTING_ERROR",
|
|
4033
|
+
`Routing API error ${res.status}: ${text}`,
|
|
4034
|
+
{ statusCode: res.status, responseBody: text }
|
|
4035
|
+
);
|
|
4036
|
+
}
|
|
4037
|
+
const json = await res.json();
|
|
4038
|
+
if (!json.success) {
|
|
4039
|
+
throw new OwneyError(
|
|
4040
|
+
"API_ROUTING_FAILED",
|
|
4041
|
+
`Routing API request failed: ${json.message}`,
|
|
4042
|
+
{ message: json.message }
|
|
4043
|
+
);
|
|
4044
|
+
}
|
|
4045
|
+
return json.data;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
// src/lib/health-report.ts
|
|
4049
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
4050
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
|
|
4051
|
+
try {
|
|
4052
|
+
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
4053
|
+
method: "POST",
|
|
4054
|
+
headers: {
|
|
4055
|
+
"Content-Type": "application/json",
|
|
4056
|
+
"x-owney-api-key": apiKey
|
|
4057
|
+
},
|
|
4058
|
+
body: JSON.stringify({
|
|
4059
|
+
agent_type: agentType,
|
|
4060
|
+
error_code: errorCode,
|
|
4061
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4062
|
+
})
|
|
4063
|
+
});
|
|
4064
|
+
} catch (err) {
|
|
4065
|
+
console.warn(
|
|
4066
|
+
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
4067
|
+
err instanceof Error ? err.message : err
|
|
4068
|
+
);
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
4072
|
+
try {
|
|
4073
|
+
return await fn();
|
|
4074
|
+
} catch (err) {
|
|
4075
|
+
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
4076
|
+
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
4077
|
+
throw err;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
4082
|
+
import { parseUnits } from "viem";
|
|
4083
|
+
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
4084
|
+
const target = asset.toUpperCase();
|
|
4085
|
+
return agents.map((agent) => {
|
|
4086
|
+
const agentBalance = aggregated[agent.id];
|
|
4087
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
4088
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4089
|
+
);
|
|
4090
|
+
let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
|
|
4091
|
+
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
4092
|
+
const chainNameById = {
|
|
4093
|
+
1: "ETHEREUM",
|
|
4094
|
+
8453: "BASE",
|
|
4095
|
+
42161: "ARBITRUM"
|
|
4096
|
+
};
|
|
4097
|
+
const targetChain = chainNameById[chainId];
|
|
4098
|
+
for (const position2 of agentBalance?.positions ?? []) {
|
|
4099
|
+
const positionChain = position2.chain.trim().toUpperCase();
|
|
4100
|
+
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4101
|
+
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4102
|
+
if (position2.amountRaw !== void 0) {
|
|
4103
|
+
try {
|
|
4104
|
+
balance += BigInt(position2.amountRaw);
|
|
4105
|
+
continue;
|
|
4106
|
+
} catch {
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
balance += parseUnits(position2.amount, decimals);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
return { agent, balance };
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
4116
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
4117
|
+
agent,
|
|
4118
|
+
balance,
|
|
4119
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
4120
|
+
}));
|
|
4121
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
4122
|
+
let remainder = requested - assigned;
|
|
4123
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
4124
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
4125
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
4126
|
+
});
|
|
4127
|
+
for (const p of byHeadroom) {
|
|
4128
|
+
if (remainder === 0n) break;
|
|
4129
|
+
const headroom = p.balance - p.planned;
|
|
4130
|
+
if (headroom <= 0n) continue;
|
|
4131
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
4132
|
+
p.planned += take;
|
|
4133
|
+
remainder -= take;
|
|
4134
|
+
}
|
|
4135
|
+
return plans;
|
|
4136
|
+
}
|
|
4137
|
+
function planDisabledDrain(disabled, requested) {
|
|
4138
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
4139
|
+
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
4140
|
+
);
|
|
4141
|
+
const plans = [];
|
|
4142
|
+
let remaining = requested;
|
|
4143
|
+
for (const { agent, balance } of sorted) {
|
|
4144
|
+
if (remaining === 0n) {
|
|
4145
|
+
plans.push({ agent, balance, planned: 0n });
|
|
4146
|
+
continue;
|
|
4147
|
+
}
|
|
4148
|
+
const take = balance < remaining ? balance : remaining;
|
|
4149
|
+
plans.push({ agent, balance, planned: take });
|
|
4150
|
+
remaining -= take;
|
|
4151
|
+
}
|
|
4152
|
+
return { plans, remaining };
|
|
4153
|
+
}
|
|
4154
|
+
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
4155
|
+
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
4156
|
+
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
4157
|
+
const totalHeadroom = candidates.reduce(
|
|
4158
|
+
(s, c) => s + (c.balance - c.planned),
|
|
4159
|
+
0n
|
|
4160
|
+
);
|
|
4161
|
+
if (totalHeadroom === 0n) return;
|
|
4162
|
+
let distributed = 0n;
|
|
4163
|
+
for (const c of candidates) {
|
|
4164
|
+
const headroom = c.balance - c.planned;
|
|
4165
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
4166
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
4167
|
+
c.planned += give;
|
|
4168
|
+
distributed += give;
|
|
4169
|
+
}
|
|
4170
|
+
let leftover = amount - distributed;
|
|
4171
|
+
for (const c of candidates) {
|
|
4172
|
+
if (leftover === 0n) break;
|
|
4173
|
+
const headroom = c.balance - c.planned;
|
|
4174
|
+
if (headroom <= 0n) continue;
|
|
4175
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
4176
|
+
c.planned += take;
|
|
4177
|
+
leftover -= take;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
function sumWithdrawnAmount(results) {
|
|
4181
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
2516
4182
|
}
|
|
2517
4183
|
|
|
2518
|
-
// src/lib/
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
};
|
|
2524
|
-
function chainName(chainId) {
|
|
2525
|
-
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2526
|
-
}
|
|
2527
|
-
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2528
|
-
const actual = await pub.getChainId();
|
|
2529
|
-
if (actual === expected) return;
|
|
2530
|
-
try {
|
|
2531
|
-
await wallet.switchChain({ id: expected });
|
|
2532
|
-
} catch (error) {
|
|
2533
|
-
throw new OwneyError(
|
|
2534
|
-
"CHAIN_MISMATCH",
|
|
2535
|
-
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2536
|
-
{
|
|
2537
|
-
expectedChainId: expected,
|
|
2538
|
-
actualChainId: actual,
|
|
2539
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2540
|
-
}
|
|
2541
|
-
);
|
|
4184
|
+
// src/lib/helpers/account-apy-helper.ts
|
|
4185
|
+
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
4186
|
+
if (!tokenSymbol) {
|
|
4187
|
+
const total = Number(balance.totalBalance);
|
|
4188
|
+
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
2542
4189
|
}
|
|
2543
|
-
const
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
);
|
|
4190
|
+
const normalizedToken = tokenSymbol.toUpperCase();
|
|
4191
|
+
const snapshots = balance.assetBalances?.filter(
|
|
4192
|
+
(token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
|
|
4193
|
+
);
|
|
4194
|
+
if (snapshots?.length) {
|
|
4195
|
+
const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
|
|
4196
|
+
if (Number.isFinite(amount)) return Math.max(0, amount);
|
|
2550
4197
|
}
|
|
4198
|
+
return balance.tokens.reduce((total, token) => {
|
|
4199
|
+
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
4200
|
+
return total;
|
|
4201
|
+
}
|
|
4202
|
+
const amount = Number(token.amount);
|
|
4203
|
+
return Number.isFinite(amount) && amount > 0 ? total + amount : total;
|
|
4204
|
+
}, 0);
|
|
2551
4205
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
"CHAIN_UNSUPPORTED",
|
|
2563
|
-
`No sponsored token configured for chain ${chainId}`
|
|
2564
|
-
);
|
|
4206
|
+
function aggregateApyHistory(agentApys) {
|
|
4207
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
4208
|
+
for (const accountApy of Object.values(agentApys)) {
|
|
4209
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4210
|
+
for (const point of accountApy.history ?? []) {
|
|
4211
|
+
if (!point.date || seen.has(point.date) || !Number.isFinite(point.apy)) continue;
|
|
4212
|
+
seen.add(point.date);
|
|
4213
|
+
const points = byDate.get(point.date) ?? [];
|
|
4214
|
+
points.push(point);
|
|
4215
|
+
byDate.set(point.date, points);
|
|
2565
4216
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2571
|
-
if (balance < BigInt(amount)) {
|
|
2572
|
-
throw new OwneyError(
|
|
2573
|
-
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2574
|
-
"Insufficient balance for this deposit.",
|
|
2575
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2576
|
-
);
|
|
2577
|
-
}
|
|
2578
|
-
} catch (err) {
|
|
2579
|
-
if (err instanceof OwneyError) throw err;
|
|
2580
|
-
console.warn(
|
|
2581
|
-
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2582
|
-
err instanceof Error ? err.message : String(err)
|
|
2583
|
-
);
|
|
4217
|
+
}
|
|
4218
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([date, points]) => {
|
|
4219
|
+
if (points.length === 1 && !points[0].historicalBalance) {
|
|
4220
|
+
return [{ date, apy: points[0].apy }];
|
|
2584
4221
|
}
|
|
2585
|
-
const
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
);
|
|
2590
|
-
|
|
2591
|
-
const
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
apiKey: deps.apiKey,
|
|
2613
|
-
body: {
|
|
2614
|
-
chainId: cid,
|
|
2615
|
-
token,
|
|
2616
|
-
from: deps.ownerAddress,
|
|
2617
|
-
to: smartWallet,
|
|
2618
|
-
value: amount,
|
|
2619
|
-
validAfter: validAfter.toString(),
|
|
2620
|
-
validBefore: validBefore.toString(),
|
|
2621
|
-
nonce,
|
|
2622
|
-
authSignature,
|
|
2623
|
-
tokenName,
|
|
2624
|
-
tokenVersion
|
|
4222
|
+
const unit = points[0].historicalBalance?.unit;
|
|
4223
|
+
if (!unit || points.some(
|
|
4224
|
+
({ historicalBalance: balance }) => !balance || balance.unit !== unit || !Number.isFinite(balance.amount) || balance.amount < 0
|
|
4225
|
+
)) return [];
|
|
4226
|
+
const total = points.reduce((sum, p) => sum + p.historicalBalance.amount, 0);
|
|
4227
|
+
if (total <= 0 || !Number.isFinite(total)) return [];
|
|
4228
|
+
const apy = points.reduce((sum, p) => sum + p.apy * (p.historicalBalance.amount / total), 0);
|
|
4229
|
+
return Number.isFinite(apy) ? [{ date, apy }] : [];
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
4233
|
+
const sums = {};
|
|
4234
|
+
const weights = {};
|
|
4235
|
+
for (const id of Object.keys(agentApys)) {
|
|
4236
|
+
const cells = agentApys[id].apyByChainAndAsset;
|
|
4237
|
+
const balance = agentBalances[id] ?? 0;
|
|
4238
|
+
if (!cells || balance <= 0) continue;
|
|
4239
|
+
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
4240
|
+
if (!perAsset) continue;
|
|
4241
|
+
const chainId = Number(chainKey);
|
|
4242
|
+
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
4243
|
+
const apy = Number(apyValue ?? 0);
|
|
4244
|
+
if (apy === 0) continue;
|
|
4245
|
+
sums[chainId] ??= {};
|
|
4246
|
+
weights[chainId] ??= {};
|
|
4247
|
+
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
4248
|
+
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2625
4249
|
}
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
|
-
};
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
const out = {};
|
|
4253
|
+
for (const chainKey of Object.keys(sums)) {
|
|
4254
|
+
const chainId = Number(chainKey);
|
|
4255
|
+
const perAssetOut = {};
|
|
4256
|
+
for (const asset of Object.keys(sums[chainId])) {
|
|
4257
|
+
const w = weights[chainId][asset];
|
|
4258
|
+
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
4259
|
+
}
|
|
4260
|
+
if (Object.keys(perAssetOut).length > 0) {
|
|
4261
|
+
out[chainId] = perAssetOut;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
return out;
|
|
2629
4265
|
}
|
|
2630
4266
|
|
|
4267
|
+
// src/client.ts
|
|
4268
|
+
import {
|
|
4269
|
+
createPublicClient as createPublicClient4,
|
|
4270
|
+
createWalletClient as createWalletClient3,
|
|
4271
|
+
custom as custom3
|
|
4272
|
+
} from "viem";
|
|
4273
|
+
import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
4274
|
+
|
|
2631
4275
|
// src/lib/sponsored-weth-deposit.ts
|
|
2632
4276
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2633
4277
|
function makeSponsoredWethCallback(deps) {
|
|
2634
4278
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2635
4279
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2636
|
-
return
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
"CHAIN_UNSUPPORTED",
|
|
2642
|
-
`No sponsored WETH configured for chain ${chainId}`
|
|
2643
|
-
);
|
|
2644
|
-
}
|
|
2645
|
-
const amountWei = BigInt(amount);
|
|
2646
|
-
const pub = deps.getPublicClient(cid);
|
|
2647
|
-
const wallet = deps.getWalletClient(cid);
|
|
2648
|
-
await ensureWalletOnChain(pub, wallet, cid);
|
|
2649
|
-
try {
|
|
2650
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2651
|
-
if (balance < amountWei) {
|
|
4280
|
+
return makeVerificationAwareDepositCallback(
|
|
4281
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
4282
|
+
const cid = chainId;
|
|
4283
|
+
const token = deps.tokenAddressByChain[cid];
|
|
4284
|
+
if (!token) {
|
|
2652
4285
|
throw new OwneyError(
|
|
2653
|
-
"
|
|
2654
|
-
|
|
2655
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
4286
|
+
"CHAIN_UNSUPPORTED",
|
|
4287
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2656
4288
|
);
|
|
2657
4289
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
)
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
});
|
|
2678
|
-
const nonce = randomPermit2Nonce();
|
|
2679
|
-
const deadline = BigInt(
|
|
2680
|
-
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
2681
|
-
);
|
|
2682
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
2683
|
-
chainId: cid,
|
|
2684
|
-
message: {
|
|
2685
|
-
permitted: { token, amount: amountWei },
|
|
2686
|
-
spender: relayer,
|
|
2687
|
-
nonce,
|
|
2688
|
-
deadline
|
|
4290
|
+
const amountWei = BigInt(amount);
|
|
4291
|
+
const pub = deps.getPublicClient(cid);
|
|
4292
|
+
const wallet = deps.getWalletClient(cid);
|
|
4293
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
4294
|
+
try {
|
|
4295
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
4296
|
+
if (balance < amountWei) {
|
|
4297
|
+
throw new OwneyError(
|
|
4298
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4299
|
+
"Insufficient WETH balance for this deposit.",
|
|
4300
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
4301
|
+
);
|
|
4302
|
+
}
|
|
4303
|
+
} catch (err) {
|
|
4304
|
+
if (err instanceof OwneyError) throw err;
|
|
4305
|
+
console.warn(
|
|
4306
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
4307
|
+
err instanceof Error ? err.message : String(err)
|
|
4308
|
+
);
|
|
2689
4309
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
account: deps.ownerAddress,
|
|
2693
|
-
...typedData
|
|
2694
|
-
});
|
|
2695
|
-
deps.onApproved?.();
|
|
2696
|
-
const result = await post({
|
|
2697
|
-
baseUrl: deps.baseUrl,
|
|
2698
|
-
apiKey: deps.apiKey,
|
|
2699
|
-
body: {
|
|
2700
|
-
chainId: cid,
|
|
4310
|
+
const allowance = await readPermit2Allowance(
|
|
4311
|
+
pub,
|
|
2701
4312
|
token,
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
4313
|
+
deps.ownerAddress
|
|
4314
|
+
);
|
|
4315
|
+
if (allowance < amountWei) {
|
|
4316
|
+
throw new OwneyError(
|
|
4317
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
4318
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
4319
|
+
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
4320
|
+
);
|
|
2708
4321
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
4322
|
+
const relayer = await get({
|
|
4323
|
+
baseUrl: deps.baseUrl,
|
|
4324
|
+
apiKey: deps.apiKey,
|
|
4325
|
+
chainId: cid
|
|
4326
|
+
});
|
|
4327
|
+
const nonce = randomPermit2Nonce();
|
|
4328
|
+
const deadline = BigInt(
|
|
4329
|
+
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
4330
|
+
);
|
|
4331
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
4332
|
+
chainId: cid,
|
|
4333
|
+
message: {
|
|
4334
|
+
permitted: { token, amount: amountWei },
|
|
4335
|
+
spender: relayer,
|
|
4336
|
+
nonce,
|
|
4337
|
+
deadline
|
|
4338
|
+
}
|
|
4339
|
+
});
|
|
4340
|
+
const signature = await wallet.signTypedData({
|
|
4341
|
+
account: deps.ownerAddress,
|
|
4342
|
+
...typedData
|
|
4343
|
+
});
|
|
4344
|
+
deps.onApproved?.();
|
|
4345
|
+
const result = await post({
|
|
4346
|
+
baseUrl: deps.baseUrl,
|
|
4347
|
+
apiKey: deps.apiKey,
|
|
4348
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
4349
|
+
body: {
|
|
4350
|
+
chainId: cid,
|
|
4351
|
+
token,
|
|
4352
|
+
from: deps.ownerAddress,
|
|
4353
|
+
to: smartWallet,
|
|
4354
|
+
amount,
|
|
4355
|
+
nonce: nonce.toString(),
|
|
4356
|
+
deadline: deadline.toString(),
|
|
4357
|
+
signature,
|
|
4358
|
+
...verification?.agentId === "yieldseeker" ? {
|
|
4359
|
+
yieldseekerUserId: verification.userId,
|
|
4360
|
+
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
4361
|
+
} : {}
|
|
4362
|
+
}
|
|
4363
|
+
});
|
|
4364
|
+
return result.txHash;
|
|
4365
|
+
}
|
|
4366
|
+
);
|
|
2712
4367
|
}
|
|
2713
4368
|
|
|
2714
4369
|
// src/lib/sponsored-calls-deposit.ts
|
|
2715
|
-
import { encodeFunctionData, erc20Abi, toHex } from "viem";
|
|
4370
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
|
|
2716
4371
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
2717
4372
|
var DEFAULT_MAX_POLLS = 30;
|
|
2718
4373
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -2738,7 +4393,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2738
4393
|
}
|
|
2739
4394
|
return new URL(configured, origin).toString();
|
|
2740
4395
|
};
|
|
2741
|
-
return async (smartWallet, chainId, amount) => {
|
|
4396
|
+
return makeVerificationAwareDepositCallback(async (smartWallet, chainId, amount, verification) => {
|
|
2742
4397
|
const cid = chainId;
|
|
2743
4398
|
const token = deps.tokenAddressByChain[cid];
|
|
2744
4399
|
if (!token) {
|
|
@@ -2754,11 +4409,37 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2754
4409
|
{ chainId }
|
|
2755
4410
|
);
|
|
2756
4411
|
}
|
|
2757
|
-
const data =
|
|
2758
|
-
abi:
|
|
4412
|
+
const data = encodeFunctionData2({
|
|
4413
|
+
abi: erc20Abi2,
|
|
2759
4414
|
functionName: "transfer",
|
|
2760
4415
|
args: [smartWallet, BigInt(amount)]
|
|
2761
4416
|
});
|
|
4417
|
+
let paymasterUrl = absolutePaymasterUrl();
|
|
4418
|
+
if (verification?.agentId === "yieldseeker") {
|
|
4419
|
+
if (chainId !== 8453) {
|
|
4420
|
+
throw new OwneyError(
|
|
4421
|
+
"CHAIN_UNSUPPORTED",
|
|
4422
|
+
`Yieldseeker Base Account sponsorship is not available on chain ${chainId}.`
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
const { intent } = await postPaymasterIntent({
|
|
4426
|
+
baseUrl: deps.routingApiBaseUrl,
|
|
4427
|
+
apiKey: deps.apiKey,
|
|
4428
|
+
yieldseekerSignature: verification.signature,
|
|
4429
|
+
body: {
|
|
4430
|
+
chainId,
|
|
4431
|
+
token,
|
|
4432
|
+
from: deps.ownerAddress,
|
|
4433
|
+
to: smartWallet,
|
|
4434
|
+
amount,
|
|
4435
|
+
yieldseekerUserId: verification.userId,
|
|
4436
|
+
yieldseekerAgentId: verification.yieldseekerAgentId
|
|
4437
|
+
}
|
|
4438
|
+
});
|
|
4439
|
+
const url = new URL(paymasterUrl);
|
|
4440
|
+
url.searchParams.set("owneyIntent", intent);
|
|
4441
|
+
paymasterUrl = url.toString();
|
|
4442
|
+
}
|
|
2762
4443
|
const sendResult = await deps.provider.request({
|
|
2763
4444
|
method: "wallet_sendCalls",
|
|
2764
4445
|
params: [
|
|
@@ -2769,7 +4450,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2769
4450
|
atomicRequired: false,
|
|
2770
4451
|
calls: [{ to: token, value: "0x0", data }],
|
|
2771
4452
|
capabilities: {
|
|
2772
|
-
paymasterService: { url:
|
|
4453
|
+
paymasterService: { url: paymasterUrl }
|
|
2773
4454
|
}
|
|
2774
4455
|
}
|
|
2775
4456
|
]
|
|
@@ -2799,7 +4480,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2799
4480
|
`No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
|
|
2800
4481
|
{ chainId, callsId }
|
|
2801
4482
|
);
|
|
2802
|
-
};
|
|
4483
|
+
});
|
|
2803
4484
|
}
|
|
2804
4485
|
|
|
2805
4486
|
// src/client.ts
|
|
@@ -2829,7 +4510,7 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
2829
4510
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
2830
4511
|
};
|
|
2831
4512
|
var VIEM_CHAIN2 = {
|
|
2832
|
-
8453:
|
|
4513
|
+
8453: base4,
|
|
2833
4514
|
42161: arbitrum2,
|
|
2834
4515
|
1: mainnet2
|
|
2835
4516
|
};
|
|
@@ -2859,6 +4540,8 @@ var OwneySDK = class {
|
|
|
2859
4540
|
orgAgentConfig;
|
|
2860
4541
|
orgAgentConfigPromise = null;
|
|
2861
4542
|
zyfaiRpcUrls;
|
|
4543
|
+
yieldseekerApiBaseUrl;
|
|
4544
|
+
yieldseekerSiweOrigin;
|
|
2862
4545
|
routingApiBaseUrl;
|
|
2863
4546
|
referralSource;
|
|
2864
4547
|
cachedSponsoredCallback = null;
|
|
@@ -2881,6 +4564,8 @@ var OwneySDK = class {
|
|
|
2881
4564
|
this.apiKey = config.apiKey;
|
|
2882
4565
|
if (config.debug) setOwneyDebug(true);
|
|
2883
4566
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
4567
|
+
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4568
|
+
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
2884
4569
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
2885
4570
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
2886
4571
|
this.referralSource = config.referralSource;
|
|
@@ -2987,14 +4672,14 @@ var OwneySDK = class {
|
|
|
2987
4672
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2988
4673
|
// PublicClient/WalletClient param types — structurally identical at
|
|
2989
4674
|
// runtime, but the two share a name TS treats as unrelated.
|
|
2990
|
-
getPublicClient: (cid) =>
|
|
4675
|
+
getPublicClient: (cid) => createPublicClient4({
|
|
2991
4676
|
chain: VIEM_CHAIN2[cid],
|
|
2992
|
-
transport:
|
|
4677
|
+
transport: custom3(provider)
|
|
2993
4678
|
}),
|
|
2994
|
-
getWalletClient: (cid) =>
|
|
4679
|
+
getWalletClient: (cid) => createWalletClient3({
|
|
2995
4680
|
account: owner,
|
|
2996
4681
|
chain: VIEM_CHAIN2[cid],
|
|
2997
|
-
transport:
|
|
4682
|
+
transport: custom3(provider)
|
|
2998
4683
|
})
|
|
2999
4684
|
});
|
|
3000
4685
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
@@ -3011,6 +4696,8 @@ var OwneySDK = class {
|
|
|
3011
4696
|
if (!onApproved && cached) return cached;
|
|
3012
4697
|
const provider = this.requireConnectedProvider();
|
|
3013
4698
|
const callback = makeSponsoredCallsCallback({
|
|
4699
|
+
apiKey: this.apiKey,
|
|
4700
|
+
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
3014
4701
|
provider,
|
|
3015
4702
|
ownerAddress: this.state.walletAddress,
|
|
3016
4703
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
@@ -3040,14 +4727,14 @@ var OwneySDK = class {
|
|
|
3040
4727
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3041
4728
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3042
4729
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3043
|
-
getPublicClient: (cid) =>
|
|
4730
|
+
getPublicClient: (cid) => createPublicClient4({
|
|
3044
4731
|
chain: VIEM_CHAIN2[cid],
|
|
3045
|
-
transport:
|
|
4732
|
+
transport: custom3(provider)
|
|
3046
4733
|
}),
|
|
3047
|
-
getWalletClient: (cid) =>
|
|
4734
|
+
getWalletClient: (cid) => createWalletClient3({
|
|
3048
4735
|
account: owner,
|
|
3049
4736
|
chain: VIEM_CHAIN2[cid],
|
|
3050
|
-
transport:
|
|
4737
|
+
transport: custom3(provider)
|
|
3051
4738
|
})
|
|
3052
4739
|
});
|
|
3053
4740
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -3078,12 +4765,10 @@ var OwneySDK = class {
|
|
|
3078
4765
|
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
3079
4766
|
this.apiKey,
|
|
3080
4767
|
this.routingApiBaseUrl
|
|
3081
|
-
).then(
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
}
|
|
3086
|
-
);
|
|
4768
|
+
).then((config) => {
|
|
4769
|
+
this.orgAgentConfig = config;
|
|
4770
|
+
return config;
|
|
4771
|
+
});
|
|
3087
4772
|
}
|
|
3088
4773
|
return this.orgAgentConfigPromise;
|
|
3089
4774
|
}
|
|
@@ -3123,7 +4808,14 @@ var OwneySDK = class {
|
|
|
3123
4808
|
this.routingApiBaseUrl
|
|
3124
4809
|
);
|
|
3125
4810
|
this.disabledAgents.clear();
|
|
3126
|
-
for (const {
|
|
4811
|
+
for (const {
|
|
4812
|
+
key: key2,
|
|
4813
|
+
agent_type,
|
|
4814
|
+
is_enabled,
|
|
4815
|
+
is_configured
|
|
4816
|
+
} of agentKeys) {
|
|
4817
|
+
const configured = is_configured ?? Boolean(key2);
|
|
4818
|
+
if (!configured) continue;
|
|
3127
4819
|
const agent = this.createAgent(agent_type, key2);
|
|
3128
4820
|
if (!agent) continue;
|
|
3129
4821
|
this.agents.set(agent_type, agent);
|
|
@@ -3147,8 +4839,15 @@ var OwneySDK = class {
|
|
|
3147
4839
|
}
|
|
3148
4840
|
createAgent(agentId, key2) {
|
|
3149
4841
|
if (agentId === "zyfai") {
|
|
4842
|
+
if (!key2) return null;
|
|
3150
4843
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
3151
4844
|
}
|
|
4845
|
+
if (agentId === "yieldseeker") {
|
|
4846
|
+
return new YieldseekerAgent(this.apiKey, {
|
|
4847
|
+
auth: { origin: this.yieldseekerSiweOrigin },
|
|
4848
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
4849
|
+
});
|
|
4850
|
+
}
|
|
3152
4851
|
return null;
|
|
3153
4852
|
}
|
|
3154
4853
|
/**
|
|
@@ -3193,7 +4892,7 @@ var OwneySDK = class {
|
|
|
3193
4892
|
* If provided, ALL specified agents must support the chainId or the call
|
|
3194
4893
|
* throws before activating any agent.
|
|
3195
4894
|
*/
|
|
3196
|
-
async activateAgent(chainId, agentId) {
|
|
4895
|
+
async activateAgent(chainId, agentId, asset) {
|
|
3197
4896
|
const state = this.requireState();
|
|
3198
4897
|
await this.ensureAgentsInitialized();
|
|
3199
4898
|
if (agentId !== void 0) {
|
|
@@ -3229,7 +4928,7 @@ var OwneySDK = class {
|
|
|
3229
4928
|
this.activeAgents.add(id);
|
|
3230
4929
|
}
|
|
3231
4930
|
state.chainId = chainId;
|
|
3232
|
-
await this.activateAgentsInTurn(agents, state, chainId);
|
|
4931
|
+
await this.activateAgentsInTurn(agents, state, chainId, asset);
|
|
3233
4932
|
return;
|
|
3234
4933
|
}
|
|
3235
4934
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -3250,7 +4949,7 @@ var OwneySDK = class {
|
|
|
3250
4949
|
const enabledCompatible = compatible.filter(
|
|
3251
4950
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
3252
4951
|
);
|
|
3253
|
-
await this.activateAgentsInTurn(enabledCompatible, state, chainId);
|
|
4952
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
|
|
3254
4953
|
}
|
|
3255
4954
|
/**
|
|
3256
4955
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -3266,17 +4965,27 @@ var OwneySDK = class {
|
|
|
3266
4965
|
* at a time anyway.
|
|
3267
4966
|
*
|
|
3268
4967
|
* Every agent is attempted even if an earlier one fails, so one declined
|
|
3269
|
-
* signature can't deny the remaining agents their turn.
|
|
3270
|
-
*
|
|
3271
|
-
*
|
|
4968
|
+
* signature can't deny the remaining agents their turn. Once all agents have
|
|
4969
|
+
* had a chance, a partial failure identifies the agents that still need a
|
|
4970
|
+
* retry; if none activated, the original provider error is preserved.
|
|
3272
4971
|
*/
|
|
3273
|
-
async activateAgentsInTurn(agents, state, chainId) {
|
|
4972
|
+
async activateAgentsInTurn(agents, state, chainId, asset) {
|
|
3274
4973
|
let firstError = null;
|
|
4974
|
+
const activatedAgentIds = [];
|
|
4975
|
+
const failedAgents = [];
|
|
3275
4976
|
for (const agent of agents) {
|
|
3276
4977
|
try {
|
|
3277
|
-
await agent.activateAgent(state, chainId);
|
|
4978
|
+
await agent.activateAgent(state, chainId, asset);
|
|
3278
4979
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
4980
|
+
activatedAgentIds.push(agent.id);
|
|
3279
4981
|
} catch (error) {
|
|
4982
|
+
const message = error !== null && typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : typeof error === "string" ? error : "No activation error message was returned.";
|
|
4983
|
+
failedAgents.push({
|
|
4984
|
+
agentId: agent.id,
|
|
4985
|
+
code: error instanceof OwneyError ? error.code : void 0,
|
|
4986
|
+
message,
|
|
4987
|
+
...error instanceof OwneyError && error.details ? { details: error.details } : {}
|
|
4988
|
+
});
|
|
3280
4989
|
if (firstError === null) {
|
|
3281
4990
|
firstError = error;
|
|
3282
4991
|
} else {
|
|
@@ -3284,7 +4993,18 @@ var OwneySDK = class {
|
|
|
3284
4993
|
}
|
|
3285
4994
|
}
|
|
3286
4995
|
}
|
|
3287
|
-
if (firstError
|
|
4996
|
+
if (firstError === null) return;
|
|
4997
|
+
if (activatedAgentIds.length === 0) throw firstError;
|
|
4998
|
+
const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
|
|
4999
|
+
const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
|
|
5000
|
+
const failureMessages = failedAgents.map(
|
|
5001
|
+
({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
|
|
5002
|
+
).join(" ");
|
|
5003
|
+
throw new OwneyError(
|
|
5004
|
+
"AGENT_ACTIVATION_PARTIAL_FAILURE",
|
|
5005
|
+
`${activeNames} activated. ${failureMessages}`,
|
|
5006
|
+
{ activatedAgentIds, failedAgentIds, failures: failedAgents }
|
|
5007
|
+
);
|
|
3288
5008
|
}
|
|
3289
5009
|
/**
|
|
3290
5010
|
* Deposit into a specific agent, or distribute across all eligible agents.
|
|
@@ -3498,10 +5218,10 @@ var OwneySDK = class {
|
|
|
3498
5218
|
agent,
|
|
3499
5219
|
amount: i === agents.length - 1 ? perAgent + remainder : perAgent
|
|
3500
5220
|
}));
|
|
3501
|
-
const
|
|
5221
|
+
const valid2 = splits.filter(
|
|
3502
5222
|
(s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
|
|
3503
5223
|
);
|
|
3504
|
-
if (
|
|
5224
|
+
if (valid2.length === agents.length) {
|
|
3505
5225
|
return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
|
|
3506
5226
|
}
|
|
3507
5227
|
const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
|
|
@@ -3520,6 +5240,11 @@ var OwneySDK = class {
|
|
|
3520
5240
|
)
|
|
3521
5241
|
}));
|
|
3522
5242
|
}
|
|
5243
|
+
formatAgentName(agentId) {
|
|
5244
|
+
if (agentId === "zyfai") return "Zyfai";
|
|
5245
|
+
if (agentId === "yieldseeker") return "Yieldseeker";
|
|
5246
|
+
return agentId;
|
|
5247
|
+
}
|
|
3523
5248
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
3524
5249
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
3525
5250
|
const parsedAmount = BigInt(amount);
|
|
@@ -3551,12 +5276,12 @@ var OwneySDK = class {
|
|
|
3551
5276
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
3552
5277
|
);
|
|
3553
5278
|
const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
|
|
3554
|
-
const
|
|
5279
|
+
const position2 = (balance.positions ?? []).find((p) => {
|
|
3555
5280
|
const positionChain = p.chain.trim().toUpperCase();
|
|
3556
5281
|
const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
|
|
3557
5282
|
return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
|
|
3558
5283
|
});
|
|
3559
|
-
return !!token && Number(token.amount) > 0 || !!
|
|
5284
|
+
return !!token && Number(token.amount) > 0 || !!position2;
|
|
3560
5285
|
} catch (error) {
|
|
3561
5286
|
if (requireReliableRead) {
|
|
3562
5287
|
throw new OwneyError(
|
|
@@ -3671,6 +5396,10 @@ var OwneySDK = class {
|
|
|
3671
5396
|
}
|
|
3672
5397
|
const requested = BigInt(amount);
|
|
3673
5398
|
const aggregated = await this.getBalances();
|
|
5399
|
+
const unavailableAgents = eligibleAgents.filter(
|
|
5400
|
+
(agent) => !(agent.id in aggregated.agentBalances)
|
|
5401
|
+
);
|
|
5402
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
3674
5403
|
const balances = projectAgentBalancesForAsset(
|
|
3675
5404
|
eligibleAgents,
|
|
3676
5405
|
aggregated.agentBalances,
|
|
@@ -3679,7 +5408,18 @@ var OwneySDK = class {
|
|
|
3679
5408
|
assetInfo.decimals
|
|
3680
5409
|
);
|
|
3681
5410
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
3682
|
-
if (totalAvailable
|
|
5411
|
+
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
5412
|
+
throw new OwneyError(
|
|
5413
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5414
|
+
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
5415
|
+
{
|
|
5416
|
+
asset,
|
|
5417
|
+
unavailableAgents: unavailableAgentIds,
|
|
5418
|
+
agentErrors: aggregated.agentErrors
|
|
5419
|
+
}
|
|
5420
|
+
);
|
|
5421
|
+
}
|
|
5422
|
+
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
3683
5423
|
throw new OwneyError(
|
|
3684
5424
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3685
5425
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -3690,6 +5430,7 @@ var OwneySDK = class {
|
|
|
3690
5430
|
}
|
|
3691
5431
|
);
|
|
3692
5432
|
}
|
|
5433
|
+
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
3693
5434
|
const disabledBalances = balances.filter(
|
|
3694
5435
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
3695
5436
|
);
|
|
@@ -3698,7 +5439,7 @@ var OwneySDK = class {
|
|
|
3698
5439
|
);
|
|
3699
5440
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
3700
5441
|
disabledBalances,
|
|
3701
|
-
|
|
5442
|
+
plannedTarget
|
|
3702
5443
|
);
|
|
3703
5444
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
3704
5445
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -3708,7 +5449,9 @@ var OwneySDK = class {
|
|
|
3708
5449
|
}));
|
|
3709
5450
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
3710
5451
|
const results = {};
|
|
3711
|
-
const agentErrors = {
|
|
5452
|
+
const agentErrors = {
|
|
5453
|
+
...aggregated.agentErrors ?? {}
|
|
5454
|
+
};
|
|
3712
5455
|
for (let i = 0; i < plans.length; i++) {
|
|
3713
5456
|
const p = plans[i];
|
|
3714
5457
|
if (p.planned === 0n) continue;
|
|
@@ -3755,7 +5498,8 @@ var OwneySDK = class {
|
|
|
3755
5498
|
requested: amount,
|
|
3756
5499
|
withdrawn: withdrawn.toString(),
|
|
3757
5500
|
partialResults: results,
|
|
3758
|
-
agentErrors
|
|
5501
|
+
agentErrors,
|
|
5502
|
+
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
3759
5503
|
}
|
|
3760
5504
|
);
|
|
3761
5505
|
}
|
|
@@ -3773,7 +5517,10 @@ var OwneySDK = class {
|
|
|
3773
5517
|
if (agentId) {
|
|
3774
5518
|
const agent = this.getAgent(agentId);
|
|
3775
5519
|
const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3776
|
-
return
|
|
5520
|
+
return {
|
|
5521
|
+
...result,
|
|
5522
|
+
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
5523
|
+
};
|
|
3777
5524
|
}
|
|
3778
5525
|
let totalBalance = 0;
|
|
3779
5526
|
const results = {};
|
|
@@ -3781,7 +5528,13 @@ var OwneySDK = class {
|
|
|
3781
5528
|
const balanceResults = await Promise.allSettled(
|
|
3782
5529
|
entries.map(async ([id, agent]) => {
|
|
3783
5530
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3784
|
-
return [
|
|
5531
|
+
return [
|
|
5532
|
+
id,
|
|
5533
|
+
{
|
|
5534
|
+
...b,
|
|
5535
|
+
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
5536
|
+
}
|
|
5537
|
+
];
|
|
3785
5538
|
})
|
|
3786
5539
|
);
|
|
3787
5540
|
let successCount = 0;
|
|
@@ -3803,6 +5556,7 @@ var OwneySDK = class {
|
|
|
3803
5556
|
const retryDelay = rateLimitDelay(reason);
|
|
3804
5557
|
if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
|
|
3805
5558
|
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
5559
|
+
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
|
|
3806
5560
|
}
|
|
3807
5561
|
if (successCount === 0) {
|
|
3808
5562
|
throw new OwneyError(
|
|
@@ -3915,7 +5669,10 @@ var OwneySDK = class {
|
|
|
3915
5669
|
Promise.all(
|
|
3916
5670
|
entries.map(async ([id, agent]) => {
|
|
3917
5671
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3918
|
-
return [
|
|
5672
|
+
return [
|
|
5673
|
+
id,
|
|
5674
|
+
balanceForApyScope(b, chainId, tokenSymbol)
|
|
5675
|
+
];
|
|
3919
5676
|
})
|
|
3920
5677
|
)
|
|
3921
5678
|
]);
|
|
@@ -4096,10 +5853,10 @@ var OwneySDK = class {
|
|
|
4096
5853
|
);
|
|
4097
5854
|
}
|
|
4098
5855
|
const provider = this.requireConnectedProvider();
|
|
4099
|
-
const wallet =
|
|
5856
|
+
const wallet = createWalletClient3({
|
|
4100
5857
|
account: state.walletAddress,
|
|
4101
5858
|
chain: VIEM_CHAIN2[chainId],
|
|
4102
|
-
transport:
|
|
5859
|
+
transport: custom3(provider)
|
|
4103
5860
|
});
|
|
4104
5861
|
const hash = await wallet.writeContract({
|
|
4105
5862
|
address: token,
|
|
@@ -4109,9 +5866,9 @@ var OwneySDK = class {
|
|
|
4109
5866
|
account: state.walletAddress,
|
|
4110
5867
|
chain: VIEM_CHAIN2[chainId]
|
|
4111
5868
|
});
|
|
4112
|
-
const publicClient =
|
|
5869
|
+
const publicClient = createPublicClient4({
|
|
4113
5870
|
chain: VIEM_CHAIN2[chainId],
|
|
4114
|
-
transport:
|
|
5871
|
+
transport: custom3(provider)
|
|
4115
5872
|
});
|
|
4116
5873
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4117
5874
|
hash,
|
|
@@ -4148,7 +5905,9 @@ var OwneySDK = class {
|
|
|
4148
5905
|
return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
4149
5906
|
}
|
|
4150
5907
|
const results = {};
|
|
4151
|
-
const agentEntries = [...this.agents.entries()]
|
|
5908
|
+
const agentEntries = [...this.agents.entries()].filter(
|
|
5909
|
+
([id]) => !this.isAgentDisabled(id)
|
|
5910
|
+
);
|
|
4152
5911
|
const apyResults = await Promise.all(
|
|
4153
5912
|
agentEntries.map(async ([id, agent]) => {
|
|
4154
5913
|
const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
@@ -4223,13 +5982,13 @@ var OwneySDK = class {
|
|
|
4223
5982
|
};
|
|
4224
5983
|
|
|
4225
5984
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
4226
|
-
import { getAddress } from "viem";
|
|
4227
|
-
import { SiweMessage } from "siwe";
|
|
5985
|
+
import { getAddress as getAddress3 } from "viem";
|
|
5986
|
+
import { SiweMessage as SiweMessage2 } from "siwe";
|
|
4228
5987
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
4229
5988
|
|
|
4230
5989
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
4231
|
-
var
|
|
4232
|
-
var
|
|
5990
|
+
var KEY_PREFIX4 = "owney.siwx.session";
|
|
5991
|
+
var storage4 = () => {
|
|
4233
5992
|
if (typeof window === "undefined") return null;
|
|
4234
5993
|
try {
|
|
4235
5994
|
return window.localStorage;
|
|
@@ -4237,8 +5996,8 @@ var storage2 = () => {
|
|
|
4237
5996
|
return null;
|
|
4238
5997
|
}
|
|
4239
5998
|
};
|
|
4240
|
-
var
|
|
4241
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
5999
|
+
var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
|
|
6000
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
|
|
4242
6001
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
4243
6002
|
var readLegacySiwxSession = (store, address) => {
|
|
4244
6003
|
if (!store) return null;
|
|
@@ -4269,17 +6028,17 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
4269
6028
|
};
|
|
4270
6029
|
var readSiwxSession = (address, chainId) => {
|
|
4271
6030
|
if (typeof window === "undefined") return null;
|
|
4272
|
-
const key2 =
|
|
4273
|
-
const store =
|
|
4274
|
-
let
|
|
6031
|
+
const key2 = buildKey3(address);
|
|
6032
|
+
const store = storage4();
|
|
6033
|
+
let raw2 = null;
|
|
4275
6034
|
try {
|
|
4276
|
-
|
|
6035
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
4277
6036
|
} catch {
|
|
4278
|
-
|
|
6037
|
+
raw2 = null;
|
|
4279
6038
|
}
|
|
4280
|
-
if (
|
|
6039
|
+
if (raw2) {
|
|
4281
6040
|
try {
|
|
4282
|
-
return JSON.parse(
|
|
6041
|
+
return JSON.parse(raw2);
|
|
4283
6042
|
} catch {
|
|
4284
6043
|
memorySiwxSessions.delete(key2);
|
|
4285
6044
|
try {
|
|
@@ -4298,18 +6057,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
4298
6057
|
};
|
|
4299
6058
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
4300
6059
|
if (typeof window === "undefined") return;
|
|
4301
|
-
const key2 =
|
|
6060
|
+
const key2 = buildKey3(address);
|
|
4302
6061
|
memorySiwxSessions.set(key2, session);
|
|
4303
|
-
const store =
|
|
6062
|
+
const store = storage4();
|
|
4304
6063
|
try {
|
|
4305
6064
|
store?.setItem(key2, JSON.stringify(session));
|
|
4306
6065
|
} catch {
|
|
4307
6066
|
}
|
|
4308
6067
|
};
|
|
4309
6068
|
var clearSiwxSession = (address, _chainId) => {
|
|
4310
|
-
const key2 =
|
|
6069
|
+
const key2 = buildKey3(address);
|
|
4311
6070
|
memorySiwxSessions.delete(key2);
|
|
4312
|
-
const store =
|
|
6071
|
+
const store = storage4();
|
|
4313
6072
|
try {
|
|
4314
6073
|
store?.removeItem(key2);
|
|
4315
6074
|
} catch {
|
|
@@ -4349,8 +6108,8 @@ function buildSIWXConfig(deps) {
|
|
|
4349
6108
|
statement: STATEMENT,
|
|
4350
6109
|
issuedAt,
|
|
4351
6110
|
toString() {
|
|
4352
|
-
return new
|
|
4353
|
-
address:
|
|
6111
|
+
return new SiweMessage2({
|
|
6112
|
+
address: getAddress3(accountAddress),
|
|
4354
6113
|
chainId: numericChainId(chainId),
|
|
4355
6114
|
domain,
|
|
4356
6115
|
uri,
|
|
@@ -4392,7 +6151,7 @@ function buildSIWXConfig(deps) {
|
|
|
4392
6151
|
const persistSession = async (session) => {
|
|
4393
6152
|
const address = session.data.accountAddress;
|
|
4394
6153
|
const id = numericChainId(session.data.chainId);
|
|
4395
|
-
const message = new
|
|
6154
|
+
const message = new SiweMessage2(session.message);
|
|
4396
6155
|
const login = await post("/auth/login", {
|
|
4397
6156
|
message,
|
|
4398
6157
|
signature: session.signature,
|
|
@@ -4441,6 +6200,7 @@ export {
|
|
|
4441
6200
|
NotConnectedError,
|
|
4442
6201
|
OwneyError,
|
|
4443
6202
|
OwneySDK,
|
|
6203
|
+
YieldseekerAgent,
|
|
4444
6204
|
createOwneySIWX,
|
|
4445
6205
|
setOwneyDebug
|
|
4446
6206
|
};
|