@owney/sdk 0.7.15 → 0.7.16-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.cjs +1566 -502
- package/dist/index.d.cts +115 -5
- package/dist/index.d.ts +115 -5
- package/dist/index.js +1570 -495
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -306,9 +306,9 @@ function mapWeightedApyByChain(raw) {
|
|
|
306
306
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
307
307
|
const perAsset = {};
|
|
308
308
|
for (const [symbol, apy] of Object.entries(tokenApy)) {
|
|
309
|
-
const
|
|
310
|
-
if (!SUPPORTED_TOKENS.includes(
|
|
311
|
-
perAsset[
|
|
309
|
+
const token2 = symbol;
|
|
310
|
+
if (!SUPPORTED_TOKENS.includes(token2)) continue;
|
|
311
|
+
perAsset[token2] = apy;
|
|
312
312
|
}
|
|
313
313
|
if (Object.keys(perAsset).length > 0) out[chainId] = perAsset;
|
|
314
314
|
}
|
|
@@ -477,23 +477,23 @@ function computeAllocationApy(positions) {
|
|
|
477
477
|
weightedSum += apy * value;
|
|
478
478
|
totalValue += value;
|
|
479
479
|
const chainId = resolveChainId(p.chain);
|
|
480
|
-
const
|
|
480
|
+
const token2 = p.asset;
|
|
481
481
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
482
|
-
if (!SUPPORTED_TOKENS.includes(
|
|
482
|
+
if (!SUPPORTED_TOKENS.includes(token2)) continue;
|
|
483
483
|
const perAsset = buckets[chainId] ?? {};
|
|
484
|
-
const cell = perAsset[
|
|
484
|
+
const cell = perAsset[token2] ?? { weightedSum: 0, value: 0 };
|
|
485
485
|
cell.weightedSum += apy * value;
|
|
486
486
|
cell.value += value;
|
|
487
|
-
perAsset[
|
|
487
|
+
perAsset[token2] = cell;
|
|
488
488
|
buckets[chainId] = perAsset;
|
|
489
489
|
}
|
|
490
490
|
const apyByChainAndAsset = {};
|
|
491
491
|
for (const [chainKey, perAsset] of Object.entries(buckets)) {
|
|
492
492
|
const chainId = Number(chainKey);
|
|
493
493
|
const out = {};
|
|
494
|
-
for (const [
|
|
494
|
+
for (const [token2, cell] of Object.entries(perAsset ?? {})) {
|
|
495
495
|
if (cell && cell.value > 0) {
|
|
496
|
-
out[
|
|
496
|
+
out[token2] = cell.weightedSum / cell.value;
|
|
497
497
|
}
|
|
498
498
|
}
|
|
499
499
|
if (Object.keys(out).length > 0) apyByChainAndAsset[chainId] = out;
|
|
@@ -507,9 +507,9 @@ var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4d
|
|
|
507
507
|
function topicToAddress(topic) {
|
|
508
508
|
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
509
509
|
}
|
|
510
|
-
function extractWithdrawnAmount(logs, recipient,
|
|
510
|
+
function extractWithdrawnAmount(logs, recipient, token2, decimals) {
|
|
511
511
|
const wantRecipient = recipient.toLowerCase();
|
|
512
|
-
const wantToken =
|
|
512
|
+
const wantToken = token2.toLowerCase();
|
|
513
513
|
let total = 0n;
|
|
514
514
|
let matched = false;
|
|
515
515
|
for (const log of logs) {
|
|
@@ -535,11 +535,11 @@ var InvalidHistoryCursorError = class extends Error {
|
|
|
535
535
|
function encodeHistoryCursor(payload) {
|
|
536
536
|
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
537
537
|
}
|
|
538
|
-
function decodeHistoryCursor(
|
|
539
|
-
if (!
|
|
538
|
+
function decodeHistoryCursor(token2) {
|
|
539
|
+
if (!token2) throw new InvalidHistoryCursorError("Cursor is empty.");
|
|
540
540
|
let parsed;
|
|
541
541
|
try {
|
|
542
|
-
const json = Buffer.from(
|
|
542
|
+
const json = Buffer.from(token2, "base64").toString("utf8");
|
|
543
543
|
parsed = JSON.parse(json);
|
|
544
544
|
} catch {
|
|
545
545
|
throw new InvalidHistoryCursorError("Cursor is not valid base64 JSON.");
|
|
@@ -562,8 +562,8 @@ var storage = () => {
|
|
|
562
562
|
};
|
|
563
563
|
var buildKey = (address) => `${KEY_PREFIX}:${address.toLowerCase()}`;
|
|
564
564
|
var legacyKeyPrefix = (address) => `${KEY_PREFIX}:${address.toLowerCase()}:`;
|
|
565
|
-
var isJwtExpired = (
|
|
566
|
-
const parts =
|
|
565
|
+
var isJwtExpired = (token2) => {
|
|
566
|
+
const parts = token2.split(".");
|
|
567
567
|
if (parts.length !== 3) return false;
|
|
568
568
|
try {
|
|
569
569
|
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -1665,20 +1665,20 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1665
1665
|
}
|
|
1666
1666
|
}
|
|
1667
1667
|
// --- IAgent: Fund operations ---
|
|
1668
|
-
async withdraw(state, chainId,
|
|
1668
|
+
async withdraw(state, chainId, token2, amount) {
|
|
1669
1669
|
const validChainId = isValidChainId(chainId);
|
|
1670
1670
|
await this.ensureConnected(state, validChainId);
|
|
1671
1671
|
const raw = await this.sdk.withdrawFunds(
|
|
1672
1672
|
this.getAddress(),
|
|
1673
1673
|
validChainId,
|
|
1674
1674
|
amount,
|
|
1675
|
-
|
|
1675
|
+
token2
|
|
1676
1676
|
);
|
|
1677
1677
|
if (!raw.success) {
|
|
1678
1678
|
throw new OwneyError(
|
|
1679
1679
|
"WITHDRAW_FAILED",
|
|
1680
1680
|
raw.message || "Zyfai withdraw failed.",
|
|
1681
|
-
{ chainId: validChainId, token, amount, response: raw },
|
|
1681
|
+
{ chainId: validChainId, token: token2, amount, response: raw },
|
|
1682
1682
|
this.id
|
|
1683
1683
|
);
|
|
1684
1684
|
}
|
|
@@ -1829,227 +1829,52 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1829
1829
|
}
|
|
1830
1830
|
};
|
|
1831
1831
|
|
|
1832
|
-
// src/
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1832
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
1833
|
+
import {
|
|
1834
|
+
createPublicClient as createPublicClient3,
|
|
1835
|
+
createWalletClient as createWalletClient2,
|
|
1836
|
+
custom as custom2,
|
|
1837
|
+
getAddress as getAddress2,
|
|
1838
|
+
isAddress,
|
|
1839
|
+
parseUnits
|
|
1840
|
+
} from "viem";
|
|
1841
|
+
import { base as base3 } from "viem/chains";
|
|
1842
|
+
|
|
1843
|
+
// src/lib/chain-guard.ts
|
|
1844
|
+
var CHAIN_NAMES = {
|
|
1845
|
+
1: "Ethereum",
|
|
1846
|
+
8453: "Base",
|
|
1847
|
+
42161: "Arbitrum"
|
|
1848
|
+
};
|
|
1849
|
+
function chainName(chainId) {
|
|
1850
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
1851
|
+
}
|
|
1852
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
1853
|
+
const actual = await pub.getChainId();
|
|
1854
|
+
if (actual === expected) return;
|
|
1836
1855
|
try {
|
|
1837
|
-
|
|
1838
|
-
method: "GET",
|
|
1839
|
-
headers: {
|
|
1840
|
-
"Content-Type": "application/json",
|
|
1841
|
-
"x-owney-api-key": `${apiKey}`
|
|
1842
|
-
}
|
|
1843
|
-
});
|
|
1844
|
-
if (!res.ok) {
|
|
1845
|
-
if (res.status !== 404) {
|
|
1846
|
-
console.warn(
|
|
1847
|
-
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
1848
|
-
);
|
|
1849
|
-
}
|
|
1850
|
-
return null;
|
|
1851
|
-
}
|
|
1852
|
-
const json = await res.json();
|
|
1853
|
-
const policy = json.success ? json.data ?? null : null;
|
|
1854
|
-
debugLog(
|
|
1855
|
-
"owney-sdk",
|
|
1856
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
1857
|
-
policy ?? void 0
|
|
1858
|
-
);
|
|
1859
|
-
return policy;
|
|
1856
|
+
await wallet.switchChain({ id: expected });
|
|
1860
1857
|
} catch (error) {
|
|
1861
|
-
console.warn(
|
|
1862
|
-
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
1863
|
-
error instanceof Error ? error.message : String(error)
|
|
1864
|
-
);
|
|
1865
|
-
return null;
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
1869
|
-
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
1870
|
-
const res = await fetch(url, {
|
|
1871
|
-
method: "GET",
|
|
1872
|
-
headers: {
|
|
1873
|
-
"Content-Type": "application/json",
|
|
1874
|
-
"x-owney-api-key": `${apiKey}`
|
|
1875
|
-
}
|
|
1876
|
-
});
|
|
1877
|
-
if (!res.ok) {
|
|
1878
|
-
const text = await res.text().catch(() => "");
|
|
1879
1858
|
throw new OwneyError(
|
|
1880
|
-
"
|
|
1881
|
-
`
|
|
1882
|
-
{
|
|
1859
|
+
"CHAIN_MISMATCH",
|
|
1860
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
1861
|
+
{
|
|
1862
|
+
expectedChainId: expected,
|
|
1863
|
+
actualChainId: actual,
|
|
1864
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1865
|
+
}
|
|
1883
1866
|
);
|
|
1884
1867
|
}
|
|
1885
|
-
const
|
|
1886
|
-
if (
|
|
1868
|
+
const after = await pub.getChainId();
|
|
1869
|
+
if (after !== expected) {
|
|
1887
1870
|
throw new OwneyError(
|
|
1888
|
-
"
|
|
1889
|
-
`
|
|
1890
|
-
{
|
|
1891
|
-
);
|
|
1892
|
-
}
|
|
1893
|
-
return json.data;
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
// src/lib/health-report.ts
|
|
1897
|
-
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
1898
|
-
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
1899
|
-
try {
|
|
1900
|
-
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
1901
|
-
method: "POST",
|
|
1902
|
-
headers: {
|
|
1903
|
-
"Content-Type": "application/json",
|
|
1904
|
-
"x-owney-api-key": apiKey
|
|
1905
|
-
},
|
|
1906
|
-
body: JSON.stringify({
|
|
1907
|
-
agent_type: agentType,
|
|
1908
|
-
error_code: errorCode,
|
|
1909
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1910
|
-
})
|
|
1911
|
-
});
|
|
1912
|
-
} catch (err) {
|
|
1913
|
-
console.warn(
|
|
1914
|
-
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
1915
|
-
err instanceof Error ? err.message : err
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
1920
|
-
try {
|
|
1921
|
-
return await fn();
|
|
1922
|
-
} catch (err) {
|
|
1923
|
-
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
1924
|
-
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
1925
|
-
throw err;
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// src/lib/helpers/withdraw-helper.ts
|
|
1930
|
-
import { parseUnits } from "viem";
|
|
1931
|
-
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
1932
|
-
const target = asset.toUpperCase();
|
|
1933
|
-
return agents.map((agent) => {
|
|
1934
|
-
const agentBalance = aggregated[agent.id];
|
|
1935
|
-
const tokenBalance = agentBalance?.tokens.find(
|
|
1936
|
-
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
1871
|
+
"CHAIN_MISMATCH",
|
|
1872
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
1873
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
1937
1874
|
);
|
|
1938
|
-
if (!tokenBalance) return { agent, balance: 0n };
|
|
1939
|
-
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
1940
|
-
});
|
|
1941
|
-
}
|
|
1942
|
-
function planProportionalShares(balances, requested, totalAvailable) {
|
|
1943
|
-
const plans = balances.map(({ agent, balance }) => ({
|
|
1944
|
-
agent,
|
|
1945
|
-
balance,
|
|
1946
|
-
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
1947
|
-
}));
|
|
1948
|
-
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
1949
|
-
let remainder = requested - assigned;
|
|
1950
|
-
const byHeadroom = [...plans].sort((a, b) => {
|
|
1951
|
-
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
1952
|
-
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
1953
|
-
});
|
|
1954
|
-
for (const p of byHeadroom) {
|
|
1955
|
-
if (remainder === 0n) break;
|
|
1956
|
-
const headroom = p.balance - p.planned;
|
|
1957
|
-
if (headroom <= 0n) continue;
|
|
1958
|
-
const take = headroom < remainder ? headroom : remainder;
|
|
1959
|
-
p.planned += take;
|
|
1960
|
-
remainder -= take;
|
|
1961
|
-
}
|
|
1962
|
-
return plans;
|
|
1963
|
-
}
|
|
1964
|
-
function planDisabledDrain(disabled, requested) {
|
|
1965
|
-
const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
1966
|
-
const plans = [];
|
|
1967
|
-
let remaining = requested;
|
|
1968
|
-
for (const { agent, balance } of sorted) {
|
|
1969
|
-
if (remaining === 0n) {
|
|
1970
|
-
plans.push({ agent, balance, planned: 0n });
|
|
1971
|
-
continue;
|
|
1972
|
-
}
|
|
1973
|
-
const take = balance < remaining ? balance : remaining;
|
|
1974
|
-
plans.push({ agent, balance, planned: take });
|
|
1975
|
-
remaining -= take;
|
|
1976
|
-
}
|
|
1977
|
-
return { plans, remaining };
|
|
1978
|
-
}
|
|
1979
|
-
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
1980
|
-
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
1981
|
-
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
1982
|
-
const totalHeadroom = candidates.reduce(
|
|
1983
|
-
(s, c) => s + (c.balance - c.planned),
|
|
1984
|
-
0n
|
|
1985
|
-
);
|
|
1986
|
-
if (totalHeadroom === 0n) return;
|
|
1987
|
-
let distributed = 0n;
|
|
1988
|
-
for (const c of candidates) {
|
|
1989
|
-
const headroom = c.balance - c.planned;
|
|
1990
|
-
const proportional = headroom * amount / totalHeadroom;
|
|
1991
|
-
const give = proportional > headroom ? headroom : proportional;
|
|
1992
|
-
c.planned += give;
|
|
1993
|
-
distributed += give;
|
|
1994
|
-
}
|
|
1995
|
-
let leftover = amount - distributed;
|
|
1996
|
-
for (const c of candidates) {
|
|
1997
|
-
if (leftover === 0n) break;
|
|
1998
|
-
const headroom = c.balance - c.planned;
|
|
1999
|
-
if (headroom <= 0n) continue;
|
|
2000
|
-
const take = headroom < leftover ? headroom : leftover;
|
|
2001
|
-
c.planned += take;
|
|
2002
|
-
leftover -= take;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
function sumWithdrawnAmount(results) {
|
|
2006
|
-
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
// src/lib/helpers/account-apy-helper.ts
|
|
2010
|
-
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
2011
|
-
const sums = {};
|
|
2012
|
-
const weights = {};
|
|
2013
|
-
for (const id of Object.keys(agentApys)) {
|
|
2014
|
-
const cells = agentApys[id].apyByChainAndAsset;
|
|
2015
|
-
const balance = agentBalances[id] ?? 0;
|
|
2016
|
-
if (!cells || balance <= 0) continue;
|
|
2017
|
-
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
2018
|
-
if (!perAsset) continue;
|
|
2019
|
-
const chainId = Number(chainKey);
|
|
2020
|
-
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
2021
|
-
const apy = Number(apyValue ?? 0);
|
|
2022
|
-
if (apy === 0) continue;
|
|
2023
|
-
sums[chainId] ??= {};
|
|
2024
|
-
weights[chainId] ??= {};
|
|
2025
|
-
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
2026
|
-
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
const out = {};
|
|
2031
|
-
for (const chainKey of Object.keys(sums)) {
|
|
2032
|
-
const chainId = Number(chainKey);
|
|
2033
|
-
const perAssetOut = {};
|
|
2034
|
-
for (const asset of Object.keys(sums[chainId])) {
|
|
2035
|
-
const w = weights[chainId][asset];
|
|
2036
|
-
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
2037
|
-
}
|
|
2038
|
-
if (Object.keys(perAssetOut).length > 0) {
|
|
2039
|
-
out[chainId] = perAssetOut;
|
|
2040
|
-
}
|
|
2041
1875
|
}
|
|
2042
|
-
return out;
|
|
2043
1876
|
}
|
|
2044
1877
|
|
|
2045
|
-
// src/client.ts
|
|
2046
|
-
import {
|
|
2047
|
-
createPublicClient as createPublicClient2,
|
|
2048
|
-
createWalletClient,
|
|
2049
|
-
custom
|
|
2050
|
-
} from "viem";
|
|
2051
|
-
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
2052
|
-
|
|
2053
1878
|
// src/lib/transfer-auth.ts
|
|
2054
1879
|
import { bytesToHex } from "viem";
|
|
2055
1880
|
var ERC20_META_ABI = [
|
|
@@ -2073,10 +1898,10 @@ function buildTransferWithAuthorizationTypedData(input) {
|
|
|
2073
1898
|
message: input.message
|
|
2074
1899
|
};
|
|
2075
1900
|
}
|
|
2076
|
-
async function readTokenMeta(publicClient,
|
|
1901
|
+
async function readTokenMeta(publicClient, token2) {
|
|
2077
1902
|
const [tokenName, tokenVersion] = await Promise.all([
|
|
2078
|
-
publicClient.readContract({ address:
|
|
2079
|
-
publicClient.readContract({ address:
|
|
1903
|
+
publicClient.readContract({ address: token2, abi: ERC20_META_ABI, functionName: "name" }),
|
|
1904
|
+
publicClient.readContract({ address: token2, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2080
1905
|
]);
|
|
2081
1906
|
return { tokenName, tokenVersion };
|
|
2082
1907
|
}
|
|
@@ -2087,16 +1912,17 @@ function randomAuthNonce() {
|
|
|
2087
1912
|
}
|
|
2088
1913
|
|
|
2089
1914
|
// src/lib/sponsor-client.ts
|
|
2090
|
-
var
|
|
1915
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2091
1916
|
async function postSponsorTransferAuth(input) {
|
|
2092
|
-
const
|
|
1917
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2093
1918
|
let res;
|
|
2094
1919
|
try {
|
|
2095
|
-
res = await fetch(`${
|
|
1920
|
+
res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2096
1921
|
method: "POST",
|
|
2097
1922
|
headers: {
|
|
2098
1923
|
"content-type": "application/json",
|
|
2099
|
-
"x-owney-api-key": input.apiKey
|
|
1924
|
+
"x-owney-api-key": input.apiKey,
|
|
1925
|
+
...input.yieldseekerSignature ? { "x-signature": input.yieldseekerSignature } : {}
|
|
2100
1926
|
},
|
|
2101
1927
|
body: JSON.stringify(input.body)
|
|
2102
1928
|
});
|
|
@@ -2129,14 +1955,15 @@ async function postSponsorTransferAuth(input) {
|
|
|
2129
1955
|
return parsed.data;
|
|
2130
1956
|
}
|
|
2131
1957
|
async function postSponsorPermit2Transfer(input) {
|
|
2132
|
-
const
|
|
1958
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2133
1959
|
let res;
|
|
2134
1960
|
try {
|
|
2135
|
-
res = await fetch(`${
|
|
1961
|
+
res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
|
|
2136
1962
|
method: "POST",
|
|
2137
1963
|
headers: {
|
|
2138
1964
|
"content-type": "application/json",
|
|
2139
|
-
"x-owney-api-key": input.apiKey
|
|
1965
|
+
"x-owney-api-key": input.apiKey,
|
|
1966
|
+
...input.yieldseekerSignature ? { "x-signature": input.yieldseekerSignature } : {}
|
|
2140
1967
|
},
|
|
2141
1968
|
body: JSON.stringify(input.body)
|
|
2142
1969
|
});
|
|
@@ -2167,11 +1994,11 @@ async function postSponsorPermit2Transfer(input) {
|
|
|
2167
1994
|
return parsed.data;
|
|
2168
1995
|
}
|
|
2169
1996
|
async function getSponsorRelayerAddress(input) {
|
|
2170
|
-
const
|
|
1997
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2171
1998
|
let res;
|
|
2172
1999
|
try {
|
|
2173
2000
|
res = await fetch(
|
|
2174
|
-
`${
|
|
2001
|
+
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2175
2002
|
{
|
|
2176
2003
|
headers: { "x-owney-api-key": input.apiKey }
|
|
2177
2004
|
}
|
|
@@ -2264,217 +2091,1394 @@ function randomPermit2Nonce() {
|
|
|
2264
2091
|
globalThis.crypto.getRandomValues(bytes);
|
|
2265
2092
|
return BigInt(bytesToHex2(bytes));
|
|
2266
2093
|
}
|
|
2267
|
-
async function readPermit2Allowance(publicClient,
|
|
2094
|
+
async function readPermit2Allowance(publicClient, token2, owner) {
|
|
2268
2095
|
return publicClient.readContract({
|
|
2269
|
-
address:
|
|
2096
|
+
address: token2,
|
|
2270
2097
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2271
2098
|
functionName: "allowance",
|
|
2272
2099
|
args: [owner, PERMIT2_ADDRESS]
|
|
2273
2100
|
});
|
|
2274
2101
|
}
|
|
2275
|
-
async function readErc20Balance(publicClient,
|
|
2276
|
-
return publicClient.readContract({
|
|
2277
|
-
address:
|
|
2278
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2279
|
-
functionName: "balanceOf",
|
|
2280
|
-
args: [owner]
|
|
2102
|
+
async function readErc20Balance(publicClient, token2, owner) {
|
|
2103
|
+
return publicClient.readContract({
|
|
2104
|
+
address: token2,
|
|
2105
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2106
|
+
functionName: "balanceOf",
|
|
2107
|
+
args: [owner]
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/lib/sponsored-deposit.ts
|
|
2112
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2113
|
+
var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
|
|
2114
|
+
function provideDepositVerificationContext(callback, context) {
|
|
2115
|
+
callback[verificationSetter]?.(context);
|
|
2116
|
+
}
|
|
2117
|
+
function makeVerificationAwareDepositCallback(implementation) {
|
|
2118
|
+
let nextVerification;
|
|
2119
|
+
const callback = async (smartWallet, chainId, amount) => {
|
|
2120
|
+
const verification = nextVerification;
|
|
2121
|
+
nextVerification = void 0;
|
|
2122
|
+
return implementation(smartWallet, chainId, amount, verification);
|
|
2123
|
+
};
|
|
2124
|
+
Object.defineProperty(callback, verificationSetter, {
|
|
2125
|
+
value: (context) => {
|
|
2126
|
+
nextVerification = context;
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
return callback;
|
|
2130
|
+
}
|
|
2131
|
+
function makeSponsoredDepositCallback(deps) {
|
|
2132
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
2133
|
+
return makeVerificationAwareDepositCallback(
|
|
2134
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
2135
|
+
const cid = chainId;
|
|
2136
|
+
const token2 = deps.tokenAddressByChain[cid];
|
|
2137
|
+
if (!token2) {
|
|
2138
|
+
throw new OwneyError(
|
|
2139
|
+
"CHAIN_UNSUPPORTED",
|
|
2140
|
+
`No sponsored token configured for chain ${chainId}`
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
const pub = deps.getPublicClient(cid);
|
|
2144
|
+
const wallet = deps.getWalletClient(cid);
|
|
2145
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2146
|
+
try {
|
|
2147
|
+
const balance = await readErc20Balance(pub, token2, deps.ownerAddress);
|
|
2148
|
+
if (balance < BigInt(amount)) {
|
|
2149
|
+
throw new OwneyError(
|
|
2150
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2151
|
+
"Insufficient balance for this deposit.",
|
|
2152
|
+
{ token: token2, chainId: cid, balance: balance.toString(), amount }
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
} catch (err) {
|
|
2156
|
+
if (err instanceof OwneyError) throw err;
|
|
2157
|
+
console.warn(
|
|
2158
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2159
|
+
err instanceof Error ? err.message : String(err)
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token2);
|
|
2163
|
+
const validAfter = 0n;
|
|
2164
|
+
const validBefore = BigInt(
|
|
2165
|
+
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2166
|
+
);
|
|
2167
|
+
const nonce = randomAuthNonce();
|
|
2168
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
2169
|
+
token: token2,
|
|
2170
|
+
chainId: cid,
|
|
2171
|
+
tokenName,
|
|
2172
|
+
tokenVersion,
|
|
2173
|
+
message: {
|
|
2174
|
+
from: deps.ownerAddress,
|
|
2175
|
+
to: smartWallet,
|
|
2176
|
+
value: BigInt(amount),
|
|
2177
|
+
validAfter,
|
|
2178
|
+
validBefore,
|
|
2179
|
+
nonce
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
const authSignature = await wallet.signTypedData({
|
|
2183
|
+
account: deps.ownerAddress,
|
|
2184
|
+
...typedData
|
|
2185
|
+
});
|
|
2186
|
+
deps.onApproved?.();
|
|
2187
|
+
const result = await post({
|
|
2188
|
+
baseUrl: deps.baseUrl,
|
|
2189
|
+
apiKey: deps.apiKey,
|
|
2190
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
2191
|
+
body: {
|
|
2192
|
+
chainId: cid,
|
|
2193
|
+
token: token2,
|
|
2194
|
+
from: deps.ownerAddress,
|
|
2195
|
+
to: smartWallet,
|
|
2196
|
+
value: amount,
|
|
2197
|
+
validAfter: validAfter.toString(),
|
|
2198
|
+
validBefore: validBefore.toString(),
|
|
2199
|
+
nonce,
|
|
2200
|
+
authSignature,
|
|
2201
|
+
tokenName,
|
|
2202
|
+
tokenVersion
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
return result.txHash;
|
|
2206
|
+
}
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2211
|
+
import { SiweMessage, generateNonce } from "siwe";
|
|
2212
|
+
import {
|
|
2213
|
+
createPublicClient as createPublicClient2,
|
|
2214
|
+
createWalletClient,
|
|
2215
|
+
custom,
|
|
2216
|
+
getAddress
|
|
2217
|
+
} from "viem";
|
|
2218
|
+
import { base as base2 } from "viem/chains";
|
|
2219
|
+
|
|
2220
|
+
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
2221
|
+
var KEY_PREFIX2 = "owney.yieldseeker.session";
|
|
2222
|
+
var storage2 = () => {
|
|
2223
|
+
if (typeof window === "undefined") return null;
|
|
2224
|
+
try {
|
|
2225
|
+
return window.localStorage;
|
|
2226
|
+
} catch {
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2231
|
+
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2232
|
+
var isValidSession = (session) => {
|
|
2233
|
+
if (!session?.token) return false;
|
|
2234
|
+
try {
|
|
2235
|
+
const parsed = JSON.parse(atob(session.token));
|
|
2236
|
+
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2237
|
+
} catch {
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
};
|
|
2241
|
+
var readYieldseekerSession = (address, chainId) => {
|
|
2242
|
+
if (typeof window === "undefined") return null;
|
|
2243
|
+
const key2 = buildKey2(address, chainId);
|
|
2244
|
+
const store = storage2();
|
|
2245
|
+
let raw = null;
|
|
2246
|
+
try {
|
|
2247
|
+
raw = store?.getItem(key2) ?? null;
|
|
2248
|
+
} catch {
|
|
2249
|
+
raw = null;
|
|
2250
|
+
}
|
|
2251
|
+
if (raw) {
|
|
2252
|
+
try {
|
|
2253
|
+
const parsed = JSON.parse(raw);
|
|
2254
|
+
if (isValidSession(parsed)) return parsed.token;
|
|
2255
|
+
} catch {
|
|
2256
|
+
}
|
|
2257
|
+
memorySessions2.delete(key2);
|
|
2258
|
+
try {
|
|
2259
|
+
store?.removeItem(key2);
|
|
2260
|
+
} catch {
|
|
2261
|
+
}
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
const cached = memorySessions2.get(key2);
|
|
2265
|
+
if (isValidSession(cached)) return cached.token;
|
|
2266
|
+
if (cached) memorySessions2.delete(key2);
|
|
2267
|
+
return null;
|
|
2268
|
+
};
|
|
2269
|
+
var writeYieldseekerSession = (address, chainId, token2) => {
|
|
2270
|
+
if (typeof window === "undefined") return;
|
|
2271
|
+
const session = { token: token2 };
|
|
2272
|
+
if (!isValidSession(session)) return;
|
|
2273
|
+
const key2 = buildKey2(address, chainId);
|
|
2274
|
+
memorySessions2.set(key2, session);
|
|
2275
|
+
const store = storage2();
|
|
2276
|
+
try {
|
|
2277
|
+
store?.setItem(key2, JSON.stringify(session));
|
|
2278
|
+
} catch {
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
var clearYieldseekerSession = (address, chainId) => {
|
|
2282
|
+
const key2 = buildKey2(address, chainId);
|
|
2283
|
+
memorySessions2.delete(key2);
|
|
2284
|
+
const store = storage2();
|
|
2285
|
+
try {
|
|
2286
|
+
store?.removeItem(key2);
|
|
2287
|
+
} catch {
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2292
|
+
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2293
|
+
return new SiweMessage({
|
|
2294
|
+
domain: "yieldseeker.xyz",
|
|
2295
|
+
address: getAddress(address),
|
|
2296
|
+
uri: "https://yieldseeker.xyz",
|
|
2297
|
+
version: "1",
|
|
2298
|
+
chainId,
|
|
2299
|
+
nonce: (dependencies.nonce ?? generateNonce)(),
|
|
2300
|
+
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2301
|
+
}).prepareMessage();
|
|
2302
|
+
}
|
|
2303
|
+
function encodeYieldseekerAuthToken(token2) {
|
|
2304
|
+
const bytes = new TextEncoder().encode(JSON.stringify(token2));
|
|
2305
|
+
let binary = "";
|
|
2306
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2307
|
+
return btoa(binary);
|
|
2308
|
+
}
|
|
2309
|
+
var YieldseekerAuth = class {
|
|
2310
|
+
constructor(dependencies = {}) {
|
|
2311
|
+
this.dependencies = dependencies;
|
|
2312
|
+
}
|
|
2313
|
+
dependencies;
|
|
2314
|
+
tokens = /* @__PURE__ */ new Map();
|
|
2315
|
+
pending = /* @__PURE__ */ new Map();
|
|
2316
|
+
scopes = /* @__PURE__ */ new Map();
|
|
2317
|
+
key(state, chainId) {
|
|
2318
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
2319
|
+
}
|
|
2320
|
+
async getToken(state, chainId) {
|
|
2321
|
+
const key2 = this.key(state, chainId);
|
|
2322
|
+
const scope = { address: state.walletAddress, chainId };
|
|
2323
|
+
this.scopes.set(key2, scope);
|
|
2324
|
+
const cached = this.tokens.get(key2);
|
|
2325
|
+
if (cached) return cached;
|
|
2326
|
+
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2327
|
+
if (persisted) {
|
|
2328
|
+
this.tokens.set(key2, persisted);
|
|
2329
|
+
return persisted;
|
|
2330
|
+
}
|
|
2331
|
+
const inFlight = this.pending.get(key2);
|
|
2332
|
+
if (inFlight) return inFlight;
|
|
2333
|
+
const request = this.sign(state, chainId).then((token2) => {
|
|
2334
|
+
this.tokens.set(key2, token2);
|
|
2335
|
+
writeYieldseekerSession(scope.address, scope.chainId, token2);
|
|
2336
|
+
return token2;
|
|
2337
|
+
});
|
|
2338
|
+
this.pending.set(key2, request);
|
|
2339
|
+
try {
|
|
2340
|
+
return await request;
|
|
2341
|
+
} finally {
|
|
2342
|
+
this.pending.delete(key2);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
clear(state, chainId) {
|
|
2346
|
+
if (!state || chainId === void 0) {
|
|
2347
|
+
for (const scope of this.scopes.values()) {
|
|
2348
|
+
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2349
|
+
}
|
|
2350
|
+
this.tokens.clear();
|
|
2351
|
+
this.pending.clear();
|
|
2352
|
+
this.scopes.clear();
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
const key2 = this.key(state, chainId);
|
|
2356
|
+
this.tokens.delete(key2);
|
|
2357
|
+
this.pending.delete(key2);
|
|
2358
|
+
this.scopes.delete(key2);
|
|
2359
|
+
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2360
|
+
}
|
|
2361
|
+
async sign(state, chainId) {
|
|
2362
|
+
const account = getAddress(state.walletAddress);
|
|
2363
|
+
const publicClient = createPublicClient2({
|
|
2364
|
+
chain: base2,
|
|
2365
|
+
transport: custom(state.provider)
|
|
2366
|
+
});
|
|
2367
|
+
const walletClient = createWalletClient({
|
|
2368
|
+
account,
|
|
2369
|
+
chain: base2,
|
|
2370
|
+
transport: custom(state.provider)
|
|
2371
|
+
});
|
|
2372
|
+
await ensureWalletOnChain(
|
|
2373
|
+
publicClient,
|
|
2374
|
+
walletClient,
|
|
2375
|
+
8453
|
|
2376
|
+
);
|
|
2377
|
+
const message = createYieldseekerSiweMessage(
|
|
2378
|
+
account,
|
|
2379
|
+
chainId,
|
|
2380
|
+
this.dependencies
|
|
2381
|
+
);
|
|
2382
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
2383
|
+
return encodeYieldseekerAuthToken({ message, signature });
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
|
|
2387
|
+
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2388
|
+
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2389
|
+
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2390
|
+
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2391
|
+
}
|
|
2392
|
+
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2393
|
+
var YieldseekerApiError = class extends Error {
|
|
2394
|
+
constructor(status, providerCode, responseFields) {
|
|
2395
|
+
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2396
|
+
this.status = status;
|
|
2397
|
+
this.providerCode = providerCode;
|
|
2398
|
+
this.responseFields = responseFields;
|
|
2399
|
+
this.name = "YieldseekerApiError";
|
|
2400
|
+
}
|
|
2401
|
+
status;
|
|
2402
|
+
providerCode;
|
|
2403
|
+
responseFields;
|
|
2404
|
+
get isAuthenticationError() {
|
|
2405
|
+
return this.status === 401 || this.status === 403;
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
function providerError(body, fallback) {
|
|
2409
|
+
if (!body || typeof body !== "object") return { code: fallback };
|
|
2410
|
+
const record = body;
|
|
2411
|
+
return {
|
|
2412
|
+
code: typeof record.message === "string" ? record.message : fallback,
|
|
2413
|
+
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
var YieldseekerApiClient = class {
|
|
2417
|
+
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
|
|
2418
|
+
this.owneyApiKey = owneyApiKey;
|
|
2419
|
+
this.baseUrl = baseUrl;
|
|
2420
|
+
this.fetchFn = fetchFn;
|
|
2421
|
+
}
|
|
2422
|
+
owneyApiKey;
|
|
2423
|
+
baseUrl;
|
|
2424
|
+
fetchFn;
|
|
2425
|
+
async request(path, options = {}) {
|
|
2426
|
+
const controller = new AbortController();
|
|
2427
|
+
const timer = setTimeout(
|
|
2428
|
+
() => controller.abort(),
|
|
2429
|
+
options.timeoutMs ?? 15e3
|
|
2430
|
+
);
|
|
2431
|
+
try {
|
|
2432
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2433
|
+
method: options.method ?? "GET",
|
|
2434
|
+
headers: {
|
|
2435
|
+
"Content-Type": "application/json",
|
|
2436
|
+
"x-owney-api-key": this.owneyApiKey,
|
|
2437
|
+
...options.signature ? { "X-Signature": options.signature } : {}
|
|
2438
|
+
},
|
|
2439
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2440
|
+
signal: controller.signal
|
|
2441
|
+
});
|
|
2442
|
+
const payload = await response.json().catch(() => null);
|
|
2443
|
+
if (!response.ok) {
|
|
2444
|
+
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2445
|
+
throw new YieldseekerApiError(
|
|
2446
|
+
response.status,
|
|
2447
|
+
error.code,
|
|
2448
|
+
error.fields
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2452
|
+
return payload.data;
|
|
2453
|
+
}
|
|
2454
|
+
return payload;
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
if (error instanceof YieldseekerApiError) throw error;
|
|
2457
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2458
|
+
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2459
|
+
}
|
|
2460
|
+
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2461
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2462
|
+
});
|
|
2463
|
+
} finally {
|
|
2464
|
+
clearTimeout(timer);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
|
|
2469
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2470
|
+
import { formatUnits } from "viem";
|
|
2471
|
+
var REBALANCE_AMOUNT_DECIMALS = {
|
|
2472
|
+
USDC: 6,
|
|
2473
|
+
WETH: 18
|
|
2474
|
+
};
|
|
2475
|
+
function rebalanceAmount(value, tokenSymbol) {
|
|
2476
|
+
const decimals = REBALANCE_AMOUNT_DECIMALS[tokenSymbol.toUpperCase()];
|
|
2477
|
+
if (decimals === void 0) {
|
|
2478
|
+
return invalid(
|
|
2479
|
+
"history",
|
|
2480
|
+
`unsupported rebalance token ${tokenSymbol || "<empty>"}`
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
try {
|
|
2484
|
+
return formatUnits(BigInt(value), decimals);
|
|
2485
|
+
} catch {
|
|
2486
|
+
return invalid("history", `invalid rebalance amount ${value}`);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function invalid(endpoint, detail) {
|
|
2490
|
+
throw new OwneyError(
|
|
2491
|
+
"AGENT_INVALID_RESPONSE",
|
|
2492
|
+
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2493
|
+
{ endpoint, detail },
|
|
2494
|
+
"yieldseeker"
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
function requireObject(value, endpoint) {
|
|
2498
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2499
|
+
return invalid(endpoint, "expected an object");
|
|
2500
|
+
}
|
|
2501
|
+
return value;
|
|
2502
|
+
}
|
|
2503
|
+
function requireEnvelope(value, key2, endpoint) {
|
|
2504
|
+
const root = requireObject(value, endpoint);
|
|
2505
|
+
return requireObject(root[key2], endpoint);
|
|
2506
|
+
}
|
|
2507
|
+
function token(value) {
|
|
2508
|
+
return {
|
|
2509
|
+
chain: value.chain,
|
|
2510
|
+
chainId: value.chainId,
|
|
2511
|
+
asset: value.asset,
|
|
2512
|
+
amount: String(value.amount)
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
function position(value) {
|
|
2516
|
+
return {
|
|
2517
|
+
chain: String(value.chain),
|
|
2518
|
+
protocol: String(value.protocol),
|
|
2519
|
+
...value.protocolId ? { protocolId: value.protocolId } : {},
|
|
2520
|
+
...value.pool ? { pool: value.pool } : {},
|
|
2521
|
+
asset: String(value.asset),
|
|
2522
|
+
amount: String(value.amount),
|
|
2523
|
+
...value.amountRaw !== void 0 ? { amountRaw: value.amountRaw } : {},
|
|
2524
|
+
...typeof value.apy === "number" ? { apy: value.apy } : {},
|
|
2525
|
+
...typeof value.tvl === "number" ? { tvl: value.tvl } : {},
|
|
2526
|
+
...typeof value.liquidity === "number" ? { liquidity: value.liquidity } : {}
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
function mapApyMatrix(matrix) {
|
|
2530
|
+
const mapped = {};
|
|
2531
|
+
for (const [chain, assets] of Object.entries(matrix ?? {})) {
|
|
2532
|
+
const chainId = Number(chain);
|
|
2533
|
+
if (chainId !== 8453 || !assets || typeof assets !== "object") continue;
|
|
2534
|
+
mapped[8453] = {};
|
|
2535
|
+
for (const [asset, apy] of Object.entries(assets)) {
|
|
2536
|
+
if ((asset === "USDC" || asset === "WETH") && typeof apy === "number") {
|
|
2537
|
+
mapped[8453][asset] = apy;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return mapped;
|
|
2542
|
+
}
|
|
2543
|
+
function mapYieldseekerProfile(value) {
|
|
2544
|
+
const profile = requireEnvelope(
|
|
2545
|
+
value,
|
|
2546
|
+
"profile",
|
|
2547
|
+
"profile"
|
|
2548
|
+
);
|
|
2549
|
+
if (typeof profile.address !== "string" || typeof profile.smartWallet !== "string" || !Array.isArray(profile.chains) || typeof profile.hasActiveSessionKey !== "boolean" || !Array.isArray(profile.protocols)) {
|
|
2550
|
+
return invalid("profile", "missing required fields");
|
|
2551
|
+
}
|
|
2552
|
+
return {
|
|
2553
|
+
address: profile.address,
|
|
2554
|
+
smartWallet: profile.smartWallet,
|
|
2555
|
+
chains: profile.chains.map(Number),
|
|
2556
|
+
hasActiveSessionKey: profile.hasActiveSessionKey,
|
|
2557
|
+
protocols: profile.protocols.map(String)
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
function mapYieldseekerBalances(value) {
|
|
2561
|
+
const balances = requireEnvelope(
|
|
2562
|
+
value,
|
|
2563
|
+
"balances",
|
|
2564
|
+
"balances"
|
|
2565
|
+
);
|
|
2566
|
+
if (typeof balances.smartWallet !== "string" || typeof balances.totalBalance !== "string" || typeof balances.totalBalanceAsset !== "string" || !Array.isArray(balances.tokens) || !Array.isArray(balances.positions)) {
|
|
2567
|
+
return invalid("balances", "missing required fields");
|
|
2568
|
+
}
|
|
2569
|
+
return {
|
|
2570
|
+
smartWallet: balances.smartWallet,
|
|
2571
|
+
totalBalance: balances.totalBalance,
|
|
2572
|
+
totalBalanceAsset: balances.totalBalanceAsset.toLowerCase(),
|
|
2573
|
+
tokens: balances.tokens.map(token),
|
|
2574
|
+
positions: balances.positions.map(position)
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
function mapYieldseekerEarnings(value) {
|
|
2578
|
+
const earnings = requireEnvelope(
|
|
2579
|
+
value,
|
|
2580
|
+
"earnings",
|
|
2581
|
+
"earnings"
|
|
2582
|
+
);
|
|
2583
|
+
if (typeof earnings.smartWallet !== "string" || typeof earnings.lifetimeEarnings !== "number" || !Array.isArray(earnings.tokens)) {
|
|
2584
|
+
return invalid("earnings", "missing required fields");
|
|
2585
|
+
}
|
|
2586
|
+
return {
|
|
2587
|
+
smartWallet: earnings.smartWallet,
|
|
2588
|
+
lifetimeEarnings: earnings.lifetimeEarnings,
|
|
2589
|
+
tokens: earnings.tokens.map(token)
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
function mapYieldseekerApy(value) {
|
|
2593
|
+
const apy = requireEnvelope(
|
|
2594
|
+
value,
|
|
2595
|
+
"apy",
|
|
2596
|
+
"apy"
|
|
2597
|
+
);
|
|
2598
|
+
if (typeof apy.walletAddress !== "string" || typeof apy.weightedApyAfterFee !== "number" || !apy.apyByChainAndAsset || typeof apy.apyByChainAndAsset !== "object" || !Array.isArray(apy.history)) {
|
|
2599
|
+
return invalid("apy", "missing required fields");
|
|
2600
|
+
}
|
|
2601
|
+
return {
|
|
2602
|
+
walletAddress: apy.walletAddress,
|
|
2603
|
+
weightedApyAfterFee: apy.weightedApyAfterFee,
|
|
2604
|
+
apyByChainAndAsset: mapApyMatrix(apy.apyByChainAndAsset),
|
|
2605
|
+
history: apy.history.map((point) => ({
|
|
2606
|
+
date: String(point.date),
|
|
2607
|
+
apy: Number(point.apy)
|
|
2608
|
+
}))
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
function historyAction(value) {
|
|
2612
|
+
const normalized = value.trim().toLowerCase();
|
|
2613
|
+
if (normalized === "rebalance") return "Rebalance";
|
|
2614
|
+
if (normalized === "deposit") return "Deposit";
|
|
2615
|
+
if (normalized === "top up" || normalized === "topup") return "Top up";
|
|
2616
|
+
if (normalized === "withdraw" || normalized === "withdrawal")
|
|
2617
|
+
return "Withdraw";
|
|
2618
|
+
if (normalized === "earned" || normalized === "earnings") return "Earned";
|
|
2619
|
+
return invalid("history", `unsupported action ${value}`);
|
|
2620
|
+
}
|
|
2621
|
+
function historyItem(value) {
|
|
2622
|
+
return {
|
|
2623
|
+
agent: "yieldseeker",
|
|
2624
|
+
action: historyAction(value.action),
|
|
2625
|
+
date: String(value.date),
|
|
2626
|
+
oldApy: value.oldApy === null ? null : String(value.oldApy),
|
|
2627
|
+
newApy: value.newApy === null ? null : String(value.newApy),
|
|
2628
|
+
transactions: (value.transactions ?? []).map((transaction) => ({
|
|
2629
|
+
txHashes: transaction.txHashes.map(String),
|
|
2630
|
+
...typeof transaction.chainId === "number" ? { chainId: transaction.chainId } : {},
|
|
2631
|
+
...typeof transaction.tokenSymbol === "string" ? { tokenSymbol: transaction.tokenSymbol } : {},
|
|
2632
|
+
...typeof transaction.amount === "string" ? { amount: transaction.amount } : {}
|
|
2633
|
+
})),
|
|
2634
|
+
rebalanceLog: (value.rebalanceLog ?? []).map((entry) => ({
|
|
2635
|
+
fromProtocol: String(entry.fromProtocol),
|
|
2636
|
+
toProtocol: String(entry.toProtocol),
|
|
2637
|
+
...entry.fromPool ? { fromPool: entry.fromPool } : {},
|
|
2638
|
+
...entry.toPool ? { toPool: entry.toPool } : {},
|
|
2639
|
+
tokenSymbol: String(entry.tokenSymbol),
|
|
2640
|
+
// Unlike Yieldseeker's other read-response amounts, live history
|
|
2641
|
+
// rebalance amounts are returned in the token's smallest unit. Normalize
|
|
2642
|
+
// them at the adapter boundary so every Owney consumer receives the
|
|
2643
|
+
// shared human-readable decimal shape.
|
|
2644
|
+
amount: rebalanceAmount(String(entry.amount), String(entry.tokenSymbol)),
|
|
2645
|
+
status: entry.status
|
|
2646
|
+
}))
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
function mapYieldseekerHistory(value) {
|
|
2650
|
+
const history = requireEnvelope(
|
|
2651
|
+
value,
|
|
2652
|
+
"history",
|
|
2653
|
+
"history"
|
|
2654
|
+
);
|
|
2655
|
+
if (!Array.isArray(history.data) || typeof history.hasMore !== "boolean") {
|
|
2656
|
+
return invalid("history", "missing required fields");
|
|
2657
|
+
}
|
|
2658
|
+
return {
|
|
2659
|
+
data: history.data.map(historyItem),
|
|
2660
|
+
hasMore: history.hasMore,
|
|
2661
|
+
...history.hasMore && typeof history.nextCursor === "string" ? { nextCursor: history.nextCursor } : {}
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
function mapYieldseekerAgentApy(value) {
|
|
2665
|
+
const agentApy = requireEnvelope(
|
|
2666
|
+
value,
|
|
2667
|
+
"agentApy",
|
|
2668
|
+
"agent APY"
|
|
2669
|
+
);
|
|
2670
|
+
if (typeof agentApy.averageApy !== "number") {
|
|
2671
|
+
return invalid("agent APY", "missing averageApy");
|
|
2672
|
+
}
|
|
2673
|
+
return {
|
|
2674
|
+
averageApy: agentApy.averageApy,
|
|
2675
|
+
...agentApy.detailedApys ? {
|
|
2676
|
+
detailedApys: {
|
|
2677
|
+
apyPerAsset: mapApyMatrix(agentApy.detailedApys.apyPerAsset)
|
|
2678
|
+
}
|
|
2679
|
+
} : {}
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2684
|
+
function query(params) {
|
|
2685
|
+
const search = new URLSearchParams();
|
|
2686
|
+
for (const [key2, value] of Object.entries(params)) {
|
|
2687
|
+
if (value !== void 0) search.set(key2, String(value));
|
|
2688
|
+
}
|
|
2689
|
+
const encoded = search.toString();
|
|
2690
|
+
return encoded ? `?${encoded}` : "";
|
|
2691
|
+
}
|
|
2692
|
+
var YieldseekerAgent = class {
|
|
2693
|
+
id = "yieldseeker";
|
|
2694
|
+
balanceComposition = "tokens-plus-positions";
|
|
2695
|
+
supportedChainIds = [8453];
|
|
2696
|
+
supportedAssets = [
|
|
2697
|
+
{
|
|
2698
|
+
chainId: 8453,
|
|
2699
|
+
chain: "BASE",
|
|
2700
|
+
assets: [
|
|
2701
|
+
{ symbol: "USDC", minDepositAmount: "1" },
|
|
2702
|
+
{ symbol: "WETH", minDepositAmount: "1" }
|
|
2703
|
+
]
|
|
2704
|
+
}
|
|
2705
|
+
];
|
|
2706
|
+
api;
|
|
2707
|
+
auth;
|
|
2708
|
+
transactionExecutor;
|
|
2709
|
+
unwindReceiptWaiter;
|
|
2710
|
+
activated = /* @__PURE__ */ new Set();
|
|
2711
|
+
constructor(owneyApiKey, options = {}) {
|
|
2712
|
+
this.api = new YieldseekerApiClient(
|
|
2713
|
+
owneyApiKey,
|
|
2714
|
+
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
2715
|
+
options.fetchFn
|
|
2716
|
+
);
|
|
2717
|
+
this.auth = new YieldseekerAuth(options.auth);
|
|
2718
|
+
this.transactionExecutor = options.transactionExecutor;
|
|
2719
|
+
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
2720
|
+
}
|
|
2721
|
+
async disconnect() {
|
|
2722
|
+
this.auth.clear();
|
|
2723
|
+
this.activated.clear();
|
|
2724
|
+
}
|
|
2725
|
+
async activateAgent(state, chainId, asset) {
|
|
2726
|
+
this.assertChain(chainId);
|
|
2727
|
+
await this.auth.getToken(state, chainId);
|
|
2728
|
+
if (asset === "USDC" || asset === "WETH") {
|
|
2729
|
+
await this.ensureActivated(state, chainId, asset);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
2733
|
+
this.assertChain(chainId);
|
|
2734
|
+
this.assertAsset(asset);
|
|
2735
|
+
if (BigInt(amount) <= 0n) {
|
|
2736
|
+
throw new OwneyError(
|
|
2737
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
2738
|
+
"Yieldseeker deposits must be greater than zero.",
|
|
2739
|
+
{ amount, minDepositAmount: "1" },
|
|
2740
|
+
this.id
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
await this.ensureActivated(state, chainId, asset);
|
|
2744
|
+
const response = await this.walletRequest(
|
|
2745
|
+
state,
|
|
2746
|
+
chainId,
|
|
2747
|
+
"/deposits",
|
|
2748
|
+
{ method: "POST", body: { chainId, asset, amount } }
|
|
2749
|
+
);
|
|
2750
|
+
const deposit = response?.deposit;
|
|
2751
|
+
if (!deposit?.transaction || typeof deposit.amount !== "string" || deposit.amount !== amount || typeof deposit.smartWallet !== "string" || !isAddress(deposit.smartWallet)) {
|
|
2752
|
+
throw this.invalidResponse("deposit");
|
|
2753
|
+
}
|
|
2754
|
+
if (depositCallback) {
|
|
2755
|
+
provideDepositVerificationContext(depositCallback, {
|
|
2756
|
+
agentId: "yieldseeker",
|
|
2757
|
+
// Re-read after /deposits so an authentication retry cannot forward
|
|
2758
|
+
// the stale token that Yieldseeker just rejected.
|
|
2759
|
+
signature: await this.auth.getToken(state, chainId)
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
const txHash = depositCallback ? await depositCallback(deposit.smartWallet, chainId, amount) : await this.submitTransaction(state, chainId, deposit.transaction);
|
|
2763
|
+
const confirmation = await this.walletRequest(
|
|
2764
|
+
state,
|
|
2765
|
+
chainId,
|
|
2766
|
+
"/deposits/confirm",
|
|
2767
|
+
{ method: "POST", body: { chainId, asset } }
|
|
2768
|
+
);
|
|
2769
|
+
if (typeof confirmation?.confirmation?.autoseekQueued !== "boolean") {
|
|
2770
|
+
throw this.invalidResponse("deposit confirmation", {
|
|
2771
|
+
transactionHash: txHash,
|
|
2772
|
+
transactionConfirmed: true
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
return {
|
|
2776
|
+
txHash,
|
|
2777
|
+
smartWallet: deposit.smartWallet,
|
|
2778
|
+
amount: deposit.amount
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
async withdraw(state, chainId, asset, amount) {
|
|
2782
|
+
this.assertChain(chainId);
|
|
2783
|
+
this.assertAsset(asset);
|
|
2784
|
+
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
2785
|
+
throw new OwneyError(
|
|
2786
|
+
"WITHDRAW_FAILED",
|
|
2787
|
+
"Yieldseeker withdrawals must be greater than zero.",
|
|
2788
|
+
{ amount },
|
|
2789
|
+
this.id
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
const balances = await this.getBalances(state, chainId);
|
|
2793
|
+
const decimals = asset === "USDC" ? 6 : 18;
|
|
2794
|
+
const targetAsset = asset.toUpperCase();
|
|
2795
|
+
const idle = balances.tokens.filter(
|
|
2796
|
+
(token2) => token2.chainId === chainId && token2.asset.toUpperCase() === targetAsset
|
|
2797
|
+
).reduce((total, token2) => total + parseUnits(token2.amount, decimals), 0n);
|
|
2798
|
+
const positions = (balances.positions ?? []).filter(
|
|
2799
|
+
(position2) => this.positionMatchesChain(position2.chain, chainId) && position2.asset.toUpperCase() === targetAsset
|
|
2800
|
+
).map((position2) => ({
|
|
2801
|
+
position: position2,
|
|
2802
|
+
amount: position2.amountRaw !== void 0 ? BigInt(position2.amountRaw) : parseUnits(position2.amount, decimals)
|
|
2803
|
+
}));
|
|
2804
|
+
const deployed = positions.reduce(
|
|
2805
|
+
(total, position2) => total + position2.amount,
|
|
2806
|
+
0n
|
|
2807
|
+
);
|
|
2808
|
+
const totalAvailable = idle + deployed;
|
|
2809
|
+
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
2810
|
+
if (requested > totalAvailable) {
|
|
2811
|
+
throw new OwneyError(
|
|
2812
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2813
|
+
`Requested withdrawal "${requested.toString()}" exceeds available Yieldseeker balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
2814
|
+
{
|
|
2815
|
+
asset,
|
|
2816
|
+
requested: requested.toString(),
|
|
2817
|
+
available: totalAvailable.toString()
|
|
2818
|
+
},
|
|
2819
|
+
this.id
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
const requiredFromPositions = requested > idle ? requested - idle : 0n;
|
|
2823
|
+
const unwindPlans = [];
|
|
2824
|
+
let remainingToUnwind = requiredFromPositions;
|
|
2825
|
+
for (const { position: position2, amount: positionAmount } of positions) {
|
|
2826
|
+
if (remainingToUnwind === 0n) break;
|
|
2827
|
+
if (positionAmount <= 0n) continue;
|
|
2828
|
+
if (!position2.protocolId || !isAddress(position2.protocolId)) {
|
|
2829
|
+
throw this.invalidResponse("position", {
|
|
2830
|
+
reason: "A deployed position is missing its vault address.",
|
|
2831
|
+
protocol: position2.protocol,
|
|
2832
|
+
pool: position2.pool
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
const unwindAmount = positionAmount < remainingToUnwind ? positionAmount : remainingToUnwind;
|
|
2836
|
+
unwindPlans.push({
|
|
2837
|
+
vaultAddress: getAddress2(position2.protocolId),
|
|
2838
|
+
amount: unwindAmount
|
|
2839
|
+
});
|
|
2840
|
+
remainingToUnwind -= unwindAmount;
|
|
2841
|
+
}
|
|
2842
|
+
if (remainingToUnwind > 0n) {
|
|
2843
|
+
throw this.invalidResponse("balances", {
|
|
2844
|
+
reason: "Deployed position balances could not cover the unwind.",
|
|
2845
|
+
required: requiredFromPositions.toString(),
|
|
2846
|
+
planned: (requiredFromPositions - remainingToUnwind).toString()
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
for (const plan of unwindPlans) {
|
|
2850
|
+
const response2 = await this.walletRequest(
|
|
2851
|
+
state,
|
|
2852
|
+
chainId,
|
|
2853
|
+
"/positions/unwind",
|
|
2854
|
+
{
|
|
2855
|
+
method: "POST",
|
|
2856
|
+
body: {
|
|
2857
|
+
chainId,
|
|
2858
|
+
asset,
|
|
2859
|
+
vaultAddress: plan.vaultAddress,
|
|
2860
|
+
amount: plan.amount.toString()
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
);
|
|
2864
|
+
const transactionHash = response2?.unwind?.transactionHash;
|
|
2865
|
+
if (!this.isTransactionHash(transactionHash)) {
|
|
2866
|
+
throw this.invalidResponse("position unwind");
|
|
2867
|
+
}
|
|
2868
|
+
await this.waitForUnwindReceipt(state, chainId, transactionHash);
|
|
2869
|
+
}
|
|
2870
|
+
const response = await this.walletRequest(
|
|
2871
|
+
state,
|
|
2872
|
+
chainId,
|
|
2873
|
+
"/withdrawals",
|
|
2874
|
+
{
|
|
2875
|
+
method: "POST",
|
|
2876
|
+
body: { chainId, asset, ...amount !== void 0 ? { amount } : {} }
|
|
2877
|
+
}
|
|
2878
|
+
);
|
|
2879
|
+
const withdrawal = response?.withdrawal;
|
|
2880
|
+
if (!withdrawal?.transaction || typeof withdrawal.amount !== "string") {
|
|
2881
|
+
throw this.invalidResponse("withdrawal");
|
|
2882
|
+
}
|
|
2883
|
+
const txHash = await this.submitTransaction(
|
|
2884
|
+
state,
|
|
2885
|
+
chainId,
|
|
2886
|
+
withdrawal.transaction
|
|
2887
|
+
);
|
|
2888
|
+
return {
|
|
2889
|
+
txHash,
|
|
2890
|
+
type: amount === void 0 ? "full" : "partial",
|
|
2891
|
+
amount: withdrawal.amount
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
async getBalances(state, chainId) {
|
|
2895
|
+
return mapYieldseekerBalances(
|
|
2896
|
+
await this.walletRequest(
|
|
2897
|
+
state,
|
|
2898
|
+
chainId,
|
|
2899
|
+
`/balances${query({ chainId })}`
|
|
2900
|
+
)
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
async getEarnings(state, chainId) {
|
|
2904
|
+
return mapYieldseekerEarnings(
|
|
2905
|
+
await this.walletRequest(
|
|
2906
|
+
state,
|
|
2907
|
+
chainId,
|
|
2908
|
+
`/earnings${query({ chainId })}`
|
|
2909
|
+
)
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
2913
|
+
return mapYieldseekerApy(
|
|
2914
|
+
await this.walletRequest(
|
|
2915
|
+
state,
|
|
2916
|
+
chainId,
|
|
2917
|
+
`/apy${query({ chainId, days, tokenSymbol })}`
|
|
2918
|
+
)
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
async getHistory(state, chainId, options) {
|
|
2922
|
+
return mapYieldseekerHistory(
|
|
2923
|
+
await this.walletRequest(
|
|
2924
|
+
state,
|
|
2925
|
+
chainId,
|
|
2926
|
+
`/history${query({
|
|
2927
|
+
chainId,
|
|
2928
|
+
limit: options?.limit ?? 10,
|
|
2929
|
+
cursor: options?.cursor,
|
|
2930
|
+
fromDate: options?.fromDate,
|
|
2931
|
+
toDate: options?.toDate
|
|
2932
|
+
})}`
|
|
2933
|
+
)
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
async getUserProfile(state, chainId) {
|
|
2937
|
+
return mapYieldseekerProfile(
|
|
2938
|
+
await this.walletRequest(state, chainId, `/profile${query({ chainId })}`)
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
async getAgentApy(days, options) {
|
|
2942
|
+
this.assertOptionalChain(options?.chainId);
|
|
2943
|
+
return mapYieldseekerAgentApy(
|
|
2944
|
+
await this.api.request(
|
|
2945
|
+
`/agent/apy${query({
|
|
2946
|
+
days,
|
|
2947
|
+
tokenSymbol: options?.tokenSymbol,
|
|
2948
|
+
chainId: options?.chainId
|
|
2949
|
+
})}`
|
|
2950
|
+
)
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
async ensureActivated(state, chainId, asset) {
|
|
2954
|
+
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${asset}`;
|
|
2955
|
+
if (this.activated.has(key2)) return;
|
|
2956
|
+
const response = await this.walletRequest(
|
|
2957
|
+
state,
|
|
2958
|
+
chainId,
|
|
2959
|
+
"/activation",
|
|
2960
|
+
{ method: "POST", body: { chainId, asset } }
|
|
2961
|
+
);
|
|
2962
|
+
if (typeof response?.activation?.smartWallet !== "string" || typeof response.activation.deployed !== "boolean" || typeof response.activation.hasActiveSessionKey !== "boolean") {
|
|
2963
|
+
throw this.invalidResponse("activation");
|
|
2964
|
+
}
|
|
2965
|
+
this.activated.add(key2);
|
|
2966
|
+
}
|
|
2967
|
+
async walletRequest(state, chainId, path, options = {}) {
|
|
2968
|
+
this.assertChain(chainId);
|
|
2969
|
+
let signature = await this.auth.getToken(state, chainId);
|
|
2970
|
+
try {
|
|
2971
|
+
return await this.api.request(path, { ...options, signature });
|
|
2972
|
+
} catch (error) {
|
|
2973
|
+
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
2974
|
+
if (error.isAuthenticationError) {
|
|
2975
|
+
this.auth.clear(state, chainId);
|
|
2976
|
+
signature = await this.auth.getToken(state, chainId);
|
|
2977
|
+
try {
|
|
2978
|
+
return await this.api.request(path, { ...options, signature });
|
|
2979
|
+
} catch (retryError) {
|
|
2980
|
+
throw this.mapApiError(retryError);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
throw this.mapApiError(error);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
mapApiError(error) {
|
|
2987
|
+
if (!(error instanceof YieldseekerApiError)) {
|
|
2988
|
+
return new OwneyError(
|
|
2989
|
+
"AGENT_API_ERROR",
|
|
2990
|
+
"Yieldseeker request failed.",
|
|
2991
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
2992
|
+
this.id
|
|
2993
|
+
);
|
|
2994
|
+
}
|
|
2995
|
+
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
2996
|
+
return new OwneyError(
|
|
2997
|
+
code,
|
|
2998
|
+
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
2999
|
+
{
|
|
3000
|
+
statusCode: error.status,
|
|
3001
|
+
providerCode: error.providerCode,
|
|
3002
|
+
...error.responseFields ? { fields: error.responseFields } : {}
|
|
3003
|
+
},
|
|
3004
|
+
this.id
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
async submitTransaction(state, chainId, transaction) {
|
|
3008
|
+
if (this.transactionExecutor) {
|
|
3009
|
+
return this.transactionExecutor(state, chainId, transaction);
|
|
3010
|
+
}
|
|
3011
|
+
if (typeof transaction.from !== "string" || typeof transaction.to !== "string" || typeof transaction.data !== "string" || typeof transaction.value !== "string" || transaction.chainId !== chainId) {
|
|
3012
|
+
throw this.invalidResponse("transaction");
|
|
3013
|
+
}
|
|
3014
|
+
const account = getAddress2(state.walletAddress);
|
|
3015
|
+
if (getAddress2(transaction.from) !== account) {
|
|
3016
|
+
throw this.invalidResponse("transaction", {
|
|
3017
|
+
reason: "transaction.from does not match the connected wallet"
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
const walletClient = createWalletClient2({
|
|
3021
|
+
account,
|
|
3022
|
+
chain: base3,
|
|
3023
|
+
transport: custom2(state.provider)
|
|
3024
|
+
});
|
|
3025
|
+
const publicClient = createPublicClient3({
|
|
3026
|
+
chain: base3,
|
|
3027
|
+
transport: custom2(state.provider)
|
|
3028
|
+
});
|
|
3029
|
+
await ensureWalletOnChain(
|
|
3030
|
+
publicClient,
|
|
3031
|
+
walletClient,
|
|
3032
|
+
8453
|
|
3033
|
+
);
|
|
3034
|
+
const hash = await walletClient.sendTransaction({
|
|
3035
|
+
account,
|
|
3036
|
+
chain: base3,
|
|
3037
|
+
to: getAddress2(transaction.to),
|
|
3038
|
+
data: transaction.data,
|
|
3039
|
+
value: BigInt(transaction.value)
|
|
3040
|
+
});
|
|
3041
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3042
|
+
hash,
|
|
3043
|
+
confirmations: 1
|
|
3044
|
+
});
|
|
3045
|
+
if (receipt.status !== "success") {
|
|
3046
|
+
throw new OwneyError(
|
|
3047
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3048
|
+
`Yieldseeker transaction reverted (${hash}).`,
|
|
3049
|
+
{ transactionHash: hash },
|
|
3050
|
+
this.id
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
return hash;
|
|
3054
|
+
}
|
|
3055
|
+
async waitForUnwindReceipt(state, chainId, transactionHash) {
|
|
3056
|
+
if (this.unwindReceiptWaiter) {
|
|
3057
|
+
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
const publicClient = createPublicClient3({
|
|
3061
|
+
chain: base3,
|
|
3062
|
+
transport: custom2(state.provider)
|
|
3063
|
+
});
|
|
3064
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3065
|
+
hash: transactionHash,
|
|
3066
|
+
confirmations: 1
|
|
3067
|
+
});
|
|
3068
|
+
if (receipt.status !== "success") {
|
|
3069
|
+
throw new OwneyError(
|
|
3070
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3071
|
+
`Yieldseeker position unwind reverted (${transactionHash}).`,
|
|
3072
|
+
{ transactionHash },
|
|
3073
|
+
this.id
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
positionMatchesChain(chain, chainId) {
|
|
3078
|
+
const normalized = chain.trim().toUpperCase();
|
|
3079
|
+
return normalized === String(chainId) || normalized === "BASE";
|
|
3080
|
+
}
|
|
3081
|
+
isTransactionHash(value) {
|
|
3082
|
+
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3083
|
+
}
|
|
3084
|
+
assertChain(chainId) {
|
|
3085
|
+
if (chainId !== 8453) {
|
|
3086
|
+
throw new OwneyError(
|
|
3087
|
+
"CHAIN_UNSUPPORTED",
|
|
3088
|
+
`Yieldseeker does not support chain ${chainId}.`,
|
|
3089
|
+
{ chainId, supportedChainIds: [8453] },
|
|
3090
|
+
this.id
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
assertOptionalChain(chainId) {
|
|
3095
|
+
if (chainId !== void 0) this.assertChain(chainId);
|
|
3096
|
+
}
|
|
3097
|
+
assertAsset(asset) {
|
|
3098
|
+
if (asset !== "USDC" && asset !== "WETH") {
|
|
3099
|
+
throw new OwneyError(
|
|
3100
|
+
"ASSET_UNSUPPORTED",
|
|
3101
|
+
`Yieldseeker does not support asset ${asset}.`,
|
|
3102
|
+
{ asset, supportedAssets: ["USDC", "WETH"] },
|
|
3103
|
+
this.id
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
invalidResponse(operation, details = {}) {
|
|
3108
|
+
return new OwneyError(
|
|
3109
|
+
"AGENT_INVALID_RESPONSE",
|
|
3110
|
+
`Yieldseeker returned an invalid ${operation} response.`,
|
|
3111
|
+
details,
|
|
3112
|
+
this.id
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
|
|
3117
|
+
// src/lib/routing-api.ts
|
|
3118
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3119
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3120
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
3121
|
+
try {
|
|
3122
|
+
const res = await fetch(url, {
|
|
3123
|
+
method: "GET",
|
|
3124
|
+
headers: {
|
|
3125
|
+
"Content-Type": "application/json",
|
|
3126
|
+
"x-owney-api-key": `${apiKey}`
|
|
3127
|
+
}
|
|
3128
|
+
});
|
|
3129
|
+
if (!res.ok) {
|
|
3130
|
+
if (res.status !== 404) {
|
|
3131
|
+
console.warn(
|
|
3132
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
return null;
|
|
3136
|
+
}
|
|
3137
|
+
const json = await res.json();
|
|
3138
|
+
const policy = json.success ? json.data ?? null : null;
|
|
3139
|
+
debugLog(
|
|
3140
|
+
"owney-sdk",
|
|
3141
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
3142
|
+
policy ?? void 0
|
|
3143
|
+
);
|
|
3144
|
+
return policy;
|
|
3145
|
+
} catch (error) {
|
|
3146
|
+
console.warn(
|
|
3147
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
3148
|
+
error instanceof Error ? error.message : String(error)
|
|
3149
|
+
);
|
|
3150
|
+
return null;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3154
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
3155
|
+
const res = await fetch(url, {
|
|
3156
|
+
method: "GET",
|
|
3157
|
+
headers: {
|
|
3158
|
+
"Content-Type": "application/json",
|
|
3159
|
+
"x-owney-api-key": `${apiKey}`
|
|
3160
|
+
}
|
|
3161
|
+
});
|
|
3162
|
+
if (!res.ok) {
|
|
3163
|
+
const text = await res.text().catch(() => "");
|
|
3164
|
+
throw new OwneyError(
|
|
3165
|
+
"API_ROUTING_ERROR",
|
|
3166
|
+
`Routing API error ${res.status}: ${text}`,
|
|
3167
|
+
{ statusCode: res.status, responseBody: text }
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
const json = await res.json();
|
|
3171
|
+
if (!json.success) {
|
|
3172
|
+
throw new OwneyError(
|
|
3173
|
+
"API_ROUTING_FAILED",
|
|
3174
|
+
`Routing API request failed: ${json.message}`,
|
|
3175
|
+
{ message: json.message }
|
|
3176
|
+
);
|
|
3177
|
+
}
|
|
3178
|
+
return json.data;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
// src/lib/health-report.ts
|
|
3182
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3183
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
|
|
3184
|
+
try {
|
|
3185
|
+
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
3186
|
+
method: "POST",
|
|
3187
|
+
headers: {
|
|
3188
|
+
"Content-Type": "application/json",
|
|
3189
|
+
"x-owney-api-key": apiKey
|
|
3190
|
+
},
|
|
3191
|
+
body: JSON.stringify({
|
|
3192
|
+
agent_type: agentType,
|
|
3193
|
+
error_code: errorCode,
|
|
3194
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3195
|
+
})
|
|
3196
|
+
});
|
|
3197
|
+
} catch (err) {
|
|
3198
|
+
console.warn(
|
|
3199
|
+
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
3200
|
+
err instanceof Error ? err.message : err
|
|
3201
|
+
);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
3205
|
+
try {
|
|
3206
|
+
return await fn();
|
|
3207
|
+
} catch (err) {
|
|
3208
|
+
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
3209
|
+
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
3210
|
+
throw err;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
3215
|
+
import { parseUnits as parseUnits2 } from "viem";
|
|
3216
|
+
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
3217
|
+
const target = asset.toUpperCase();
|
|
3218
|
+
return agents.map((agent) => {
|
|
3219
|
+
const agentBalance = aggregated[agent.id];
|
|
3220
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
3221
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
3222
|
+
);
|
|
3223
|
+
let balance = tokenBalance ? parseUnits2(tokenBalance.amount, decimals) : 0n;
|
|
3224
|
+
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
3225
|
+
const chainNameById = {
|
|
3226
|
+
1: "ETHEREUM",
|
|
3227
|
+
8453: "BASE",
|
|
3228
|
+
42161: "ARBITRUM"
|
|
3229
|
+
};
|
|
3230
|
+
const targetChain = chainNameById[chainId];
|
|
3231
|
+
for (const position2 of agentBalance?.positions ?? []) {
|
|
3232
|
+
const positionChain = position2.chain.trim().toUpperCase();
|
|
3233
|
+
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
3234
|
+
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
3235
|
+
if (position2.amountRaw !== void 0) {
|
|
3236
|
+
try {
|
|
3237
|
+
balance += BigInt(position2.amountRaw);
|
|
3238
|
+
continue;
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
balance += parseUnits2(position2.amount, decimals);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
return { agent, balance };
|
|
3246
|
+
});
|
|
3247
|
+
}
|
|
3248
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
3249
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
3250
|
+
agent,
|
|
3251
|
+
balance,
|
|
3252
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
3253
|
+
}));
|
|
3254
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
3255
|
+
let remainder = requested - assigned;
|
|
3256
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
3257
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
3258
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2281
3259
|
});
|
|
3260
|
+
for (const p of byHeadroom) {
|
|
3261
|
+
if (remainder === 0n) break;
|
|
3262
|
+
const headroom = p.balance - p.planned;
|
|
3263
|
+
if (headroom <= 0n) continue;
|
|
3264
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
3265
|
+
p.planned += take;
|
|
3266
|
+
remainder -= take;
|
|
3267
|
+
}
|
|
3268
|
+
return plans;
|
|
2282
3269
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
|
|
3270
|
+
function planDisabledDrain(disabled, requested) {
|
|
3271
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
3272
|
+
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
3273
|
+
);
|
|
3274
|
+
const plans = [];
|
|
3275
|
+
let remaining = requested;
|
|
3276
|
+
for (const { agent, balance } of sorted) {
|
|
3277
|
+
if (remaining === 0n) {
|
|
3278
|
+
plans.push({ agent, balance, planned: 0n });
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
const take = balance < remaining ? balance : remaining;
|
|
3282
|
+
plans.push({ agent, balance, planned: take });
|
|
3283
|
+
remaining -= take;
|
|
3284
|
+
}
|
|
3285
|
+
return { plans, remaining };
|
|
2292
3286
|
}
|
|
2293
|
-
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
3287
|
+
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
3288
|
+
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
3289
|
+
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
3290
|
+
const totalHeadroom = candidates.reduce(
|
|
3291
|
+
(s, c) => s + (c.balance - c.planned),
|
|
3292
|
+
0n
|
|
3293
|
+
);
|
|
3294
|
+
if (totalHeadroom === 0n) return;
|
|
3295
|
+
let distributed = 0n;
|
|
3296
|
+
for (const c of candidates) {
|
|
3297
|
+
const headroom = c.balance - c.planned;
|
|
3298
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
3299
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
3300
|
+
c.planned += give;
|
|
3301
|
+
distributed += give;
|
|
2308
3302
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
3303
|
+
let leftover = amount - distributed;
|
|
3304
|
+
for (const c of candidates) {
|
|
3305
|
+
if (leftover === 0n) break;
|
|
3306
|
+
const headroom = c.balance - c.planned;
|
|
3307
|
+
if (headroom <= 0n) continue;
|
|
3308
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
3309
|
+
c.planned += take;
|
|
3310
|
+
leftover -= take;
|
|
2316
3311
|
}
|
|
2317
3312
|
}
|
|
3313
|
+
function sumWithdrawnAmount(results) {
|
|
3314
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
3315
|
+
}
|
|
2318
3316
|
|
|
2319
|
-
// src/lib/
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
`No sponsored token configured for chain ${chainId}`
|
|
2330
|
-
);
|
|
3317
|
+
// src/lib/helpers/account-apy-helper.ts
|
|
3318
|
+
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
3319
|
+
if (!tokenSymbol) {
|
|
3320
|
+
const total = Number(balance.totalBalance);
|
|
3321
|
+
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
3322
|
+
}
|
|
3323
|
+
const normalizedToken = tokenSymbol.toUpperCase();
|
|
3324
|
+
return balance.tokens.reduce((total, token2) => {
|
|
3325
|
+
if (Number(token2.chainId) !== chainId || String(token2.asset).toUpperCase() !== normalizedToken) {
|
|
3326
|
+
return total;
|
|
2331
3327
|
}
|
|
2332
|
-
const
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
account: deps.ownerAddress,
|
|
2373
|
-
...typedData
|
|
2374
|
-
});
|
|
2375
|
-
deps.onApproved?.();
|
|
2376
|
-
const result = await post({
|
|
2377
|
-
baseUrl: deps.baseUrl,
|
|
2378
|
-
apiKey: deps.apiKey,
|
|
2379
|
-
body: {
|
|
2380
|
-
chainId: cid,
|
|
2381
|
-
token,
|
|
2382
|
-
from: deps.ownerAddress,
|
|
2383
|
-
to: smartWallet,
|
|
2384
|
-
value: amount,
|
|
2385
|
-
validAfter: validAfter.toString(),
|
|
2386
|
-
validBefore: validBefore.toString(),
|
|
2387
|
-
nonce,
|
|
2388
|
-
authSignature,
|
|
2389
|
-
tokenName,
|
|
2390
|
-
tokenVersion
|
|
3328
|
+
const amount = Number(token2.amount);
|
|
3329
|
+
return Number.isFinite(amount) && amount > 0 ? total + amount : total;
|
|
3330
|
+
}, 0);
|
|
3331
|
+
}
|
|
3332
|
+
function aggregateApyHistory(agentApys, agentBalances) {
|
|
3333
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
3334
|
+
for (const [id, accountApy] of Object.entries(agentApys)) {
|
|
3335
|
+
const balance = agentBalances[id] ?? 0;
|
|
3336
|
+
if (!Number.isFinite(balance) || balance <= 0) continue;
|
|
3337
|
+
for (const point of accountApy.history ?? []) {
|
|
3338
|
+
const apy = Number(point.apy);
|
|
3339
|
+
if (!point.date || !Number.isFinite(apy)) continue;
|
|
3340
|
+
const current = byDate.get(point.date) ?? { weightedSum: 0, weight: 0 };
|
|
3341
|
+
current.weightedSum += apy * balance;
|
|
3342
|
+
current.weight += balance;
|
|
3343
|
+
byDate.set(point.date, current);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
3347
|
+
date,
|
|
3348
|
+
apy: value.weightedSum / value.weight
|
|
3349
|
+
}));
|
|
3350
|
+
}
|
|
3351
|
+
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
3352
|
+
const sums = {};
|
|
3353
|
+
const weights = {};
|
|
3354
|
+
for (const id of Object.keys(agentApys)) {
|
|
3355
|
+
const cells = agentApys[id].apyByChainAndAsset;
|
|
3356
|
+
const balance = agentBalances[id] ?? 0;
|
|
3357
|
+
if (!cells || balance <= 0) continue;
|
|
3358
|
+
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
3359
|
+
if (!perAsset) continue;
|
|
3360
|
+
const chainId = Number(chainKey);
|
|
3361
|
+
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
3362
|
+
const apy = Number(apyValue ?? 0);
|
|
3363
|
+
if (apy === 0) continue;
|
|
3364
|
+
sums[chainId] ??= {};
|
|
3365
|
+
weights[chainId] ??= {};
|
|
3366
|
+
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
3367
|
+
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2391
3368
|
}
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
};
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
const out = {};
|
|
3372
|
+
for (const chainKey of Object.keys(sums)) {
|
|
3373
|
+
const chainId = Number(chainKey);
|
|
3374
|
+
const perAssetOut = {};
|
|
3375
|
+
for (const asset of Object.keys(sums[chainId])) {
|
|
3376
|
+
const w = weights[chainId][asset];
|
|
3377
|
+
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
3378
|
+
}
|
|
3379
|
+
if (Object.keys(perAssetOut).length > 0) {
|
|
3380
|
+
out[chainId] = perAssetOut;
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
return out;
|
|
2395
3384
|
}
|
|
2396
3385
|
|
|
3386
|
+
// src/client.ts
|
|
3387
|
+
import {
|
|
3388
|
+
createPublicClient as createPublicClient4,
|
|
3389
|
+
createWalletClient as createWalletClient3,
|
|
3390
|
+
custom as custom3
|
|
3391
|
+
} from "viem";
|
|
3392
|
+
import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
3393
|
+
|
|
2397
3394
|
// src/lib/sponsored-weth-deposit.ts
|
|
2398
3395
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2399
3396
|
function makeSponsoredWethCallback(deps) {
|
|
2400
3397
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2401
3398
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2402
|
-
return
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
"CHAIN_UNSUPPORTED",
|
|
2408
|
-
`No sponsored WETH configured for chain ${chainId}`
|
|
2409
|
-
);
|
|
2410
|
-
}
|
|
2411
|
-
const amountWei = BigInt(amount);
|
|
2412
|
-
const pub = deps.getPublicClient(cid);
|
|
2413
|
-
const wallet = deps.getWalletClient(cid);
|
|
2414
|
-
await ensureWalletOnChain(pub, wallet, cid);
|
|
2415
|
-
try {
|
|
2416
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2417
|
-
if (balance < amountWei) {
|
|
3399
|
+
return makeVerificationAwareDepositCallback(
|
|
3400
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
3401
|
+
const cid = chainId;
|
|
3402
|
+
const token2 = deps.tokenAddressByChain[cid];
|
|
3403
|
+
if (!token2) {
|
|
2418
3404
|
throw new OwneyError(
|
|
2419
|
-
"
|
|
2420
|
-
|
|
2421
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
3405
|
+
"CHAIN_UNSUPPORTED",
|
|
3406
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2422
3407
|
);
|
|
2423
3408
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
3409
|
+
const amountWei = BigInt(amount);
|
|
3410
|
+
const pub = deps.getPublicClient(cid);
|
|
3411
|
+
const wallet = deps.getWalletClient(cid);
|
|
3412
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
3413
|
+
try {
|
|
3414
|
+
const balance = await readErc20Balance(pub, token2, deps.ownerAddress);
|
|
3415
|
+
if (balance < amountWei) {
|
|
3416
|
+
throw new OwneyError(
|
|
3417
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
3418
|
+
"Insufficient WETH balance for this deposit.",
|
|
3419
|
+
{ token: token2, chainId: cid, balance: balance.toString(), amount }
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
} catch (err) {
|
|
3423
|
+
if (err instanceof OwneyError) throw err;
|
|
3424
|
+
console.warn(
|
|
3425
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
3426
|
+
err instanceof Error ? err.message : String(err)
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
const allowance = await readPermit2Allowance(
|
|
3430
|
+
pub,
|
|
3431
|
+
token2,
|
|
3432
|
+
deps.ownerAddress
|
|
2437
3433
|
);
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
const nonce = randomPermit2Nonce();
|
|
2445
|
-
const deadline = BigInt(
|
|
2446
|
-
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
2447
|
-
);
|
|
2448
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
2449
|
-
chainId: cid,
|
|
2450
|
-
message: {
|
|
2451
|
-
permitted: { token, amount: amountWei },
|
|
2452
|
-
spender: relayer,
|
|
2453
|
-
nonce,
|
|
2454
|
-
deadline
|
|
3434
|
+
if (allowance < amountWei) {
|
|
3435
|
+
throw new OwneyError(
|
|
3436
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
3437
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
3438
|
+
{ token: token2, chainId: cid, allowance: allowance.toString(), amount }
|
|
3439
|
+
);
|
|
2455
3440
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
3441
|
+
const relayer = await get({
|
|
3442
|
+
baseUrl: deps.baseUrl,
|
|
3443
|
+
apiKey: deps.apiKey,
|
|
3444
|
+
chainId: cid
|
|
3445
|
+
});
|
|
3446
|
+
const nonce = randomPermit2Nonce();
|
|
3447
|
+
const deadline = BigInt(
|
|
3448
|
+
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
3449
|
+
);
|
|
3450
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
2466
3451
|
chainId: cid,
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
3452
|
+
message: {
|
|
3453
|
+
permitted: { token: token2, amount: amountWei },
|
|
3454
|
+
spender: relayer,
|
|
3455
|
+
nonce,
|
|
3456
|
+
deadline
|
|
3457
|
+
}
|
|
3458
|
+
});
|
|
3459
|
+
const signature = await wallet.signTypedData({
|
|
3460
|
+
account: deps.ownerAddress,
|
|
3461
|
+
...typedData
|
|
3462
|
+
});
|
|
3463
|
+
deps.onApproved?.();
|
|
3464
|
+
const result = await post({
|
|
3465
|
+
baseUrl: deps.baseUrl,
|
|
3466
|
+
apiKey: deps.apiKey,
|
|
3467
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
3468
|
+
body: {
|
|
3469
|
+
chainId: cid,
|
|
3470
|
+
token: token2,
|
|
3471
|
+
from: deps.ownerAddress,
|
|
3472
|
+
to: smartWallet,
|
|
3473
|
+
amount,
|
|
3474
|
+
nonce: nonce.toString(),
|
|
3475
|
+
deadline: deadline.toString(),
|
|
3476
|
+
signature
|
|
3477
|
+
}
|
|
3478
|
+
});
|
|
3479
|
+
return result.txHash;
|
|
3480
|
+
}
|
|
3481
|
+
);
|
|
2478
3482
|
}
|
|
2479
3483
|
|
|
2480
3484
|
// src/lib/sponsored-calls-deposit.ts
|
|
@@ -2506,8 +3510,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2506
3510
|
};
|
|
2507
3511
|
return async (smartWallet, chainId, amount) => {
|
|
2508
3512
|
const cid = chainId;
|
|
2509
|
-
const
|
|
2510
|
-
if (!
|
|
3513
|
+
const token2 = deps.tokenAddressByChain[cid];
|
|
3514
|
+
if (!token2) {
|
|
2511
3515
|
throw new OwneyError(
|
|
2512
3516
|
"CHAIN_UNSUPPORTED",
|
|
2513
3517
|
`No sponsored token configured for chain ${chainId}`
|
|
@@ -2533,7 +3537,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2533
3537
|
from: deps.ownerAddress,
|
|
2534
3538
|
chainId: toHex(chainId),
|
|
2535
3539
|
atomicRequired: false,
|
|
2536
|
-
calls: [{ to:
|
|
3540
|
+
calls: [{ to: token2, value: "0x0", data }],
|
|
2537
3541
|
capabilities: {
|
|
2538
3542
|
paymasterService: { url: absolutePaymasterUrl() }
|
|
2539
3543
|
}
|
|
@@ -2572,10 +3576,10 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2572
3576
|
function encodeMultiAgentCursor(map) {
|
|
2573
3577
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
2574
3578
|
}
|
|
2575
|
-
function decodeMultiAgentCursor(
|
|
3579
|
+
function decodeMultiAgentCursor(token2) {
|
|
2576
3580
|
let parsed;
|
|
2577
3581
|
try {
|
|
2578
|
-
const json = Buffer.from(
|
|
3582
|
+
const json = Buffer.from(token2, "base64").toString("utf8");
|
|
2579
3583
|
parsed = JSON.parse(json);
|
|
2580
3584
|
} catch {
|
|
2581
3585
|
throw new InvalidHistoryCursorError(
|
|
@@ -2595,7 +3599,7 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
2595
3599
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
2596
3600
|
};
|
|
2597
3601
|
var VIEM_CHAIN2 = {
|
|
2598
|
-
8453:
|
|
3602
|
+
8453: base4,
|
|
2599
3603
|
42161: arbitrum2,
|
|
2600
3604
|
1: mainnet2
|
|
2601
3605
|
};
|
|
@@ -2625,6 +3629,7 @@ var OwneySDK = class {
|
|
|
2625
3629
|
orgAgentConfig;
|
|
2626
3630
|
orgAgentConfigPromise = null;
|
|
2627
3631
|
zyfaiRpcUrls;
|
|
3632
|
+
yieldseekerApiBaseUrl;
|
|
2628
3633
|
routingApiBaseUrl;
|
|
2629
3634
|
referralSource;
|
|
2630
3635
|
cachedSponsoredCallback = null;
|
|
@@ -2636,6 +3641,7 @@ var OwneySDK = class {
|
|
|
2636
3641
|
this.apiKey = config.apiKey;
|
|
2637
3642
|
if (config.debug) setOwneyDebug(true);
|
|
2638
3643
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
3644
|
+
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
2639
3645
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
2640
3646
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
2641
3647
|
this.referralSource = config.referralSource;
|
|
@@ -2738,14 +3744,14 @@ var OwneySDK = class {
|
|
|
2738
3744
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2739
3745
|
// PublicClient/WalletClient param types — structurally identical at
|
|
2740
3746
|
// runtime, but the two share a name TS treats as unrelated.
|
|
2741
|
-
getPublicClient: (cid) =>
|
|
3747
|
+
getPublicClient: (cid) => createPublicClient4({
|
|
2742
3748
|
chain: VIEM_CHAIN2[cid],
|
|
2743
|
-
transport:
|
|
3749
|
+
transport: custom3(provider)
|
|
2744
3750
|
}),
|
|
2745
|
-
getWalletClient: (cid) =>
|
|
3751
|
+
getWalletClient: (cid) => createWalletClient3({
|
|
2746
3752
|
account: owner,
|
|
2747
3753
|
chain: VIEM_CHAIN2[cid],
|
|
2748
|
-
transport:
|
|
3754
|
+
transport: custom3(provider)
|
|
2749
3755
|
})
|
|
2750
3756
|
});
|
|
2751
3757
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
@@ -2791,14 +3797,14 @@ var OwneySDK = class {
|
|
|
2791
3797
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2792
3798
|
// PublicClient/WalletClient param types — structurally identical at
|
|
2793
3799
|
// runtime, but the two share a name TS treats as unrelated.
|
|
2794
|
-
getPublicClient: (cid) =>
|
|
3800
|
+
getPublicClient: (cid) => createPublicClient4({
|
|
2795
3801
|
chain: VIEM_CHAIN2[cid],
|
|
2796
|
-
transport:
|
|
3802
|
+
transport: custom3(provider)
|
|
2797
3803
|
}),
|
|
2798
|
-
getWalletClient: (cid) =>
|
|
3804
|
+
getWalletClient: (cid) => createWalletClient3({
|
|
2799
3805
|
account: owner,
|
|
2800
3806
|
chain: VIEM_CHAIN2[cid],
|
|
2801
|
-
transport:
|
|
3807
|
+
transport: custom3(provider)
|
|
2802
3808
|
})
|
|
2803
3809
|
});
|
|
2804
3810
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -2829,12 +3835,10 @@ var OwneySDK = class {
|
|
|
2829
3835
|
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
2830
3836
|
this.apiKey,
|
|
2831
3837
|
this.routingApiBaseUrl
|
|
2832
|
-
).then(
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
}
|
|
2837
|
-
);
|
|
3838
|
+
).then((config) => {
|
|
3839
|
+
this.orgAgentConfig = config;
|
|
3840
|
+
return config;
|
|
3841
|
+
});
|
|
2838
3842
|
}
|
|
2839
3843
|
return this.orgAgentConfigPromise;
|
|
2840
3844
|
}
|
|
@@ -2874,7 +3878,14 @@ var OwneySDK = class {
|
|
|
2874
3878
|
this.routingApiBaseUrl
|
|
2875
3879
|
);
|
|
2876
3880
|
this.disabledAgents.clear();
|
|
2877
|
-
for (const {
|
|
3881
|
+
for (const {
|
|
3882
|
+
key: key2,
|
|
3883
|
+
agent_type,
|
|
3884
|
+
is_enabled,
|
|
3885
|
+
is_configured
|
|
3886
|
+
} of agentKeys) {
|
|
3887
|
+
const configured = is_configured ?? Boolean(key2);
|
|
3888
|
+
if (!configured) continue;
|
|
2878
3889
|
const agent = this.createAgent(agent_type, key2);
|
|
2879
3890
|
if (!agent) continue;
|
|
2880
3891
|
this.agents.set(agent_type, agent);
|
|
@@ -2898,10 +3909,47 @@ var OwneySDK = class {
|
|
|
2898
3909
|
}
|
|
2899
3910
|
createAgent(agentId, key2) {
|
|
2900
3911
|
if (agentId === "zyfai") {
|
|
3912
|
+
if (!key2) return null;
|
|
2901
3913
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
2902
3914
|
}
|
|
3915
|
+
if (agentId === "yieldseeker") {
|
|
3916
|
+
return new YieldseekerAgent(this.apiKey, {
|
|
3917
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
2903
3920
|
return null;
|
|
2904
3921
|
}
|
|
3922
|
+
/**
|
|
3923
|
+
* Discover the agents actually returned by routing for this organization.
|
|
3924
|
+
* Consumers should use this instead of hardcoding a global agent roster.
|
|
3925
|
+
*/
|
|
3926
|
+
async getAvailableAgents(options = {}) {
|
|
3927
|
+
await this.ensureAgentsInitialized();
|
|
3928
|
+
const { chainId, asset, includeDisabled = false } = options;
|
|
3929
|
+
const available = [];
|
|
3930
|
+
for (const [id, agent] of this.agents) {
|
|
3931
|
+
const isEnabled = !this.isAgentDisabled(id);
|
|
3932
|
+
if (!includeDisabled && !isEnabled) continue;
|
|
3933
|
+
if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
|
|
3934
|
+
continue;
|
|
3935
|
+
}
|
|
3936
|
+
if (asset !== void 0) {
|
|
3937
|
+
const supportsAsset = agent.supportedAssets.some(
|
|
3938
|
+
(entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
|
|
3939
|
+
);
|
|
3940
|
+
if (!supportsAsset) {
|
|
3941
|
+
continue;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
available.push({
|
|
3945
|
+
id,
|
|
3946
|
+
isEnabled,
|
|
3947
|
+
supportedChainIds: agent.supportedChainIds,
|
|
3948
|
+
supportedAssets: agent.supportedAssets
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3951
|
+
return available;
|
|
3952
|
+
}
|
|
2905
3953
|
// --- Account lifecycle ---
|
|
2906
3954
|
/**
|
|
2907
3955
|
* Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
|
|
@@ -2913,7 +3961,7 @@ var OwneySDK = class {
|
|
|
2913
3961
|
* If provided, ALL specified agents must support the chainId or the call
|
|
2914
3962
|
* throws before activating any agent.
|
|
2915
3963
|
*/
|
|
2916
|
-
async activateAgent(chainId, agentId) {
|
|
3964
|
+
async activateAgent(chainId, agentId, asset) {
|
|
2917
3965
|
const state = this.requireState();
|
|
2918
3966
|
await this.ensureAgentsInitialized();
|
|
2919
3967
|
if (agentId !== void 0) {
|
|
@@ -2949,9 +3997,7 @@ var OwneySDK = class {
|
|
|
2949
3997
|
this.activeAgents.add(id);
|
|
2950
3998
|
}
|
|
2951
3999
|
state.chainId = chainId;
|
|
2952
|
-
this.activateAgentsInTurn(agents, state, chainId)
|
|
2953
|
-
console.error("activateAgent background init failed:", error);
|
|
2954
|
-
});
|
|
4000
|
+
await this.activateAgentsInTurn(agents, state, chainId, asset);
|
|
2955
4001
|
return;
|
|
2956
4002
|
}
|
|
2957
4003
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -2972,11 +4018,7 @@ var OwneySDK = class {
|
|
|
2972
4018
|
const enabledCompatible = compatible.filter(
|
|
2973
4019
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
2974
4020
|
);
|
|
2975
|
-
this.activateAgentsInTurn(enabledCompatible, state, chainId)
|
|
2976
|
-
(error) => {
|
|
2977
|
-
console.error("activateAgent background init failed:", error);
|
|
2978
|
-
}
|
|
2979
|
-
);
|
|
4021
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
|
|
2980
4022
|
}
|
|
2981
4023
|
/**
|
|
2982
4024
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -2996,11 +4038,11 @@ var OwneySDK = class {
|
|
|
2996
4038
|
* rethrown (matching the previous `Promise.all` rejection) once all agents
|
|
2997
4039
|
* have had a chance to activate.
|
|
2998
4040
|
*/
|
|
2999
|
-
async activateAgentsInTurn(agents, state, chainId) {
|
|
4041
|
+
async activateAgentsInTurn(agents, state, chainId, asset) {
|
|
3000
4042
|
let firstError = null;
|
|
3001
4043
|
for (const agent of agents) {
|
|
3002
4044
|
try {
|
|
3003
|
-
await agent.activateAgent(state, chainId);
|
|
4045
|
+
await agent.activateAgent(state, chainId, asset);
|
|
3004
4046
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
3005
4047
|
} catch (error) {
|
|
3006
4048
|
if (firstError === null) {
|
|
@@ -3209,10 +4251,10 @@ var OwneySDK = class {
|
|
|
3209
4251
|
try {
|
|
3210
4252
|
const balance = await agent.getBalances(state, chainId);
|
|
3211
4253
|
const target = asset.toLowerCase();
|
|
3212
|
-
const
|
|
4254
|
+
const token2 = balance.tokens.find(
|
|
3213
4255
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
3214
4256
|
);
|
|
3215
|
-
return !!
|
|
4257
|
+
return !!token2 && Number(token2.amount) > 0;
|
|
3216
4258
|
} catch {
|
|
3217
4259
|
return false;
|
|
3218
4260
|
}
|
|
@@ -3263,9 +4305,9 @@ var OwneySDK = class {
|
|
|
3263
4305
|
const { asset, amount, agentId } = options;
|
|
3264
4306
|
const state = this.requireState();
|
|
3265
4307
|
const chainId = this.requireChainId();
|
|
3266
|
-
const
|
|
4308
|
+
const token2 = asset;
|
|
3267
4309
|
const assetInfo = SupportedAssets.find(
|
|
3268
|
-
(a) => a.chainId === chainId && a.symbol ===
|
|
4310
|
+
(a) => a.chainId === chainId && a.symbol === token2
|
|
3269
4311
|
);
|
|
3270
4312
|
if (!assetInfo) {
|
|
3271
4313
|
throw new OwneyError(
|
|
@@ -3279,7 +4321,7 @@ var OwneySDK = class {
|
|
|
3279
4321
|
return withFailureReporting(
|
|
3280
4322
|
this.apiKey,
|
|
3281
4323
|
agent.id,
|
|
3282
|
-
() => agent.withdraw(state, chainId,
|
|
4324
|
+
() => agent.withdraw(state, chainId, token2, amount),
|
|
3283
4325
|
this.routingApiBaseUrl
|
|
3284
4326
|
);
|
|
3285
4327
|
}
|
|
@@ -3289,7 +4331,7 @@ var OwneySDK = class {
|
|
|
3289
4331
|
const agentErrors2 = {};
|
|
3290
4332
|
for (const agent of eligibleAgents) {
|
|
3291
4333
|
try {
|
|
3292
|
-
results2[agent.id] = await agent.withdraw(state, chainId,
|
|
4334
|
+
results2[agent.id] = await agent.withdraw(state, chainId, token2);
|
|
3293
4335
|
} catch (err) {
|
|
3294
4336
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
3295
4337
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
@@ -3316,6 +4358,10 @@ var OwneySDK = class {
|
|
|
3316
4358
|
}
|
|
3317
4359
|
const requested = BigInt(amount);
|
|
3318
4360
|
const aggregated = await this.getBalances();
|
|
4361
|
+
const unavailableAgents = eligibleAgents.filter(
|
|
4362
|
+
(agent) => !(agent.id in aggregated.agentBalances)
|
|
4363
|
+
);
|
|
4364
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
3319
4365
|
const balances = projectAgentBalancesForAsset(
|
|
3320
4366
|
eligibleAgents,
|
|
3321
4367
|
aggregated.agentBalances,
|
|
@@ -3324,7 +4370,18 @@ var OwneySDK = class {
|
|
|
3324
4370
|
assetInfo.decimals
|
|
3325
4371
|
);
|
|
3326
4372
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
3327
|
-
if (totalAvailable
|
|
4373
|
+
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
4374
|
+
throw new OwneyError(
|
|
4375
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
4376
|
+
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
4377
|
+
{
|
|
4378
|
+
asset,
|
|
4379
|
+
unavailableAgents: unavailableAgentIds,
|
|
4380
|
+
agentErrors: aggregated.agentErrors
|
|
4381
|
+
}
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
3328
4385
|
throw new OwneyError(
|
|
3329
4386
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3330
4387
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -3335,6 +4392,7 @@ var OwneySDK = class {
|
|
|
3335
4392
|
}
|
|
3336
4393
|
);
|
|
3337
4394
|
}
|
|
4395
|
+
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
3338
4396
|
const disabledBalances = balances.filter(
|
|
3339
4397
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
3340
4398
|
);
|
|
@@ -3343,7 +4401,7 @@ var OwneySDK = class {
|
|
|
3343
4401
|
);
|
|
3344
4402
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
3345
4403
|
disabledBalances,
|
|
3346
|
-
|
|
4404
|
+
plannedTarget
|
|
3347
4405
|
);
|
|
3348
4406
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
3349
4407
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -3353,7 +4411,9 @@ var OwneySDK = class {
|
|
|
3353
4411
|
}));
|
|
3354
4412
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
3355
4413
|
const results = {};
|
|
3356
|
-
const agentErrors = {
|
|
4414
|
+
const agentErrors = {
|
|
4415
|
+
...aggregated.agentErrors ?? {}
|
|
4416
|
+
};
|
|
3357
4417
|
for (let i = 0; i < plans.length; i++) {
|
|
3358
4418
|
const p = plans[i];
|
|
3359
4419
|
if (p.planned === 0n) continue;
|
|
@@ -3361,7 +4421,7 @@ var OwneySDK = class {
|
|
|
3361
4421
|
results[p.agent.id] = await p.agent.withdraw(
|
|
3362
4422
|
state,
|
|
3363
4423
|
chainId,
|
|
3364
|
-
|
|
4424
|
+
token2,
|
|
3365
4425
|
p.planned.toString()
|
|
3366
4426
|
);
|
|
3367
4427
|
} catch (err) {
|
|
@@ -3400,7 +4460,8 @@ var OwneySDK = class {
|
|
|
3400
4460
|
requested: amount,
|
|
3401
4461
|
withdrawn: withdrawn.toString(),
|
|
3402
4462
|
partialResults: results,
|
|
3403
|
-
agentErrors
|
|
4463
|
+
agentErrors,
|
|
4464
|
+
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
3404
4465
|
}
|
|
3405
4466
|
);
|
|
3406
4467
|
}
|
|
@@ -3441,7 +4502,12 @@ var OwneySDK = class {
|
|
|
3441
4502
|
continue;
|
|
3442
4503
|
}
|
|
3443
4504
|
const reason = settledResult.reason;
|
|
3444
|
-
|
|
4505
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
4506
|
+
agentErrors[agentId2] = message;
|
|
4507
|
+
console.error(
|
|
4508
|
+
`[owney-sdk] Balance fetch failed for agent "${agentId2}":`,
|
|
4509
|
+
reason
|
|
4510
|
+
);
|
|
3445
4511
|
}
|
|
3446
4512
|
if (successCount === 0) {
|
|
3447
4513
|
throw new OwneyError(
|
|
@@ -3453,7 +4519,8 @@ var OwneySDK = class {
|
|
|
3453
4519
|
return {
|
|
3454
4520
|
totalBalance: String(totalBalance),
|
|
3455
4521
|
totalBalanceAsset: "usdc",
|
|
3456
|
-
agentBalances: results
|
|
4522
|
+
agentBalances: results,
|
|
4523
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors } : {}
|
|
3457
4524
|
};
|
|
3458
4525
|
}
|
|
3459
4526
|
/**
|
|
@@ -3547,7 +4614,10 @@ var OwneySDK = class {
|
|
|
3547
4614
|
Promise.all(
|
|
3548
4615
|
entries.map(async ([id, agent]) => {
|
|
3549
4616
|
const b = await agent.getBalances(state, chainId);
|
|
3550
|
-
return [
|
|
4617
|
+
return [
|
|
4618
|
+
id,
|
|
4619
|
+
balanceForApyScope(b, chainId, tokenSymbol)
|
|
4620
|
+
];
|
|
3551
4621
|
})
|
|
3552
4622
|
)
|
|
3553
4623
|
]);
|
|
@@ -3575,10 +4645,12 @@ var OwneySDK = class {
|
|
|
3575
4645
|
}
|
|
3576
4646
|
}
|
|
3577
4647
|
const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
|
|
4648
|
+
const history = aggregateApyHistory(results, balances);
|
|
3578
4649
|
return {
|
|
3579
4650
|
totalApy: String(totalApy),
|
|
3580
4651
|
agentApy: results,
|
|
3581
|
-
apyByChainAndAsset
|
|
4652
|
+
apyByChainAndAsset,
|
|
4653
|
+
history
|
|
3582
4654
|
};
|
|
3583
4655
|
}
|
|
3584
4656
|
/**
|
|
@@ -3700,30 +4772,30 @@ var OwneySDK = class {
|
|
|
3700
4772
|
const state = this.requireState();
|
|
3701
4773
|
const chainId = this.requireChainId();
|
|
3702
4774
|
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
3703
|
-
const
|
|
3704
|
-
if (!
|
|
4775
|
+
const token2 = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
4776
|
+
if (!token2) {
|
|
3705
4777
|
throw new OwneyError(
|
|
3706
4778
|
"CHAIN_UNSUPPORTED",
|
|
3707
4779
|
`No sponsored WETH on chain ${chainId}`
|
|
3708
4780
|
);
|
|
3709
4781
|
}
|
|
3710
4782
|
const provider = this.requireConnectedProvider();
|
|
3711
|
-
const wallet =
|
|
4783
|
+
const wallet = createWalletClient3({
|
|
3712
4784
|
account: state.walletAddress,
|
|
3713
4785
|
chain: VIEM_CHAIN2[chainId],
|
|
3714
|
-
transport:
|
|
4786
|
+
transport: custom3(provider)
|
|
3715
4787
|
});
|
|
3716
4788
|
const hash = await wallet.writeContract({
|
|
3717
|
-
address:
|
|
4789
|
+
address: token2,
|
|
3718
4790
|
abi: ERC20_ALLOWANCE_ABI,
|
|
3719
4791
|
functionName: "approve",
|
|
3720
4792
|
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
3721
4793
|
account: state.walletAddress,
|
|
3722
4794
|
chain: VIEM_CHAIN2[chainId]
|
|
3723
4795
|
});
|
|
3724
|
-
const publicClient =
|
|
4796
|
+
const publicClient = createPublicClient4({
|
|
3725
4797
|
chain: VIEM_CHAIN2[chainId],
|
|
3726
|
-
transport:
|
|
4798
|
+
transport: custom3(provider)
|
|
3727
4799
|
});
|
|
3728
4800
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3729
4801
|
hash,
|
|
@@ -3759,7 +4831,9 @@ var OwneySDK = class {
|
|
|
3759
4831
|
return this.getAgent(agentId).getAgentApy(days, agentOptions);
|
|
3760
4832
|
}
|
|
3761
4833
|
const results = {};
|
|
3762
|
-
const agentEntries = [...this.agents.entries()]
|
|
4834
|
+
const agentEntries = [...this.agents.entries()].filter(
|
|
4835
|
+
([id]) => !this.isAgentDisabled(id)
|
|
4836
|
+
);
|
|
3763
4837
|
const apyResults = await Promise.all(
|
|
3764
4838
|
agentEntries.map(async ([id, agent]) => {
|
|
3765
4839
|
const apy = await agent.getAgentApy(days, agentOptions);
|
|
@@ -3832,13 +4906,13 @@ var OwneySDK = class {
|
|
|
3832
4906
|
};
|
|
3833
4907
|
|
|
3834
4908
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
3835
|
-
import { getAddress } from "viem";
|
|
3836
|
-
import { SiweMessage } from "siwe";
|
|
4909
|
+
import { getAddress as getAddress3 } from "viem";
|
|
4910
|
+
import { SiweMessage as SiweMessage2 } from "siwe";
|
|
3837
4911
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
3838
4912
|
|
|
3839
4913
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
3840
|
-
var
|
|
3841
|
-
var
|
|
4914
|
+
var KEY_PREFIX3 = "owney.siwx.session";
|
|
4915
|
+
var storage3 = () => {
|
|
3842
4916
|
if (typeof window === "undefined") return null;
|
|
3843
4917
|
try {
|
|
3844
4918
|
return window.localStorage;
|
|
@@ -3846,8 +4920,8 @@ var storage2 = () => {
|
|
|
3846
4920
|
return null;
|
|
3847
4921
|
}
|
|
3848
4922
|
};
|
|
3849
|
-
var
|
|
3850
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
4923
|
+
var buildKey3 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
|
|
4924
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
|
|
3851
4925
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
3852
4926
|
var readLegacySiwxSession = (store, address) => {
|
|
3853
4927
|
if (!store) return null;
|
|
@@ -3878,8 +4952,8 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
3878
4952
|
};
|
|
3879
4953
|
var readSiwxSession = (address, chainId) => {
|
|
3880
4954
|
if (typeof window === "undefined") return null;
|
|
3881
|
-
const key2 =
|
|
3882
|
-
const store =
|
|
4955
|
+
const key2 = buildKey3(address);
|
|
4956
|
+
const store = storage3();
|
|
3883
4957
|
let raw = null;
|
|
3884
4958
|
try {
|
|
3885
4959
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -3907,18 +4981,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
3907
4981
|
};
|
|
3908
4982
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
3909
4983
|
if (typeof window === "undefined") return;
|
|
3910
|
-
const key2 =
|
|
4984
|
+
const key2 = buildKey3(address);
|
|
3911
4985
|
memorySiwxSessions.set(key2, session);
|
|
3912
|
-
const store =
|
|
4986
|
+
const store = storage3();
|
|
3913
4987
|
try {
|
|
3914
4988
|
store?.setItem(key2, JSON.stringify(session));
|
|
3915
4989
|
} catch {
|
|
3916
4990
|
}
|
|
3917
4991
|
};
|
|
3918
4992
|
var clearSiwxSession = (address, _chainId) => {
|
|
3919
|
-
const key2 =
|
|
4993
|
+
const key2 = buildKey3(address);
|
|
3920
4994
|
memorySiwxSessions.delete(key2);
|
|
3921
|
-
const store =
|
|
4995
|
+
const store = storage3();
|
|
3922
4996
|
try {
|
|
3923
4997
|
store?.removeItem(key2);
|
|
3924
4998
|
} catch {
|
|
@@ -3958,8 +5032,8 @@ function buildSIWXConfig(deps) {
|
|
|
3958
5032
|
statement: STATEMENT,
|
|
3959
5033
|
issuedAt,
|
|
3960
5034
|
toString() {
|
|
3961
|
-
return new
|
|
3962
|
-
address:
|
|
5035
|
+
return new SiweMessage2({
|
|
5036
|
+
address: getAddress3(accountAddress),
|
|
3963
5037
|
chainId: numericChainId(chainId),
|
|
3964
5038
|
domain,
|
|
3965
5039
|
uri,
|
|
@@ -4001,7 +5075,7 @@ function buildSIWXConfig(deps) {
|
|
|
4001
5075
|
const persistSession = async (session) => {
|
|
4002
5076
|
const address = session.data.accountAddress;
|
|
4003
5077
|
const id = numericChainId(session.data.chainId);
|
|
4004
|
-
const message = new
|
|
5078
|
+
const message = new SiweMessage2(session.message);
|
|
4005
5079
|
const login = await post("/auth/login", {
|
|
4006
5080
|
message,
|
|
4007
5081
|
signature: session.signature,
|
|
@@ -4049,6 +5123,7 @@ export {
|
|
|
4049
5123
|
NotConnectedError,
|
|
4050
5124
|
OwneyError,
|
|
4051
5125
|
OwneySDK,
|
|
5126
|
+
YieldseekerAgent,
|
|
4052
5127
|
createOwneySIWX,
|
|
4053
5128
|
setOwneyDebug
|
|
4054
5129
|
};
|