@owney/sdk 0.7.16-beta.0 → 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 +1700 -640
- package/dist/index.d.cts +120 -10
- package/dist/index.d.ts +120 -10
- package/dist/index.js +1706 -635
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
NotConnectedError: () => NotConnectedError,
|
|
27
27
|
OwneyError: () => OwneyError,
|
|
28
28
|
OwneySDK: () => OwneySDK,
|
|
29
|
+
YieldseekerAgent: () => YieldseekerAgent,
|
|
29
30
|
createOwneySIWX: () => createOwneySIWX,
|
|
30
31
|
setOwneyDebug: () => setOwneyDebug
|
|
31
32
|
});
|
|
@@ -83,23 +84,6 @@ var SupportedAssets = [
|
|
|
83
84
|
}
|
|
84
85
|
];
|
|
85
86
|
|
|
86
|
-
// src/lib/debug.ts
|
|
87
|
-
var configuredDebug = false;
|
|
88
|
-
function setOwneyDebug(enabled) {
|
|
89
|
-
configuredDebug = enabled;
|
|
90
|
-
}
|
|
91
|
-
function isOwneyDebug() {
|
|
92
|
-
return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
|
|
93
|
-
}
|
|
94
|
-
function debugLog(scope, message, data) {
|
|
95
|
-
if (!isOwneyDebug()) return;
|
|
96
|
-
if (data === void 0) {
|
|
97
|
-
console.log(`[${scope}] ${message}`);
|
|
98
|
-
} else {
|
|
99
|
-
console.log(`[${scope}] ${message}`, data);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
87
|
// src/errors.ts
|
|
104
88
|
var OwneyError = class extends Error {
|
|
105
89
|
code;
|
|
@@ -299,7 +283,7 @@ function mapBalances(raw, _chainId, smartWallet) {
|
|
|
299
283
|
)
|
|
300
284
|
)
|
|
301
285
|
),
|
|
302
|
-
apy:
|
|
286
|
+
apy: p.pool_apy,
|
|
303
287
|
tvl: p.pool_tvl,
|
|
304
288
|
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
305
289
|
// structural read keeps it undefined today and auto-populates later.
|
|
@@ -356,27 +340,14 @@ function mapWeightedApyByChain(raw) {
|
|
|
356
340
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
357
341
|
const perAsset = {};
|
|
358
342
|
for (const [symbol, apy] of Object.entries(tokenApy)) {
|
|
359
|
-
const
|
|
360
|
-
if (!SUPPORTED_TOKENS.includes(
|
|
361
|
-
perAsset[
|
|
343
|
+
const token2 = symbol;
|
|
344
|
+
if (!SUPPORTED_TOKENS.includes(token2)) continue;
|
|
345
|
+
perAsset[token2] = apy;
|
|
362
346
|
}
|
|
363
347
|
if (Object.keys(perAsset).length > 0) out[chainId] = perAsset;
|
|
364
348
|
}
|
|
365
349
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
366
350
|
}
|
|
367
|
-
var warnedGrossApyFallbacks = /* @__PURE__ */ new Set();
|
|
368
|
-
function netApy(net, gross, field) {
|
|
369
|
-
if (net !== void 0 && net !== null) return net;
|
|
370
|
-
if (gross === void 0 || gross === null) return void 0;
|
|
371
|
-
if (!warnedGrossApyFallbacks.has(field)) {
|
|
372
|
-
warnedGrossApyFallbacks.add(field);
|
|
373
|
-
console.warn(
|
|
374
|
-
`[owney] @zyfai/sdk omitted "${field}"; falling back to the gross APY, which does not deduct Zyfai's performance fee and so reads high.`
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
debugLog("zyfai:apy", `gross fallback for ${field}`, { gross });
|
|
378
|
-
return gross;
|
|
379
|
-
}
|
|
380
351
|
function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
381
352
|
let weightedSum = 0;
|
|
382
353
|
let totalBalance = 0;
|
|
@@ -384,9 +355,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
|
384
355
|
if (p.chainId !== chainId) continue;
|
|
385
356
|
if (tokenSymbol && p.tokenSymbol !== tokenSymbol) continue;
|
|
386
357
|
const balance = p.balance ?? 0;
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
weightedSum += apy * balance;
|
|
358
|
+
if (balance <= 0 || typeof p.apy !== "number") continue;
|
|
359
|
+
weightedSum += p.apy * balance;
|
|
390
360
|
totalBalance += balance;
|
|
391
361
|
}
|
|
392
362
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
@@ -434,12 +404,8 @@ function mapEntries(rawEntries, chainId) {
|
|
|
434
404
|
const matched = positions.find(
|
|
435
405
|
(p) => p.protocol_name ? rawLog.newOpportunity.includes(`${p.protocol_name} (${p.pool})`) : false
|
|
436
406
|
);
|
|
437
|
-
oldApy = Number(
|
|
438
|
-
|
|
439
|
-
);
|
|
440
|
-
newApy = Number(
|
|
441
|
-
netApy(rawLog.newApy_withFee, rawLog.newApy, "newApy_withFee")
|
|
442
|
-
);
|
|
407
|
+
oldApy = Number(rawLog.oldApy);
|
|
408
|
+
newApy = Number(rawLog.newApy);
|
|
443
409
|
const from = splitOpportunity(rawLog.oldOpportunity);
|
|
444
410
|
const to = splitOpportunity(rawLog.newOpportunity);
|
|
445
411
|
rebalanceLog = [
|
|
@@ -519,11 +485,7 @@ function mapApyByStrategy(raw) {
|
|
|
519
485
|
const chainKey = supported.chainId;
|
|
520
486
|
const symbolKey = supported.symbol;
|
|
521
487
|
const bucket = apyPerAsset[chainKey] ?? {};
|
|
522
|
-
const apy =
|
|
523
|
-
entry.average_apy_withFee,
|
|
524
|
-
entry.average_apy,
|
|
525
|
-
"average_apy_withFee"
|
|
526
|
-
) ?? entry.average_apy;
|
|
488
|
+
const apy = entry.average_apy_with_fee ?? entry.average_apy;
|
|
527
489
|
bucket[symbolKey] = apy;
|
|
528
490
|
apyPerAsset[chainKey] = bucket;
|
|
529
491
|
apySum += apy;
|
|
@@ -549,23 +511,23 @@ function computeAllocationApy(positions) {
|
|
|
549
511
|
weightedSum += apy * value;
|
|
550
512
|
totalValue += value;
|
|
551
513
|
const chainId = resolveChainId(p.chain);
|
|
552
|
-
const
|
|
514
|
+
const token2 = p.asset;
|
|
553
515
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
554
|
-
if (!SUPPORTED_TOKENS.includes(
|
|
516
|
+
if (!SUPPORTED_TOKENS.includes(token2)) continue;
|
|
555
517
|
const perAsset = buckets[chainId] ?? {};
|
|
556
|
-
const cell = perAsset[
|
|
518
|
+
const cell = perAsset[token2] ?? { weightedSum: 0, value: 0 };
|
|
557
519
|
cell.weightedSum += apy * value;
|
|
558
520
|
cell.value += value;
|
|
559
|
-
perAsset[
|
|
521
|
+
perAsset[token2] = cell;
|
|
560
522
|
buckets[chainId] = perAsset;
|
|
561
523
|
}
|
|
562
524
|
const apyByChainAndAsset = {};
|
|
563
525
|
for (const [chainKey, perAsset] of Object.entries(buckets)) {
|
|
564
526
|
const chainId = Number(chainKey);
|
|
565
527
|
const out = {};
|
|
566
|
-
for (const [
|
|
528
|
+
for (const [token2, cell] of Object.entries(perAsset ?? {})) {
|
|
567
529
|
if (cell && cell.value > 0) {
|
|
568
|
-
out[
|
|
530
|
+
out[token2] = cell.weightedSum / cell.value;
|
|
569
531
|
}
|
|
570
532
|
}
|
|
571
533
|
if (Object.keys(out).length > 0) apyByChainAndAsset[chainId] = out;
|
|
@@ -579,9 +541,9 @@ var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4d
|
|
|
579
541
|
function topicToAddress(topic) {
|
|
580
542
|
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
581
543
|
}
|
|
582
|
-
function extractWithdrawnAmount(logs, recipient,
|
|
544
|
+
function extractWithdrawnAmount(logs, recipient, token2, decimals) {
|
|
583
545
|
const wantRecipient = recipient.toLowerCase();
|
|
584
|
-
const wantToken =
|
|
546
|
+
const wantToken = token2.toLowerCase();
|
|
585
547
|
let total = 0n;
|
|
586
548
|
let matched = false;
|
|
587
549
|
for (const log of logs) {
|
|
@@ -607,11 +569,11 @@ var InvalidHistoryCursorError = class extends Error {
|
|
|
607
569
|
function encodeHistoryCursor(payload) {
|
|
608
570
|
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
609
571
|
}
|
|
610
|
-
function decodeHistoryCursor(
|
|
611
|
-
if (!
|
|
572
|
+
function decodeHistoryCursor(token2) {
|
|
573
|
+
if (!token2) throw new InvalidHistoryCursorError("Cursor is empty.");
|
|
612
574
|
let parsed;
|
|
613
575
|
try {
|
|
614
|
-
const json = Buffer.from(
|
|
576
|
+
const json = Buffer.from(token2, "base64").toString("utf8");
|
|
615
577
|
parsed = JSON.parse(json);
|
|
616
578
|
} catch {
|
|
617
579
|
throw new InvalidHistoryCursorError("Cursor is not valid base64 JSON.");
|
|
@@ -634,8 +596,8 @@ var storage = () => {
|
|
|
634
596
|
};
|
|
635
597
|
var buildKey = (address) => `${KEY_PREFIX}:${address.toLowerCase()}`;
|
|
636
598
|
var legacyKeyPrefix = (address) => `${KEY_PREFIX}:${address.toLowerCase()}:`;
|
|
637
|
-
var isJwtExpired = (
|
|
638
|
-
const parts =
|
|
599
|
+
var isJwtExpired = (token2) => {
|
|
600
|
+
const parts = token2.split(".");
|
|
639
601
|
if (parts.length !== 3) return false;
|
|
640
602
|
try {
|
|
641
603
|
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -883,6 +845,23 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
|
|
|
883
845
|
return !protocolListsEqual(current.protocols, desiredProtocols);
|
|
884
846
|
}
|
|
885
847
|
|
|
848
|
+
// src/lib/debug.ts
|
|
849
|
+
var configuredDebug = false;
|
|
850
|
+
function setOwneyDebug(enabled) {
|
|
851
|
+
configuredDebug = enabled;
|
|
852
|
+
}
|
|
853
|
+
function isOwneyDebug() {
|
|
854
|
+
return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
|
|
855
|
+
}
|
|
856
|
+
function debugLog(scope, message, data) {
|
|
857
|
+
if (!isOwneyDebug()) return;
|
|
858
|
+
if (data === void 0) {
|
|
859
|
+
console.log(`[${scope}] ${message}`);
|
|
860
|
+
} else {
|
|
861
|
+
console.log(`[${scope}] ${message}`, data);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
886
865
|
// src/agents/zyfai/zyfai.agent.ts
|
|
887
866
|
var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem.parseAbi)([
|
|
888
867
|
"function isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) view returns (bool)"
|
|
@@ -1720,20 +1699,20 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1720
1699
|
}
|
|
1721
1700
|
}
|
|
1722
1701
|
// --- IAgent: Fund operations ---
|
|
1723
|
-
async withdraw(state, chainId,
|
|
1702
|
+
async withdraw(state, chainId, token2, amount) {
|
|
1724
1703
|
const validChainId = isValidChainId(chainId);
|
|
1725
1704
|
await this.ensureConnected(state, validChainId);
|
|
1726
1705
|
const raw = await this.sdk.withdrawFunds(
|
|
1727
1706
|
this.getAddress(),
|
|
1728
1707
|
validChainId,
|
|
1729
1708
|
amount,
|
|
1730
|
-
|
|
1709
|
+
token2
|
|
1731
1710
|
);
|
|
1732
1711
|
if (!raw.success) {
|
|
1733
1712
|
throw new OwneyError(
|
|
1734
1713
|
"WITHDRAW_FAILED",
|
|
1735
1714
|
raw.message || "Zyfai withdraw failed.",
|
|
1736
|
-
{ chainId: validChainId, token, amount, response: raw },
|
|
1715
|
+
{ chainId: validChainId, token: token2, amount, response: raw },
|
|
1737
1716
|
this.id
|
|
1738
1717
|
);
|
|
1739
1718
|
}
|
|
@@ -1884,345 +1863,169 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1884
1863
|
}
|
|
1885
1864
|
};
|
|
1886
1865
|
|
|
1887
|
-
// src/
|
|
1888
|
-
var
|
|
1889
|
-
|
|
1890
|
-
|
|
1866
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
1867
|
+
var import_viem6 = require("viem");
|
|
1868
|
+
var import_chains3 = require("viem/chains");
|
|
1869
|
+
|
|
1870
|
+
// src/lib/chain-guard.ts
|
|
1871
|
+
var CHAIN_NAMES = {
|
|
1872
|
+
1: "Ethereum",
|
|
1873
|
+
8453: "Base",
|
|
1874
|
+
42161: "Arbitrum"
|
|
1875
|
+
};
|
|
1876
|
+
function chainName(chainId) {
|
|
1877
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
1878
|
+
}
|
|
1879
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
1880
|
+
const actual = await pub.getChainId();
|
|
1881
|
+
if (actual === expected) return;
|
|
1891
1882
|
try {
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
console.warn(
|
|
1902
|
-
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
1903
|
-
);
|
|
1883
|
+
await wallet.switchChain({ id: expected });
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
throw new OwneyError(
|
|
1886
|
+
"CHAIN_MISMATCH",
|
|
1887
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
1888
|
+
{
|
|
1889
|
+
expectedChainId: expected,
|
|
1890
|
+
actualChainId: actual,
|
|
1891
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1904
1892
|
}
|
|
1905
|
-
return null;
|
|
1906
|
-
}
|
|
1907
|
-
const json = await res.json();
|
|
1908
|
-
const policy = json.success ? json.data ?? null : null;
|
|
1909
|
-
debugLog(
|
|
1910
|
-
"owney-sdk",
|
|
1911
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
1912
|
-
policy ?? void 0
|
|
1913
1893
|
);
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1894
|
+
}
|
|
1895
|
+
const after = await pub.getChainId();
|
|
1896
|
+
if (after !== expected) {
|
|
1897
|
+
throw new OwneyError(
|
|
1898
|
+
"CHAIN_MISMATCH",
|
|
1899
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
1900
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
1919
1901
|
);
|
|
1920
|
-
return null;
|
|
1921
1902
|
}
|
|
1922
1903
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1904
|
+
|
|
1905
|
+
// src/lib/transfer-auth.ts
|
|
1906
|
+
var import_viem2 = require("viem");
|
|
1907
|
+
var ERC20_META_ABI = [
|
|
1908
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
1909
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
1910
|
+
];
|
|
1911
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
1912
|
+
return {
|
|
1913
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
1914
|
+
types: {
|
|
1915
|
+
TransferWithAuthorization: [
|
|
1916
|
+
{ name: "from", type: "address" },
|
|
1917
|
+
{ name: "to", type: "address" },
|
|
1918
|
+
{ name: "value", type: "uint256" },
|
|
1919
|
+
{ name: "validAfter", type: "uint256" },
|
|
1920
|
+
{ name: "validBefore", type: "uint256" },
|
|
1921
|
+
{ name: "nonce", type: "bytes32" }
|
|
1922
|
+
]
|
|
1923
|
+
},
|
|
1924
|
+
primaryType: "TransferWithAuthorization",
|
|
1925
|
+
message: input.message
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
async function readTokenMeta(publicClient, token2) {
|
|
1929
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
1930
|
+
publicClient.readContract({ address: token2, abi: ERC20_META_ABI, functionName: "name" }),
|
|
1931
|
+
publicClient.readContract({ address: token2, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
1932
|
+
]);
|
|
1933
|
+
return { tokenName, tokenVersion };
|
|
1934
|
+
}
|
|
1935
|
+
function randomAuthNonce() {
|
|
1936
|
+
const bytes = new Uint8Array(32);
|
|
1937
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1938
|
+
return (0, import_viem2.bytesToHex)(bytes);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// src/lib/sponsor-client.ts
|
|
1942
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
1943
|
+
async function postSponsorTransferAuth(input) {
|
|
1944
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
1945
|
+
let res;
|
|
1946
|
+
try {
|
|
1947
|
+
res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
1948
|
+
method: "POST",
|
|
1949
|
+
headers: {
|
|
1950
|
+
"content-type": "application/json",
|
|
1951
|
+
"x-owney-api-key": input.apiKey,
|
|
1952
|
+
...input.yieldseekerSignature ? { "x-signature": input.yieldseekerSignature } : {}
|
|
1953
|
+
},
|
|
1954
|
+
body: JSON.stringify(input.body)
|
|
1955
|
+
});
|
|
1956
|
+
} catch (networkError) {
|
|
1934
1957
|
throw new OwneyError(
|
|
1935
|
-
"
|
|
1936
|
-
`
|
|
1937
|
-
{
|
|
1958
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1959
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1960
|
+
{ cause: String(networkError) }
|
|
1938
1961
|
);
|
|
1939
1962
|
}
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1963
|
+
const text = await res.text();
|
|
1964
|
+
let parsed = null;
|
|
1965
|
+
try {
|
|
1966
|
+
parsed = JSON.parse(text);
|
|
1967
|
+
} catch {
|
|
1968
|
+
}
|
|
1969
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
1942
1970
|
throw new OwneyError(
|
|
1943
|
-
"
|
|
1944
|
-
`
|
|
1945
|
-
{
|
|
1971
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1972
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1973
|
+
{
|
|
1974
|
+
statusCode: res.status,
|
|
1975
|
+
responseBody: text.slice(0, 500),
|
|
1976
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
1977
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
1978
|
+
safeToFallback: res.status === 503
|
|
1979
|
+
}
|
|
1946
1980
|
);
|
|
1947
1981
|
}
|
|
1948
|
-
return
|
|
1982
|
+
return parsed.data;
|
|
1949
1983
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
async function reportAgentFailure(apiKey, agentType, errorCode) {
|
|
1984
|
+
async function postSponsorPermit2Transfer(input) {
|
|
1985
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
1986
|
+
let res;
|
|
1954
1987
|
try {
|
|
1955
|
-
await fetch(`${
|
|
1988
|
+
res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
|
|
1956
1989
|
method: "POST",
|
|
1957
1990
|
headers: {
|
|
1958
|
-
"
|
|
1959
|
-
"x-owney-api-key": apiKey
|
|
1991
|
+
"content-type": "application/json",
|
|
1992
|
+
"x-owney-api-key": input.apiKey,
|
|
1993
|
+
...input.yieldseekerSignature ? { "x-signature": input.yieldseekerSignature } : {}
|
|
1960
1994
|
},
|
|
1961
|
-
body: JSON.stringify(
|
|
1962
|
-
agent_type: agentType,
|
|
1963
|
-
error_code: errorCode,
|
|
1964
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1965
|
-
})
|
|
1995
|
+
body: JSON.stringify(input.body)
|
|
1966
1996
|
});
|
|
1967
|
-
} catch (
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1997
|
+
} catch (networkError) {
|
|
1998
|
+
throw new OwneyError(
|
|
1999
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2000
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2001
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
1971
2002
|
);
|
|
1972
2003
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
2004
|
+
const text = await res.text();
|
|
2005
|
+
let parsed = null;
|
|
1975
2006
|
try {
|
|
1976
|
-
|
|
1977
|
-
} catch
|
|
1978
|
-
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
1979
|
-
void reportAgentFailure(apiKey, agentType, errorCode);
|
|
1980
|
-
throw err;
|
|
2007
|
+
parsed = JSON.parse(text);
|
|
2008
|
+
} catch {
|
|
1981
2009
|
}
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2010
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2011
|
+
throw new OwneyError(
|
|
2012
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2013
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2014
|
+
{
|
|
2015
|
+
statusCode: res.status,
|
|
2016
|
+
responseBody: text.slice(0, 500),
|
|
2017
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2018
|
+
}
|
|
1992
2019
|
);
|
|
1993
|
-
if (!tokenBalance) return { agent, balance: 0n };
|
|
1994
|
-
return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
|
|
1995
|
-
});
|
|
1996
|
-
}
|
|
1997
|
-
function planProportionalShares(balances, requested, totalAvailable) {
|
|
1998
|
-
const plans = balances.map(({ agent, balance }) => ({
|
|
1999
|
-
agent,
|
|
2000
|
-
balance,
|
|
2001
|
-
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
2002
|
-
}));
|
|
2003
|
-
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
2004
|
-
let remainder = requested - assigned;
|
|
2005
|
-
const byHeadroom = [...plans].sort((a, b) => {
|
|
2006
|
-
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
2007
|
-
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2008
|
-
});
|
|
2009
|
-
for (const p of byHeadroom) {
|
|
2010
|
-
if (remainder === 0n) break;
|
|
2011
|
-
const headroom = p.balance - p.planned;
|
|
2012
|
-
if (headroom <= 0n) continue;
|
|
2013
|
-
const take = headroom < remainder ? headroom : remainder;
|
|
2014
|
-
p.planned += take;
|
|
2015
|
-
remainder -= take;
|
|
2016
2020
|
}
|
|
2017
|
-
return
|
|
2018
|
-
}
|
|
2019
|
-
function planDisabledDrain(disabled, requested) {
|
|
2020
|
-
const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
2021
|
-
const plans = [];
|
|
2022
|
-
let remaining = requested;
|
|
2023
|
-
for (const { agent, balance } of sorted) {
|
|
2024
|
-
if (remaining === 0n) {
|
|
2025
|
-
plans.push({ agent, balance, planned: 0n });
|
|
2026
|
-
continue;
|
|
2027
|
-
}
|
|
2028
|
-
const take = balance < remaining ? balance : remaining;
|
|
2029
|
-
plans.push({ agent, balance, planned: take });
|
|
2030
|
-
remaining -= take;
|
|
2031
|
-
}
|
|
2032
|
-
return { plans, remaining };
|
|
2033
|
-
}
|
|
2034
|
-
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
2035
|
-
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
2036
|
-
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
2037
|
-
const totalHeadroom = candidates.reduce(
|
|
2038
|
-
(s, c) => s + (c.balance - c.planned),
|
|
2039
|
-
0n
|
|
2040
|
-
);
|
|
2041
|
-
if (totalHeadroom === 0n) return;
|
|
2042
|
-
let distributed = 0n;
|
|
2043
|
-
for (const c of candidates) {
|
|
2044
|
-
const headroom = c.balance - c.planned;
|
|
2045
|
-
const proportional = headroom * amount / totalHeadroom;
|
|
2046
|
-
const give = proportional > headroom ? headroom : proportional;
|
|
2047
|
-
c.planned += give;
|
|
2048
|
-
distributed += give;
|
|
2049
|
-
}
|
|
2050
|
-
let leftover = amount - distributed;
|
|
2051
|
-
for (const c of candidates) {
|
|
2052
|
-
if (leftover === 0n) break;
|
|
2053
|
-
const headroom = c.balance - c.planned;
|
|
2054
|
-
if (headroom <= 0n) continue;
|
|
2055
|
-
const take = headroom < leftover ? headroom : leftover;
|
|
2056
|
-
c.planned += take;
|
|
2057
|
-
leftover -= take;
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
function sumWithdrawnAmount(results) {
|
|
2061
|
-
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
// src/lib/helpers/account-apy-helper.ts
|
|
2065
|
-
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
2066
|
-
const sums = {};
|
|
2067
|
-
const weights = {};
|
|
2068
|
-
for (const id of Object.keys(agentApys)) {
|
|
2069
|
-
const cells = agentApys[id].apyByChainAndAsset;
|
|
2070
|
-
const balance = agentBalances[id] ?? 0;
|
|
2071
|
-
if (!cells || balance <= 0) continue;
|
|
2072
|
-
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
2073
|
-
if (!perAsset) continue;
|
|
2074
|
-
const chainId = Number(chainKey);
|
|
2075
|
-
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
2076
|
-
const apy = Number(apyValue ?? 0);
|
|
2077
|
-
if (apy === 0) continue;
|
|
2078
|
-
sums[chainId] ??= {};
|
|
2079
|
-
weights[chainId] ??= {};
|
|
2080
|
-
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
2081
|
-
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
const out = {};
|
|
2086
|
-
for (const chainKey of Object.keys(sums)) {
|
|
2087
|
-
const chainId = Number(chainKey);
|
|
2088
|
-
const perAssetOut = {};
|
|
2089
|
-
for (const asset of Object.keys(sums[chainId])) {
|
|
2090
|
-
const w = weights[chainId][asset];
|
|
2091
|
-
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
2092
|
-
}
|
|
2093
|
-
if (Object.keys(perAssetOut).length > 0) {
|
|
2094
|
-
out[chainId] = perAssetOut;
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
return out;
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
// src/client.ts
|
|
2101
|
-
var import_viem6 = require("viem");
|
|
2102
|
-
var import_chains2 = require("viem/chains");
|
|
2103
|
-
|
|
2104
|
-
// src/lib/transfer-auth.ts
|
|
2105
|
-
var import_viem3 = require("viem");
|
|
2106
|
-
var ERC20_META_ABI = [
|
|
2107
|
-
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2108
|
-
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2109
|
-
];
|
|
2110
|
-
function buildTransferWithAuthorizationTypedData(input) {
|
|
2111
|
-
return {
|
|
2112
|
-
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2113
|
-
types: {
|
|
2114
|
-
TransferWithAuthorization: [
|
|
2115
|
-
{ name: "from", type: "address" },
|
|
2116
|
-
{ name: "to", type: "address" },
|
|
2117
|
-
{ name: "value", type: "uint256" },
|
|
2118
|
-
{ name: "validAfter", type: "uint256" },
|
|
2119
|
-
{ name: "validBefore", type: "uint256" },
|
|
2120
|
-
{ name: "nonce", type: "bytes32" }
|
|
2121
|
-
]
|
|
2122
|
-
},
|
|
2123
|
-
primaryType: "TransferWithAuthorization",
|
|
2124
|
-
message: input.message
|
|
2125
|
-
};
|
|
2126
|
-
}
|
|
2127
|
-
async function readTokenMeta(publicClient, token) {
|
|
2128
|
-
const [tokenName, tokenVersion] = await Promise.all([
|
|
2129
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2130
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2131
|
-
]);
|
|
2132
|
-
return { tokenName, tokenVersion };
|
|
2133
|
-
}
|
|
2134
|
-
function randomAuthNonce() {
|
|
2135
|
-
const bytes = new Uint8Array(32);
|
|
2136
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2137
|
-
return (0, import_viem3.bytesToHex)(bytes);
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
// src/lib/sponsor-client.ts
|
|
2141
|
-
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2142
|
-
async function postSponsorTransferAuth(input) {
|
|
2143
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2144
|
-
let res;
|
|
2145
|
-
try {
|
|
2146
|
-
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2147
|
-
method: "POST",
|
|
2148
|
-
headers: {
|
|
2149
|
-
"content-type": "application/json",
|
|
2150
|
-
"x-owney-api-key": input.apiKey
|
|
2151
|
-
},
|
|
2152
|
-
body: JSON.stringify(input.body)
|
|
2153
|
-
});
|
|
2154
|
-
} catch (networkError) {
|
|
2155
|
-
throw new OwneyError(
|
|
2156
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2157
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2158
|
-
{ cause: String(networkError) }
|
|
2159
|
-
);
|
|
2160
|
-
}
|
|
2161
|
-
const text = await res.text();
|
|
2162
|
-
let parsed = null;
|
|
2163
|
-
try {
|
|
2164
|
-
parsed = JSON.parse(text);
|
|
2165
|
-
} catch {
|
|
2166
|
-
}
|
|
2167
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2168
|
-
throw new OwneyError(
|
|
2169
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2170
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2171
|
-
{
|
|
2172
|
-
statusCode: res.status,
|
|
2173
|
-
responseBody: text.slice(0, 500),
|
|
2174
|
-
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2175
|
-
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2176
|
-
safeToFallback: res.status === 503
|
|
2177
|
-
}
|
|
2178
|
-
);
|
|
2179
|
-
}
|
|
2180
|
-
return parsed.data;
|
|
2181
|
-
}
|
|
2182
|
-
async function postSponsorPermit2Transfer(input) {
|
|
2183
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2184
|
-
let res;
|
|
2185
|
-
try {
|
|
2186
|
-
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
2187
|
-
method: "POST",
|
|
2188
|
-
headers: {
|
|
2189
|
-
"content-type": "application/json",
|
|
2190
|
-
"x-owney-api-key": input.apiKey
|
|
2191
|
-
},
|
|
2192
|
-
body: JSON.stringify(input.body)
|
|
2193
|
-
});
|
|
2194
|
-
} catch (networkError) {
|
|
2195
|
-
throw new OwneyError(
|
|
2196
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2197
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2198
|
-
{ cause: String(networkError), safeToFallback: false }
|
|
2199
|
-
);
|
|
2200
|
-
}
|
|
2201
|
-
const text = await res.text();
|
|
2202
|
-
let parsed = null;
|
|
2203
|
-
try {
|
|
2204
|
-
parsed = JSON.parse(text);
|
|
2205
|
-
} catch {
|
|
2206
|
-
}
|
|
2207
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2208
|
-
throw new OwneyError(
|
|
2209
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2210
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2211
|
-
{
|
|
2212
|
-
statusCode: res.status,
|
|
2213
|
-
responseBody: text.slice(0, 500),
|
|
2214
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2215
|
-
}
|
|
2216
|
-
);
|
|
2217
|
-
}
|
|
2218
|
-
return parsed.data;
|
|
2021
|
+
return parsed.data;
|
|
2219
2022
|
}
|
|
2220
2023
|
async function getSponsorRelayerAddress(input) {
|
|
2221
|
-
const
|
|
2024
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2222
2025
|
let res;
|
|
2223
2026
|
try {
|
|
2224
2027
|
res = await fetch(
|
|
2225
|
-
`${
|
|
2028
|
+
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2226
2029
|
{
|
|
2227
2030
|
headers: { "x-owney-api-key": input.apiKey }
|
|
2228
2031
|
}
|
|
@@ -2255,7 +2058,7 @@ async function getSponsorRelayerAddress(input) {
|
|
|
2255
2058
|
}
|
|
2256
2059
|
|
|
2257
2060
|
// src/lib/permit2.ts
|
|
2258
|
-
var
|
|
2061
|
+
var import_viem3 = require("viem");
|
|
2259
2062
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2260
2063
|
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2261
2064
|
var ERC20_ALLOWANCE_ABI = [
|
|
@@ -2313,223 +2116,1391 @@ function buildPermitTransferFromTypedData(input) {
|
|
|
2313
2116
|
function randomPermit2Nonce() {
|
|
2314
2117
|
const bytes = new Uint8Array(32);
|
|
2315
2118
|
globalThis.crypto.getRandomValues(bytes);
|
|
2316
|
-
return BigInt((0,
|
|
2119
|
+
return BigInt((0, import_viem3.bytesToHex)(bytes));
|
|
2317
2120
|
}
|
|
2318
|
-
async function readPermit2Allowance(publicClient,
|
|
2121
|
+
async function readPermit2Allowance(publicClient, token2, owner) {
|
|
2319
2122
|
return publicClient.readContract({
|
|
2320
|
-
address:
|
|
2123
|
+
address: token2,
|
|
2321
2124
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2322
2125
|
functionName: "allowance",
|
|
2323
2126
|
args: [owner, PERMIT2_ADDRESS]
|
|
2324
2127
|
});
|
|
2325
2128
|
}
|
|
2326
|
-
async function readErc20Balance(publicClient,
|
|
2327
|
-
return publicClient.readContract({
|
|
2328
|
-
address:
|
|
2329
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2330
|
-
functionName: "balanceOf",
|
|
2331
|
-
args: [owner]
|
|
2129
|
+
async function readErc20Balance(publicClient, token2, owner) {
|
|
2130
|
+
return publicClient.readContract({
|
|
2131
|
+
address: token2,
|
|
2132
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2133
|
+
functionName: "balanceOf",
|
|
2134
|
+
args: [owner]
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/lib/sponsored-deposit.ts
|
|
2139
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2140
|
+
var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
|
|
2141
|
+
function provideDepositVerificationContext(callback, context) {
|
|
2142
|
+
callback[verificationSetter]?.(context);
|
|
2143
|
+
}
|
|
2144
|
+
function makeVerificationAwareDepositCallback(implementation) {
|
|
2145
|
+
let nextVerification;
|
|
2146
|
+
const callback = async (smartWallet, chainId, amount) => {
|
|
2147
|
+
const verification = nextVerification;
|
|
2148
|
+
nextVerification = void 0;
|
|
2149
|
+
return implementation(smartWallet, chainId, amount, verification);
|
|
2150
|
+
};
|
|
2151
|
+
Object.defineProperty(callback, verificationSetter, {
|
|
2152
|
+
value: (context) => {
|
|
2153
|
+
nextVerification = context;
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
return callback;
|
|
2157
|
+
}
|
|
2158
|
+
function makeSponsoredDepositCallback(deps) {
|
|
2159
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
2160
|
+
return makeVerificationAwareDepositCallback(
|
|
2161
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
2162
|
+
const cid = chainId;
|
|
2163
|
+
const token2 = deps.tokenAddressByChain[cid];
|
|
2164
|
+
if (!token2) {
|
|
2165
|
+
throw new OwneyError(
|
|
2166
|
+
"CHAIN_UNSUPPORTED",
|
|
2167
|
+
`No sponsored token configured for chain ${chainId}`
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
const pub = deps.getPublicClient(cid);
|
|
2171
|
+
const wallet = deps.getWalletClient(cid);
|
|
2172
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
2173
|
+
try {
|
|
2174
|
+
const balance = await readErc20Balance(pub, token2, deps.ownerAddress);
|
|
2175
|
+
if (balance < BigInt(amount)) {
|
|
2176
|
+
throw new OwneyError(
|
|
2177
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2178
|
+
"Insufficient balance for this deposit.",
|
|
2179
|
+
{ token: token2, chainId: cid, balance: balance.toString(), amount }
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
} catch (err) {
|
|
2183
|
+
if (err instanceof OwneyError) throw err;
|
|
2184
|
+
console.warn(
|
|
2185
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2186
|
+
err instanceof Error ? err.message : String(err)
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token2);
|
|
2190
|
+
const validAfter = 0n;
|
|
2191
|
+
const validBefore = BigInt(
|
|
2192
|
+
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2193
|
+
);
|
|
2194
|
+
const nonce = randomAuthNonce();
|
|
2195
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
2196
|
+
token: token2,
|
|
2197
|
+
chainId: cid,
|
|
2198
|
+
tokenName,
|
|
2199
|
+
tokenVersion,
|
|
2200
|
+
message: {
|
|
2201
|
+
from: deps.ownerAddress,
|
|
2202
|
+
to: smartWallet,
|
|
2203
|
+
value: BigInt(amount),
|
|
2204
|
+
validAfter,
|
|
2205
|
+
validBefore,
|
|
2206
|
+
nonce
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
const authSignature = await wallet.signTypedData({
|
|
2210
|
+
account: deps.ownerAddress,
|
|
2211
|
+
...typedData
|
|
2212
|
+
});
|
|
2213
|
+
deps.onApproved?.();
|
|
2214
|
+
const result = await post({
|
|
2215
|
+
baseUrl: deps.baseUrl,
|
|
2216
|
+
apiKey: deps.apiKey,
|
|
2217
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
2218
|
+
body: {
|
|
2219
|
+
chainId: cid,
|
|
2220
|
+
token: token2,
|
|
2221
|
+
from: deps.ownerAddress,
|
|
2222
|
+
to: smartWallet,
|
|
2223
|
+
value: amount,
|
|
2224
|
+
validAfter: validAfter.toString(),
|
|
2225
|
+
validBefore: validBefore.toString(),
|
|
2226
|
+
nonce,
|
|
2227
|
+
authSignature,
|
|
2228
|
+
tokenName,
|
|
2229
|
+
tokenVersion
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
return result.txHash;
|
|
2233
|
+
}
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2238
|
+
var import_siwe = require("siwe");
|
|
2239
|
+
var import_viem4 = require("viem");
|
|
2240
|
+
var import_chains2 = require("viem/chains");
|
|
2241
|
+
|
|
2242
|
+
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
2243
|
+
var KEY_PREFIX2 = "owney.yieldseeker.session";
|
|
2244
|
+
var storage2 = () => {
|
|
2245
|
+
if (typeof window === "undefined") return null;
|
|
2246
|
+
try {
|
|
2247
|
+
return window.localStorage;
|
|
2248
|
+
} catch {
|
|
2249
|
+
return null;
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2253
|
+
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2254
|
+
var isValidSession = (session) => {
|
|
2255
|
+
if (!session?.token) return false;
|
|
2256
|
+
try {
|
|
2257
|
+
const parsed = JSON.parse(atob(session.token));
|
|
2258
|
+
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2259
|
+
} catch {
|
|
2260
|
+
return false;
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
var readYieldseekerSession = (address, chainId) => {
|
|
2264
|
+
if (typeof window === "undefined") return null;
|
|
2265
|
+
const key2 = buildKey2(address, chainId);
|
|
2266
|
+
const store = storage2();
|
|
2267
|
+
let raw = null;
|
|
2268
|
+
try {
|
|
2269
|
+
raw = store?.getItem(key2) ?? null;
|
|
2270
|
+
} catch {
|
|
2271
|
+
raw = null;
|
|
2272
|
+
}
|
|
2273
|
+
if (raw) {
|
|
2274
|
+
try {
|
|
2275
|
+
const parsed = JSON.parse(raw);
|
|
2276
|
+
if (isValidSession(parsed)) return parsed.token;
|
|
2277
|
+
} catch {
|
|
2278
|
+
}
|
|
2279
|
+
memorySessions2.delete(key2);
|
|
2280
|
+
try {
|
|
2281
|
+
store?.removeItem(key2);
|
|
2282
|
+
} catch {
|
|
2283
|
+
}
|
|
2284
|
+
return null;
|
|
2285
|
+
}
|
|
2286
|
+
const cached = memorySessions2.get(key2);
|
|
2287
|
+
if (isValidSession(cached)) return cached.token;
|
|
2288
|
+
if (cached) memorySessions2.delete(key2);
|
|
2289
|
+
return null;
|
|
2290
|
+
};
|
|
2291
|
+
var writeYieldseekerSession = (address, chainId, token2) => {
|
|
2292
|
+
if (typeof window === "undefined") return;
|
|
2293
|
+
const session = { token: token2 };
|
|
2294
|
+
if (!isValidSession(session)) return;
|
|
2295
|
+
const key2 = buildKey2(address, chainId);
|
|
2296
|
+
memorySessions2.set(key2, session);
|
|
2297
|
+
const store = storage2();
|
|
2298
|
+
try {
|
|
2299
|
+
store?.setItem(key2, JSON.stringify(session));
|
|
2300
|
+
} catch {
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
var clearYieldseekerSession = (address, chainId) => {
|
|
2304
|
+
const key2 = buildKey2(address, chainId);
|
|
2305
|
+
memorySessions2.delete(key2);
|
|
2306
|
+
const store = storage2();
|
|
2307
|
+
try {
|
|
2308
|
+
store?.removeItem(key2);
|
|
2309
|
+
} catch {
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2314
|
+
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2315
|
+
return new import_siwe.SiweMessage({
|
|
2316
|
+
domain: "yieldseeker.xyz",
|
|
2317
|
+
address: (0, import_viem4.getAddress)(address),
|
|
2318
|
+
uri: "https://yieldseeker.xyz",
|
|
2319
|
+
version: "1",
|
|
2320
|
+
chainId,
|
|
2321
|
+
nonce: (dependencies.nonce ?? import_siwe.generateNonce)(),
|
|
2322
|
+
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2323
|
+
}).prepareMessage();
|
|
2324
|
+
}
|
|
2325
|
+
function encodeYieldseekerAuthToken(token2) {
|
|
2326
|
+
const bytes = new TextEncoder().encode(JSON.stringify(token2));
|
|
2327
|
+
let binary = "";
|
|
2328
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2329
|
+
return btoa(binary);
|
|
2330
|
+
}
|
|
2331
|
+
var YieldseekerAuth = class {
|
|
2332
|
+
constructor(dependencies = {}) {
|
|
2333
|
+
this.dependencies = dependencies;
|
|
2334
|
+
}
|
|
2335
|
+
dependencies;
|
|
2336
|
+
tokens = /* @__PURE__ */ new Map();
|
|
2337
|
+
pending = /* @__PURE__ */ new Map();
|
|
2338
|
+
scopes = /* @__PURE__ */ new Map();
|
|
2339
|
+
key(state, chainId) {
|
|
2340
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
2341
|
+
}
|
|
2342
|
+
async getToken(state, chainId) {
|
|
2343
|
+
const key2 = this.key(state, chainId);
|
|
2344
|
+
const scope = { address: state.walletAddress, chainId };
|
|
2345
|
+
this.scopes.set(key2, scope);
|
|
2346
|
+
const cached = this.tokens.get(key2);
|
|
2347
|
+
if (cached) return cached;
|
|
2348
|
+
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2349
|
+
if (persisted) {
|
|
2350
|
+
this.tokens.set(key2, persisted);
|
|
2351
|
+
return persisted;
|
|
2352
|
+
}
|
|
2353
|
+
const inFlight = this.pending.get(key2);
|
|
2354
|
+
if (inFlight) return inFlight;
|
|
2355
|
+
const request = this.sign(state, chainId).then((token2) => {
|
|
2356
|
+
this.tokens.set(key2, token2);
|
|
2357
|
+
writeYieldseekerSession(scope.address, scope.chainId, token2);
|
|
2358
|
+
return token2;
|
|
2359
|
+
});
|
|
2360
|
+
this.pending.set(key2, request);
|
|
2361
|
+
try {
|
|
2362
|
+
return await request;
|
|
2363
|
+
} finally {
|
|
2364
|
+
this.pending.delete(key2);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
clear(state, chainId) {
|
|
2368
|
+
if (!state || chainId === void 0) {
|
|
2369
|
+
for (const scope of this.scopes.values()) {
|
|
2370
|
+
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2371
|
+
}
|
|
2372
|
+
this.tokens.clear();
|
|
2373
|
+
this.pending.clear();
|
|
2374
|
+
this.scopes.clear();
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
const key2 = this.key(state, chainId);
|
|
2378
|
+
this.tokens.delete(key2);
|
|
2379
|
+
this.pending.delete(key2);
|
|
2380
|
+
this.scopes.delete(key2);
|
|
2381
|
+
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2382
|
+
}
|
|
2383
|
+
async sign(state, chainId) {
|
|
2384
|
+
const account = (0, import_viem4.getAddress)(state.walletAddress);
|
|
2385
|
+
const publicClient = (0, import_viem4.createPublicClient)({
|
|
2386
|
+
chain: import_chains2.base,
|
|
2387
|
+
transport: (0, import_viem4.custom)(state.provider)
|
|
2388
|
+
});
|
|
2389
|
+
const walletClient = (0, import_viem4.createWalletClient)({
|
|
2390
|
+
account,
|
|
2391
|
+
chain: import_chains2.base,
|
|
2392
|
+
transport: (0, import_viem4.custom)(state.provider)
|
|
2393
|
+
});
|
|
2394
|
+
await ensureWalletOnChain(
|
|
2395
|
+
publicClient,
|
|
2396
|
+
walletClient,
|
|
2397
|
+
8453
|
|
2398
|
+
);
|
|
2399
|
+
const message = createYieldseekerSiweMessage(
|
|
2400
|
+
account,
|
|
2401
|
+
chainId,
|
|
2402
|
+
this.dependencies
|
|
2403
|
+
);
|
|
2404
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
2405
|
+
return encodeYieldseekerAuthToken({ message, signature });
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
|
|
2409
|
+
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2410
|
+
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2411
|
+
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2412
|
+
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2413
|
+
}
|
|
2414
|
+
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2415
|
+
var YieldseekerApiError = class extends Error {
|
|
2416
|
+
constructor(status, providerCode, responseFields) {
|
|
2417
|
+
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2418
|
+
this.status = status;
|
|
2419
|
+
this.providerCode = providerCode;
|
|
2420
|
+
this.responseFields = responseFields;
|
|
2421
|
+
this.name = "YieldseekerApiError";
|
|
2422
|
+
}
|
|
2423
|
+
status;
|
|
2424
|
+
providerCode;
|
|
2425
|
+
responseFields;
|
|
2426
|
+
get isAuthenticationError() {
|
|
2427
|
+
return this.status === 401 || this.status === 403;
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
function providerError(body, fallback) {
|
|
2431
|
+
if (!body || typeof body !== "object") return { code: fallback };
|
|
2432
|
+
const record = body;
|
|
2433
|
+
return {
|
|
2434
|
+
code: typeof record.message === "string" ? record.message : fallback,
|
|
2435
|
+
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
var YieldseekerApiClient = class {
|
|
2439
|
+
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch) {
|
|
2440
|
+
this.owneyApiKey = owneyApiKey;
|
|
2441
|
+
this.baseUrl = baseUrl;
|
|
2442
|
+
this.fetchFn = fetchFn;
|
|
2443
|
+
}
|
|
2444
|
+
owneyApiKey;
|
|
2445
|
+
baseUrl;
|
|
2446
|
+
fetchFn;
|
|
2447
|
+
async request(path, options = {}) {
|
|
2448
|
+
const controller = new AbortController();
|
|
2449
|
+
const timer = setTimeout(
|
|
2450
|
+
() => controller.abort(),
|
|
2451
|
+
options.timeoutMs ?? 15e3
|
|
2452
|
+
);
|
|
2453
|
+
try {
|
|
2454
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2455
|
+
method: options.method ?? "GET",
|
|
2456
|
+
headers: {
|
|
2457
|
+
"Content-Type": "application/json",
|
|
2458
|
+
"x-owney-api-key": this.owneyApiKey,
|
|
2459
|
+
...options.signature ? { "X-Signature": options.signature } : {}
|
|
2460
|
+
},
|
|
2461
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2462
|
+
signal: controller.signal
|
|
2463
|
+
});
|
|
2464
|
+
const payload = await response.json().catch(() => null);
|
|
2465
|
+
if (!response.ok) {
|
|
2466
|
+
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2467
|
+
throw new YieldseekerApiError(
|
|
2468
|
+
response.status,
|
|
2469
|
+
error.code,
|
|
2470
|
+
error.fields
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2474
|
+
return payload.data;
|
|
2475
|
+
}
|
|
2476
|
+
return payload;
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
if (error instanceof YieldseekerApiError) throw error;
|
|
2479
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2480
|
+
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2481
|
+
}
|
|
2482
|
+
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2483
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2484
|
+
});
|
|
2485
|
+
} finally {
|
|
2486
|
+
clearTimeout(timer);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
|
|
2491
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2492
|
+
var import_viem5 = require("viem");
|
|
2493
|
+
var REBALANCE_AMOUNT_DECIMALS = {
|
|
2494
|
+
USDC: 6,
|
|
2495
|
+
WETH: 18
|
|
2496
|
+
};
|
|
2497
|
+
function rebalanceAmount(value, tokenSymbol) {
|
|
2498
|
+
const decimals = REBALANCE_AMOUNT_DECIMALS[tokenSymbol.toUpperCase()];
|
|
2499
|
+
if (decimals === void 0) {
|
|
2500
|
+
return invalid(
|
|
2501
|
+
"history",
|
|
2502
|
+
`unsupported rebalance token ${tokenSymbol || "<empty>"}`
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
try {
|
|
2506
|
+
return (0, import_viem5.formatUnits)(BigInt(value), decimals);
|
|
2507
|
+
} catch {
|
|
2508
|
+
return invalid("history", `invalid rebalance amount ${value}`);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
function invalid(endpoint, detail) {
|
|
2512
|
+
throw new OwneyError(
|
|
2513
|
+
"AGENT_INVALID_RESPONSE",
|
|
2514
|
+
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2515
|
+
{ endpoint, detail },
|
|
2516
|
+
"yieldseeker"
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
function requireObject(value, endpoint) {
|
|
2520
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2521
|
+
return invalid(endpoint, "expected an object");
|
|
2522
|
+
}
|
|
2523
|
+
return value;
|
|
2524
|
+
}
|
|
2525
|
+
function requireEnvelope(value, key2, endpoint) {
|
|
2526
|
+
const root = requireObject(value, endpoint);
|
|
2527
|
+
return requireObject(root[key2], endpoint);
|
|
2528
|
+
}
|
|
2529
|
+
function token(value) {
|
|
2530
|
+
return {
|
|
2531
|
+
chain: value.chain,
|
|
2532
|
+
chainId: value.chainId,
|
|
2533
|
+
asset: value.asset,
|
|
2534
|
+
amount: String(value.amount)
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
function position(value) {
|
|
2538
|
+
return {
|
|
2539
|
+
chain: String(value.chain),
|
|
2540
|
+
protocol: String(value.protocol),
|
|
2541
|
+
...value.protocolId ? { protocolId: value.protocolId } : {},
|
|
2542
|
+
...value.pool ? { pool: value.pool } : {},
|
|
2543
|
+
asset: String(value.asset),
|
|
2544
|
+
amount: String(value.amount),
|
|
2545
|
+
...value.amountRaw !== void 0 ? { amountRaw: value.amountRaw } : {},
|
|
2546
|
+
...typeof value.apy === "number" ? { apy: value.apy } : {},
|
|
2547
|
+
...typeof value.tvl === "number" ? { tvl: value.tvl } : {},
|
|
2548
|
+
...typeof value.liquidity === "number" ? { liquidity: value.liquidity } : {}
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
function mapApyMatrix(matrix) {
|
|
2552
|
+
const mapped = {};
|
|
2553
|
+
for (const [chain, assets] of Object.entries(matrix ?? {})) {
|
|
2554
|
+
const chainId = Number(chain);
|
|
2555
|
+
if (chainId !== 8453 || !assets || typeof assets !== "object") continue;
|
|
2556
|
+
mapped[8453] = {};
|
|
2557
|
+
for (const [asset, apy] of Object.entries(assets)) {
|
|
2558
|
+
if ((asset === "USDC" || asset === "WETH") && typeof apy === "number") {
|
|
2559
|
+
mapped[8453][asset] = apy;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
return mapped;
|
|
2564
|
+
}
|
|
2565
|
+
function mapYieldseekerProfile(value) {
|
|
2566
|
+
const profile = requireEnvelope(
|
|
2567
|
+
value,
|
|
2568
|
+
"profile",
|
|
2569
|
+
"profile"
|
|
2570
|
+
);
|
|
2571
|
+
if (typeof profile.address !== "string" || typeof profile.smartWallet !== "string" || !Array.isArray(profile.chains) || typeof profile.hasActiveSessionKey !== "boolean" || !Array.isArray(profile.protocols)) {
|
|
2572
|
+
return invalid("profile", "missing required fields");
|
|
2573
|
+
}
|
|
2574
|
+
return {
|
|
2575
|
+
address: profile.address,
|
|
2576
|
+
smartWallet: profile.smartWallet,
|
|
2577
|
+
chains: profile.chains.map(Number),
|
|
2578
|
+
hasActiveSessionKey: profile.hasActiveSessionKey,
|
|
2579
|
+
protocols: profile.protocols.map(String)
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
function mapYieldseekerBalances(value) {
|
|
2583
|
+
const balances = requireEnvelope(
|
|
2584
|
+
value,
|
|
2585
|
+
"balances",
|
|
2586
|
+
"balances"
|
|
2587
|
+
);
|
|
2588
|
+
if (typeof balances.smartWallet !== "string" || typeof balances.totalBalance !== "string" || typeof balances.totalBalanceAsset !== "string" || !Array.isArray(balances.tokens) || !Array.isArray(balances.positions)) {
|
|
2589
|
+
return invalid("balances", "missing required fields");
|
|
2590
|
+
}
|
|
2591
|
+
return {
|
|
2592
|
+
smartWallet: balances.smartWallet,
|
|
2593
|
+
totalBalance: balances.totalBalance,
|
|
2594
|
+
totalBalanceAsset: balances.totalBalanceAsset.toLowerCase(),
|
|
2595
|
+
tokens: balances.tokens.map(token),
|
|
2596
|
+
positions: balances.positions.map(position)
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
function mapYieldseekerEarnings(value) {
|
|
2600
|
+
const earnings = requireEnvelope(
|
|
2601
|
+
value,
|
|
2602
|
+
"earnings",
|
|
2603
|
+
"earnings"
|
|
2604
|
+
);
|
|
2605
|
+
if (typeof earnings.smartWallet !== "string" || typeof earnings.lifetimeEarnings !== "number" || !Array.isArray(earnings.tokens)) {
|
|
2606
|
+
return invalid("earnings", "missing required fields");
|
|
2607
|
+
}
|
|
2608
|
+
return {
|
|
2609
|
+
smartWallet: earnings.smartWallet,
|
|
2610
|
+
lifetimeEarnings: earnings.lifetimeEarnings,
|
|
2611
|
+
tokens: earnings.tokens.map(token)
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
function mapYieldseekerApy(value) {
|
|
2615
|
+
const apy = requireEnvelope(
|
|
2616
|
+
value,
|
|
2617
|
+
"apy",
|
|
2618
|
+
"apy"
|
|
2619
|
+
);
|
|
2620
|
+
if (typeof apy.walletAddress !== "string" || typeof apy.weightedApyAfterFee !== "number" || !apy.apyByChainAndAsset || typeof apy.apyByChainAndAsset !== "object" || !Array.isArray(apy.history)) {
|
|
2621
|
+
return invalid("apy", "missing required fields");
|
|
2622
|
+
}
|
|
2623
|
+
return {
|
|
2624
|
+
walletAddress: apy.walletAddress,
|
|
2625
|
+
weightedApyAfterFee: apy.weightedApyAfterFee,
|
|
2626
|
+
apyByChainAndAsset: mapApyMatrix(apy.apyByChainAndAsset),
|
|
2627
|
+
history: apy.history.map((point) => ({
|
|
2628
|
+
date: String(point.date),
|
|
2629
|
+
apy: Number(point.apy)
|
|
2630
|
+
}))
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
function historyAction(value) {
|
|
2634
|
+
const normalized = value.trim().toLowerCase();
|
|
2635
|
+
if (normalized === "rebalance") return "Rebalance";
|
|
2636
|
+
if (normalized === "deposit") return "Deposit";
|
|
2637
|
+
if (normalized === "top up" || normalized === "topup") return "Top up";
|
|
2638
|
+
if (normalized === "withdraw" || normalized === "withdrawal")
|
|
2639
|
+
return "Withdraw";
|
|
2640
|
+
if (normalized === "earned" || normalized === "earnings") return "Earned";
|
|
2641
|
+
return invalid("history", `unsupported action ${value}`);
|
|
2642
|
+
}
|
|
2643
|
+
function historyItem(value) {
|
|
2644
|
+
return {
|
|
2645
|
+
agent: "yieldseeker",
|
|
2646
|
+
action: historyAction(value.action),
|
|
2647
|
+
date: String(value.date),
|
|
2648
|
+
oldApy: value.oldApy === null ? null : String(value.oldApy),
|
|
2649
|
+
newApy: value.newApy === null ? null : String(value.newApy),
|
|
2650
|
+
transactions: (value.transactions ?? []).map((transaction) => ({
|
|
2651
|
+
txHashes: transaction.txHashes.map(String),
|
|
2652
|
+
...typeof transaction.chainId === "number" ? { chainId: transaction.chainId } : {},
|
|
2653
|
+
...typeof transaction.tokenSymbol === "string" ? { tokenSymbol: transaction.tokenSymbol } : {},
|
|
2654
|
+
...typeof transaction.amount === "string" ? { amount: transaction.amount } : {}
|
|
2655
|
+
})),
|
|
2656
|
+
rebalanceLog: (value.rebalanceLog ?? []).map((entry) => ({
|
|
2657
|
+
fromProtocol: String(entry.fromProtocol),
|
|
2658
|
+
toProtocol: String(entry.toProtocol),
|
|
2659
|
+
...entry.fromPool ? { fromPool: entry.fromPool } : {},
|
|
2660
|
+
...entry.toPool ? { toPool: entry.toPool } : {},
|
|
2661
|
+
tokenSymbol: String(entry.tokenSymbol),
|
|
2662
|
+
// Unlike Yieldseeker's other read-response amounts, live history
|
|
2663
|
+
// rebalance amounts are returned in the token's smallest unit. Normalize
|
|
2664
|
+
// them at the adapter boundary so every Owney consumer receives the
|
|
2665
|
+
// shared human-readable decimal shape.
|
|
2666
|
+
amount: rebalanceAmount(String(entry.amount), String(entry.tokenSymbol)),
|
|
2667
|
+
status: entry.status
|
|
2668
|
+
}))
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function mapYieldseekerHistory(value) {
|
|
2672
|
+
const history = requireEnvelope(
|
|
2673
|
+
value,
|
|
2674
|
+
"history",
|
|
2675
|
+
"history"
|
|
2676
|
+
);
|
|
2677
|
+
if (!Array.isArray(history.data) || typeof history.hasMore !== "boolean") {
|
|
2678
|
+
return invalid("history", "missing required fields");
|
|
2679
|
+
}
|
|
2680
|
+
return {
|
|
2681
|
+
data: history.data.map(historyItem),
|
|
2682
|
+
hasMore: history.hasMore,
|
|
2683
|
+
...history.hasMore && typeof history.nextCursor === "string" ? { nextCursor: history.nextCursor } : {}
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
function mapYieldseekerAgentApy(value) {
|
|
2687
|
+
const agentApy = requireEnvelope(
|
|
2688
|
+
value,
|
|
2689
|
+
"agentApy",
|
|
2690
|
+
"agent APY"
|
|
2691
|
+
);
|
|
2692
|
+
if (typeof agentApy.averageApy !== "number") {
|
|
2693
|
+
return invalid("agent APY", "missing averageApy");
|
|
2694
|
+
}
|
|
2695
|
+
return {
|
|
2696
|
+
averageApy: agentApy.averageApy,
|
|
2697
|
+
...agentApy.detailedApys ? {
|
|
2698
|
+
detailedApys: {
|
|
2699
|
+
apyPerAsset: mapApyMatrix(agentApy.detailedApys.apyPerAsset)
|
|
2700
|
+
}
|
|
2701
|
+
} : {}
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2706
|
+
function query(params) {
|
|
2707
|
+
const search = new URLSearchParams();
|
|
2708
|
+
for (const [key2, value] of Object.entries(params)) {
|
|
2709
|
+
if (value !== void 0) search.set(key2, String(value));
|
|
2710
|
+
}
|
|
2711
|
+
const encoded = search.toString();
|
|
2712
|
+
return encoded ? `?${encoded}` : "";
|
|
2713
|
+
}
|
|
2714
|
+
var YieldseekerAgent = class {
|
|
2715
|
+
id = "yieldseeker";
|
|
2716
|
+
balanceComposition = "tokens-plus-positions";
|
|
2717
|
+
supportedChainIds = [8453];
|
|
2718
|
+
supportedAssets = [
|
|
2719
|
+
{
|
|
2720
|
+
chainId: 8453,
|
|
2721
|
+
chain: "BASE",
|
|
2722
|
+
assets: [
|
|
2723
|
+
{ symbol: "USDC", minDepositAmount: "1" },
|
|
2724
|
+
{ symbol: "WETH", minDepositAmount: "1" }
|
|
2725
|
+
]
|
|
2726
|
+
}
|
|
2727
|
+
];
|
|
2728
|
+
api;
|
|
2729
|
+
auth;
|
|
2730
|
+
transactionExecutor;
|
|
2731
|
+
unwindReceiptWaiter;
|
|
2732
|
+
activated = /* @__PURE__ */ new Set();
|
|
2733
|
+
constructor(owneyApiKey, options = {}) {
|
|
2734
|
+
this.api = new YieldseekerApiClient(
|
|
2735
|
+
owneyApiKey,
|
|
2736
|
+
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
2737
|
+
options.fetchFn
|
|
2738
|
+
);
|
|
2739
|
+
this.auth = new YieldseekerAuth(options.auth);
|
|
2740
|
+
this.transactionExecutor = options.transactionExecutor;
|
|
2741
|
+
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
2742
|
+
}
|
|
2743
|
+
async disconnect() {
|
|
2744
|
+
this.auth.clear();
|
|
2745
|
+
this.activated.clear();
|
|
2746
|
+
}
|
|
2747
|
+
async activateAgent(state, chainId, asset) {
|
|
2748
|
+
this.assertChain(chainId);
|
|
2749
|
+
await this.auth.getToken(state, chainId);
|
|
2750
|
+
if (asset === "USDC" || asset === "WETH") {
|
|
2751
|
+
await this.ensureActivated(state, chainId, asset);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
2755
|
+
this.assertChain(chainId);
|
|
2756
|
+
this.assertAsset(asset);
|
|
2757
|
+
if (BigInt(amount) <= 0n) {
|
|
2758
|
+
throw new OwneyError(
|
|
2759
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
2760
|
+
"Yieldseeker deposits must be greater than zero.",
|
|
2761
|
+
{ amount, minDepositAmount: "1" },
|
|
2762
|
+
this.id
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
await this.ensureActivated(state, chainId, asset);
|
|
2766
|
+
const response = await this.walletRequest(
|
|
2767
|
+
state,
|
|
2768
|
+
chainId,
|
|
2769
|
+
"/deposits",
|
|
2770
|
+
{ method: "POST", body: { chainId, asset, amount } }
|
|
2771
|
+
);
|
|
2772
|
+
const deposit = response?.deposit;
|
|
2773
|
+
if (!deposit?.transaction || typeof deposit.amount !== "string" || deposit.amount !== amount || typeof deposit.smartWallet !== "string" || !(0, import_viem6.isAddress)(deposit.smartWallet)) {
|
|
2774
|
+
throw this.invalidResponse("deposit");
|
|
2775
|
+
}
|
|
2776
|
+
if (depositCallback) {
|
|
2777
|
+
provideDepositVerificationContext(depositCallback, {
|
|
2778
|
+
agentId: "yieldseeker",
|
|
2779
|
+
// Re-read after /deposits so an authentication retry cannot forward
|
|
2780
|
+
// the stale token that Yieldseeker just rejected.
|
|
2781
|
+
signature: await this.auth.getToken(state, chainId)
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
const txHash = depositCallback ? await depositCallback(deposit.smartWallet, chainId, amount) : await this.submitTransaction(state, chainId, deposit.transaction);
|
|
2785
|
+
const confirmation = await this.walletRequest(
|
|
2786
|
+
state,
|
|
2787
|
+
chainId,
|
|
2788
|
+
"/deposits/confirm",
|
|
2789
|
+
{ method: "POST", body: { chainId, asset } }
|
|
2790
|
+
);
|
|
2791
|
+
if (typeof confirmation?.confirmation?.autoseekQueued !== "boolean") {
|
|
2792
|
+
throw this.invalidResponse("deposit confirmation", {
|
|
2793
|
+
transactionHash: txHash,
|
|
2794
|
+
transactionConfirmed: true
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
return {
|
|
2798
|
+
txHash,
|
|
2799
|
+
smartWallet: deposit.smartWallet,
|
|
2800
|
+
amount: deposit.amount
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
async withdraw(state, chainId, asset, amount) {
|
|
2804
|
+
this.assertChain(chainId);
|
|
2805
|
+
this.assertAsset(asset);
|
|
2806
|
+
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
2807
|
+
throw new OwneyError(
|
|
2808
|
+
"WITHDRAW_FAILED",
|
|
2809
|
+
"Yieldseeker withdrawals must be greater than zero.",
|
|
2810
|
+
{ amount },
|
|
2811
|
+
this.id
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2814
|
+
const balances = await this.getBalances(state, chainId);
|
|
2815
|
+
const decimals = asset === "USDC" ? 6 : 18;
|
|
2816
|
+
const targetAsset = asset.toUpperCase();
|
|
2817
|
+
const idle = balances.tokens.filter(
|
|
2818
|
+
(token2) => token2.chainId === chainId && token2.asset.toUpperCase() === targetAsset
|
|
2819
|
+
).reduce((total, token2) => total + (0, import_viem6.parseUnits)(token2.amount, decimals), 0n);
|
|
2820
|
+
const positions = (balances.positions ?? []).filter(
|
|
2821
|
+
(position2) => this.positionMatchesChain(position2.chain, chainId) && position2.asset.toUpperCase() === targetAsset
|
|
2822
|
+
).map((position2) => ({
|
|
2823
|
+
position: position2,
|
|
2824
|
+
amount: position2.amountRaw !== void 0 ? BigInt(position2.amountRaw) : (0, import_viem6.parseUnits)(position2.amount, decimals)
|
|
2825
|
+
}));
|
|
2826
|
+
const deployed = positions.reduce(
|
|
2827
|
+
(total, position2) => total + position2.amount,
|
|
2828
|
+
0n
|
|
2829
|
+
);
|
|
2830
|
+
const totalAvailable = idle + deployed;
|
|
2831
|
+
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
2832
|
+
if (requested > totalAvailable) {
|
|
2833
|
+
throw new OwneyError(
|
|
2834
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
2835
|
+
`Requested withdrawal "${requested.toString()}" exceeds available Yieldseeker balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
2836
|
+
{
|
|
2837
|
+
asset,
|
|
2838
|
+
requested: requested.toString(),
|
|
2839
|
+
available: totalAvailable.toString()
|
|
2840
|
+
},
|
|
2841
|
+
this.id
|
|
2842
|
+
);
|
|
2843
|
+
}
|
|
2844
|
+
const requiredFromPositions = requested > idle ? requested - idle : 0n;
|
|
2845
|
+
const unwindPlans = [];
|
|
2846
|
+
let remainingToUnwind = requiredFromPositions;
|
|
2847
|
+
for (const { position: position2, amount: positionAmount } of positions) {
|
|
2848
|
+
if (remainingToUnwind === 0n) break;
|
|
2849
|
+
if (positionAmount <= 0n) continue;
|
|
2850
|
+
if (!position2.protocolId || !(0, import_viem6.isAddress)(position2.protocolId)) {
|
|
2851
|
+
throw this.invalidResponse("position", {
|
|
2852
|
+
reason: "A deployed position is missing its vault address.",
|
|
2853
|
+
protocol: position2.protocol,
|
|
2854
|
+
pool: position2.pool
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
const unwindAmount = positionAmount < remainingToUnwind ? positionAmount : remainingToUnwind;
|
|
2858
|
+
unwindPlans.push({
|
|
2859
|
+
vaultAddress: (0, import_viem6.getAddress)(position2.protocolId),
|
|
2860
|
+
amount: unwindAmount
|
|
2861
|
+
});
|
|
2862
|
+
remainingToUnwind -= unwindAmount;
|
|
2863
|
+
}
|
|
2864
|
+
if (remainingToUnwind > 0n) {
|
|
2865
|
+
throw this.invalidResponse("balances", {
|
|
2866
|
+
reason: "Deployed position balances could not cover the unwind.",
|
|
2867
|
+
required: requiredFromPositions.toString(),
|
|
2868
|
+
planned: (requiredFromPositions - remainingToUnwind).toString()
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
for (const plan of unwindPlans) {
|
|
2872
|
+
const response2 = await this.walletRequest(
|
|
2873
|
+
state,
|
|
2874
|
+
chainId,
|
|
2875
|
+
"/positions/unwind",
|
|
2876
|
+
{
|
|
2877
|
+
method: "POST",
|
|
2878
|
+
body: {
|
|
2879
|
+
chainId,
|
|
2880
|
+
asset,
|
|
2881
|
+
vaultAddress: plan.vaultAddress,
|
|
2882
|
+
amount: plan.amount.toString()
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
);
|
|
2886
|
+
const transactionHash = response2?.unwind?.transactionHash;
|
|
2887
|
+
if (!this.isTransactionHash(transactionHash)) {
|
|
2888
|
+
throw this.invalidResponse("position unwind");
|
|
2889
|
+
}
|
|
2890
|
+
await this.waitForUnwindReceipt(state, chainId, transactionHash);
|
|
2891
|
+
}
|
|
2892
|
+
const response = await this.walletRequest(
|
|
2893
|
+
state,
|
|
2894
|
+
chainId,
|
|
2895
|
+
"/withdrawals",
|
|
2896
|
+
{
|
|
2897
|
+
method: "POST",
|
|
2898
|
+
body: { chainId, asset, ...amount !== void 0 ? { amount } : {} }
|
|
2899
|
+
}
|
|
2900
|
+
);
|
|
2901
|
+
const withdrawal = response?.withdrawal;
|
|
2902
|
+
if (!withdrawal?.transaction || typeof withdrawal.amount !== "string") {
|
|
2903
|
+
throw this.invalidResponse("withdrawal");
|
|
2904
|
+
}
|
|
2905
|
+
const txHash = await this.submitTransaction(
|
|
2906
|
+
state,
|
|
2907
|
+
chainId,
|
|
2908
|
+
withdrawal.transaction
|
|
2909
|
+
);
|
|
2910
|
+
return {
|
|
2911
|
+
txHash,
|
|
2912
|
+
type: amount === void 0 ? "full" : "partial",
|
|
2913
|
+
amount: withdrawal.amount
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
async getBalances(state, chainId) {
|
|
2917
|
+
return mapYieldseekerBalances(
|
|
2918
|
+
await this.walletRequest(
|
|
2919
|
+
state,
|
|
2920
|
+
chainId,
|
|
2921
|
+
`/balances${query({ chainId })}`
|
|
2922
|
+
)
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
2925
|
+
async getEarnings(state, chainId) {
|
|
2926
|
+
return mapYieldseekerEarnings(
|
|
2927
|
+
await this.walletRequest(
|
|
2928
|
+
state,
|
|
2929
|
+
chainId,
|
|
2930
|
+
`/earnings${query({ chainId })}`
|
|
2931
|
+
)
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
2935
|
+
return mapYieldseekerApy(
|
|
2936
|
+
await this.walletRequest(
|
|
2937
|
+
state,
|
|
2938
|
+
chainId,
|
|
2939
|
+
`/apy${query({ chainId, days, tokenSymbol })}`
|
|
2940
|
+
)
|
|
2941
|
+
);
|
|
2942
|
+
}
|
|
2943
|
+
async getHistory(state, chainId, options) {
|
|
2944
|
+
return mapYieldseekerHistory(
|
|
2945
|
+
await this.walletRequest(
|
|
2946
|
+
state,
|
|
2947
|
+
chainId,
|
|
2948
|
+
`/history${query({
|
|
2949
|
+
chainId,
|
|
2950
|
+
limit: options?.limit ?? 10,
|
|
2951
|
+
cursor: options?.cursor,
|
|
2952
|
+
fromDate: options?.fromDate,
|
|
2953
|
+
toDate: options?.toDate
|
|
2954
|
+
})}`
|
|
2955
|
+
)
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2958
|
+
async getUserProfile(state, chainId) {
|
|
2959
|
+
return mapYieldseekerProfile(
|
|
2960
|
+
await this.walletRequest(state, chainId, `/profile${query({ chainId })}`)
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
async getAgentApy(days, options) {
|
|
2964
|
+
this.assertOptionalChain(options?.chainId);
|
|
2965
|
+
return mapYieldseekerAgentApy(
|
|
2966
|
+
await this.api.request(
|
|
2967
|
+
`/agent/apy${query({
|
|
2968
|
+
days,
|
|
2969
|
+
tokenSymbol: options?.tokenSymbol,
|
|
2970
|
+
chainId: options?.chainId
|
|
2971
|
+
})}`
|
|
2972
|
+
)
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
async ensureActivated(state, chainId, asset) {
|
|
2976
|
+
const key2 = `${state.walletAddress.toLowerCase()}:${chainId}:${asset}`;
|
|
2977
|
+
if (this.activated.has(key2)) return;
|
|
2978
|
+
const response = await this.walletRequest(
|
|
2979
|
+
state,
|
|
2980
|
+
chainId,
|
|
2981
|
+
"/activation",
|
|
2982
|
+
{ method: "POST", body: { chainId, asset } }
|
|
2983
|
+
);
|
|
2984
|
+
if (typeof response?.activation?.smartWallet !== "string" || typeof response.activation.deployed !== "boolean" || typeof response.activation.hasActiveSessionKey !== "boolean") {
|
|
2985
|
+
throw this.invalidResponse("activation");
|
|
2986
|
+
}
|
|
2987
|
+
this.activated.add(key2);
|
|
2988
|
+
}
|
|
2989
|
+
async walletRequest(state, chainId, path, options = {}) {
|
|
2990
|
+
this.assertChain(chainId);
|
|
2991
|
+
let signature = await this.auth.getToken(state, chainId);
|
|
2992
|
+
try {
|
|
2993
|
+
return await this.api.request(path, { ...options, signature });
|
|
2994
|
+
} catch (error) {
|
|
2995
|
+
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
2996
|
+
if (error.isAuthenticationError) {
|
|
2997
|
+
this.auth.clear(state, chainId);
|
|
2998
|
+
signature = await this.auth.getToken(state, chainId);
|
|
2999
|
+
try {
|
|
3000
|
+
return await this.api.request(path, { ...options, signature });
|
|
3001
|
+
} catch (retryError) {
|
|
3002
|
+
throw this.mapApiError(retryError);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
throw this.mapApiError(error);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
mapApiError(error) {
|
|
3009
|
+
if (!(error instanceof YieldseekerApiError)) {
|
|
3010
|
+
return new OwneyError(
|
|
3011
|
+
"AGENT_API_ERROR",
|
|
3012
|
+
"Yieldseeker request failed.",
|
|
3013
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
3014
|
+
this.id
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
3018
|
+
return new OwneyError(
|
|
3019
|
+
code,
|
|
3020
|
+
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3021
|
+
{
|
|
3022
|
+
statusCode: error.status,
|
|
3023
|
+
providerCode: error.providerCode,
|
|
3024
|
+
...error.responseFields ? { fields: error.responseFields } : {}
|
|
3025
|
+
},
|
|
3026
|
+
this.id
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
async submitTransaction(state, chainId, transaction) {
|
|
3030
|
+
if (this.transactionExecutor) {
|
|
3031
|
+
return this.transactionExecutor(state, chainId, transaction);
|
|
3032
|
+
}
|
|
3033
|
+
if (typeof transaction.from !== "string" || typeof transaction.to !== "string" || typeof transaction.data !== "string" || typeof transaction.value !== "string" || transaction.chainId !== chainId) {
|
|
3034
|
+
throw this.invalidResponse("transaction");
|
|
3035
|
+
}
|
|
3036
|
+
const account = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3037
|
+
if ((0, import_viem6.getAddress)(transaction.from) !== account) {
|
|
3038
|
+
throw this.invalidResponse("transaction", {
|
|
3039
|
+
reason: "transaction.from does not match the connected wallet"
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
const walletClient = (0, import_viem6.createWalletClient)({
|
|
3043
|
+
account,
|
|
3044
|
+
chain: import_chains3.base,
|
|
3045
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
3046
|
+
});
|
|
3047
|
+
const publicClient = (0, import_viem6.createPublicClient)({
|
|
3048
|
+
chain: import_chains3.base,
|
|
3049
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
3050
|
+
});
|
|
3051
|
+
await ensureWalletOnChain(
|
|
3052
|
+
publicClient,
|
|
3053
|
+
walletClient,
|
|
3054
|
+
8453
|
|
3055
|
+
);
|
|
3056
|
+
const hash = await walletClient.sendTransaction({
|
|
3057
|
+
account,
|
|
3058
|
+
chain: import_chains3.base,
|
|
3059
|
+
to: (0, import_viem6.getAddress)(transaction.to),
|
|
3060
|
+
data: transaction.data,
|
|
3061
|
+
value: BigInt(transaction.value)
|
|
3062
|
+
});
|
|
3063
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3064
|
+
hash,
|
|
3065
|
+
confirmations: 1
|
|
3066
|
+
});
|
|
3067
|
+
if (receipt.status !== "success") {
|
|
3068
|
+
throw new OwneyError(
|
|
3069
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3070
|
+
`Yieldseeker transaction reverted (${hash}).`,
|
|
3071
|
+
{ transactionHash: hash },
|
|
3072
|
+
this.id
|
|
3073
|
+
);
|
|
3074
|
+
}
|
|
3075
|
+
return hash;
|
|
3076
|
+
}
|
|
3077
|
+
async waitForUnwindReceipt(state, chainId, transactionHash) {
|
|
3078
|
+
if (this.unwindReceiptWaiter) {
|
|
3079
|
+
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
3082
|
+
const publicClient = (0, import_viem6.createPublicClient)({
|
|
3083
|
+
chain: import_chains3.base,
|
|
3084
|
+
transport: (0, import_viem6.custom)(state.provider)
|
|
3085
|
+
});
|
|
3086
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3087
|
+
hash: transactionHash,
|
|
3088
|
+
confirmations: 1
|
|
3089
|
+
});
|
|
3090
|
+
if (receipt.status !== "success") {
|
|
3091
|
+
throw new OwneyError(
|
|
3092
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3093
|
+
`Yieldseeker position unwind reverted (${transactionHash}).`,
|
|
3094
|
+
{ transactionHash },
|
|
3095
|
+
this.id
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
positionMatchesChain(chain, chainId) {
|
|
3100
|
+
const normalized = chain.trim().toUpperCase();
|
|
3101
|
+
return normalized === String(chainId) || normalized === "BASE";
|
|
3102
|
+
}
|
|
3103
|
+
isTransactionHash(value) {
|
|
3104
|
+
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3105
|
+
}
|
|
3106
|
+
assertChain(chainId) {
|
|
3107
|
+
if (chainId !== 8453) {
|
|
3108
|
+
throw new OwneyError(
|
|
3109
|
+
"CHAIN_UNSUPPORTED",
|
|
3110
|
+
`Yieldseeker does not support chain ${chainId}.`,
|
|
3111
|
+
{ chainId, supportedChainIds: [8453] },
|
|
3112
|
+
this.id
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
assertOptionalChain(chainId) {
|
|
3117
|
+
if (chainId !== void 0) this.assertChain(chainId);
|
|
3118
|
+
}
|
|
3119
|
+
assertAsset(asset) {
|
|
3120
|
+
if (asset !== "USDC" && asset !== "WETH") {
|
|
3121
|
+
throw new OwneyError(
|
|
3122
|
+
"ASSET_UNSUPPORTED",
|
|
3123
|
+
`Yieldseeker does not support asset ${asset}.`,
|
|
3124
|
+
{ asset, supportedAssets: ["USDC", "WETH"] },
|
|
3125
|
+
this.id
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
invalidResponse(operation, details = {}) {
|
|
3130
|
+
return new OwneyError(
|
|
3131
|
+
"AGENT_INVALID_RESPONSE",
|
|
3132
|
+
`Yieldseeker returned an invalid ${operation} response.`,
|
|
3133
|
+
details,
|
|
3134
|
+
this.id
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
};
|
|
3138
|
+
|
|
3139
|
+
// src/lib/routing-api.ts
|
|
3140
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3141
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3142
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
3143
|
+
try {
|
|
3144
|
+
const res = await fetch(url, {
|
|
3145
|
+
method: "GET",
|
|
3146
|
+
headers: {
|
|
3147
|
+
"Content-Type": "application/json",
|
|
3148
|
+
"x-owney-api-key": `${apiKey}`
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
if (!res.ok) {
|
|
3152
|
+
if (res.status !== 404) {
|
|
3153
|
+
console.warn(
|
|
3154
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
return null;
|
|
3158
|
+
}
|
|
3159
|
+
const json = await res.json();
|
|
3160
|
+
const policy = json.success ? json.data ?? null : null;
|
|
3161
|
+
debugLog(
|
|
3162
|
+
"owney-sdk",
|
|
3163
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
3164
|
+
policy ?? void 0
|
|
3165
|
+
);
|
|
3166
|
+
return policy;
|
|
3167
|
+
} catch (error) {
|
|
3168
|
+
console.warn(
|
|
3169
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
3170
|
+
error instanceof Error ? error.message : String(error)
|
|
3171
|
+
);
|
|
3172
|
+
return null;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3176
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
3177
|
+
const res = await fetch(url, {
|
|
3178
|
+
method: "GET",
|
|
3179
|
+
headers: {
|
|
3180
|
+
"Content-Type": "application/json",
|
|
3181
|
+
"x-owney-api-key": `${apiKey}`
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
if (!res.ok) {
|
|
3185
|
+
const text = await res.text().catch(() => "");
|
|
3186
|
+
throw new OwneyError(
|
|
3187
|
+
"API_ROUTING_ERROR",
|
|
3188
|
+
`Routing API error ${res.status}: ${text}`,
|
|
3189
|
+
{ statusCode: res.status, responseBody: text }
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
const json = await res.json();
|
|
3193
|
+
if (!json.success) {
|
|
3194
|
+
throw new OwneyError(
|
|
3195
|
+
"API_ROUTING_FAILED",
|
|
3196
|
+
`Routing API request failed: ${json.message}`,
|
|
3197
|
+
{ message: json.message }
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
return json.data;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
// src/lib/health-report.ts
|
|
3204
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3205
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
|
|
3206
|
+
try {
|
|
3207
|
+
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
3208
|
+
method: "POST",
|
|
3209
|
+
headers: {
|
|
3210
|
+
"Content-Type": "application/json",
|
|
3211
|
+
"x-owney-api-key": apiKey
|
|
3212
|
+
},
|
|
3213
|
+
body: JSON.stringify({
|
|
3214
|
+
agent_type: agentType,
|
|
3215
|
+
error_code: errorCode,
|
|
3216
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3217
|
+
})
|
|
3218
|
+
});
|
|
3219
|
+
} catch (err) {
|
|
3220
|
+
console.warn(
|
|
3221
|
+
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
3222
|
+
err instanceof Error ? err.message : err
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
3227
|
+
try {
|
|
3228
|
+
return await fn();
|
|
3229
|
+
} catch (err) {
|
|
3230
|
+
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
3231
|
+
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
3232
|
+
throw err;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
3237
|
+
var import_viem7 = require("viem");
|
|
3238
|
+
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
3239
|
+
const target = asset.toUpperCase();
|
|
3240
|
+
return agents.map((agent) => {
|
|
3241
|
+
const agentBalance = aggregated[agent.id];
|
|
3242
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
3243
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
3244
|
+
);
|
|
3245
|
+
let balance = tokenBalance ? (0, import_viem7.parseUnits)(tokenBalance.amount, decimals) : 0n;
|
|
3246
|
+
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
3247
|
+
const chainNameById = {
|
|
3248
|
+
1: "ETHEREUM",
|
|
3249
|
+
8453: "BASE",
|
|
3250
|
+
42161: "ARBITRUM"
|
|
3251
|
+
};
|
|
3252
|
+
const targetChain = chainNameById[chainId];
|
|
3253
|
+
for (const position2 of agentBalance?.positions ?? []) {
|
|
3254
|
+
const positionChain = position2.chain.trim().toUpperCase();
|
|
3255
|
+
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
3256
|
+
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
3257
|
+
if (position2.amountRaw !== void 0) {
|
|
3258
|
+
try {
|
|
3259
|
+
balance += BigInt(position2.amountRaw);
|
|
3260
|
+
continue;
|
|
3261
|
+
} catch {
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
return { agent, balance };
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
3271
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
3272
|
+
agent,
|
|
3273
|
+
balance,
|
|
3274
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
3275
|
+
}));
|
|
3276
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
3277
|
+
let remainder = requested - assigned;
|
|
3278
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
3279
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
3280
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2332
3281
|
});
|
|
3282
|
+
for (const p of byHeadroom) {
|
|
3283
|
+
if (remainder === 0n) break;
|
|
3284
|
+
const headroom = p.balance - p.planned;
|
|
3285
|
+
if (headroom <= 0n) continue;
|
|
3286
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
3287
|
+
p.planned += take;
|
|
3288
|
+
remainder -= take;
|
|
3289
|
+
}
|
|
3290
|
+
return plans;
|
|
2333
3291
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
}
|
|
2341
|
-
|
|
2342
|
-
|
|
3292
|
+
function planDisabledDrain(disabled, requested) {
|
|
3293
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
3294
|
+
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
3295
|
+
);
|
|
3296
|
+
const plans = [];
|
|
3297
|
+
let remaining = requested;
|
|
3298
|
+
for (const { agent, balance } of sorted) {
|
|
3299
|
+
if (remaining === 0n) {
|
|
3300
|
+
plans.push({ agent, balance, planned: 0n });
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
const take = balance < remaining ? balance : remaining;
|
|
3304
|
+
plans.push({ agent, balance, planned: take });
|
|
3305
|
+
remaining -= take;
|
|
3306
|
+
}
|
|
3307
|
+
return { plans, remaining };
|
|
2343
3308
|
}
|
|
2344
|
-
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
3309
|
+
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
3310
|
+
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
3311
|
+
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
3312
|
+
const totalHeadroom = candidates.reduce(
|
|
3313
|
+
(s, c) => s + (c.balance - c.planned),
|
|
3314
|
+
0n
|
|
3315
|
+
);
|
|
3316
|
+
if (totalHeadroom === 0n) return;
|
|
3317
|
+
let distributed = 0n;
|
|
3318
|
+
for (const c of candidates) {
|
|
3319
|
+
const headroom = c.balance - c.planned;
|
|
3320
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
3321
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
3322
|
+
c.planned += give;
|
|
3323
|
+
distributed += give;
|
|
2359
3324
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
3325
|
+
let leftover = amount - distributed;
|
|
3326
|
+
for (const c of candidates) {
|
|
3327
|
+
if (leftover === 0n) break;
|
|
3328
|
+
const headroom = c.balance - c.planned;
|
|
3329
|
+
if (headroom <= 0n) continue;
|
|
3330
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
3331
|
+
c.planned += take;
|
|
3332
|
+
leftover -= take;
|
|
2367
3333
|
}
|
|
2368
3334
|
}
|
|
3335
|
+
function sumWithdrawnAmount(results) {
|
|
3336
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
3337
|
+
}
|
|
2369
3338
|
|
|
2370
|
-
// src/lib/
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
`No sponsored token configured for chain ${chainId}`
|
|
2381
|
-
);
|
|
3339
|
+
// src/lib/helpers/account-apy-helper.ts
|
|
3340
|
+
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
3341
|
+
if (!tokenSymbol) {
|
|
3342
|
+
const total = Number(balance.totalBalance);
|
|
3343
|
+
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
3344
|
+
}
|
|
3345
|
+
const normalizedToken = tokenSymbol.toUpperCase();
|
|
3346
|
+
return balance.tokens.reduce((total, token2) => {
|
|
3347
|
+
if (Number(token2.chainId) !== chainId || String(token2.asset).toUpperCase() !== normalizedToken) {
|
|
3348
|
+
return total;
|
|
2382
3349
|
}
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
account: deps.ownerAddress,
|
|
2424
|
-
...typedData
|
|
2425
|
-
});
|
|
2426
|
-
deps.onApproved?.();
|
|
2427
|
-
const result = await post({
|
|
2428
|
-
baseUrl: deps.baseUrl,
|
|
2429
|
-
apiKey: deps.apiKey,
|
|
2430
|
-
body: {
|
|
2431
|
-
chainId: cid,
|
|
2432
|
-
token,
|
|
2433
|
-
from: deps.ownerAddress,
|
|
2434
|
-
to: smartWallet,
|
|
2435
|
-
value: amount,
|
|
2436
|
-
validAfter: validAfter.toString(),
|
|
2437
|
-
validBefore: validBefore.toString(),
|
|
2438
|
-
nonce,
|
|
2439
|
-
authSignature,
|
|
2440
|
-
tokenName,
|
|
2441
|
-
tokenVersion
|
|
3350
|
+
const amount = Number(token2.amount);
|
|
3351
|
+
return Number.isFinite(amount) && amount > 0 ? total + amount : total;
|
|
3352
|
+
}, 0);
|
|
3353
|
+
}
|
|
3354
|
+
function aggregateApyHistory(agentApys, agentBalances) {
|
|
3355
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
3356
|
+
for (const [id, accountApy] of Object.entries(agentApys)) {
|
|
3357
|
+
const balance = agentBalances[id] ?? 0;
|
|
3358
|
+
if (!Number.isFinite(balance) || balance <= 0) continue;
|
|
3359
|
+
for (const point of accountApy.history ?? []) {
|
|
3360
|
+
const apy = Number(point.apy);
|
|
3361
|
+
if (!point.date || !Number.isFinite(apy)) continue;
|
|
3362
|
+
const current = byDate.get(point.date) ?? { weightedSum: 0, weight: 0 };
|
|
3363
|
+
current.weightedSum += apy * balance;
|
|
3364
|
+
current.weight += balance;
|
|
3365
|
+
byDate.set(point.date, current);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
3369
|
+
date,
|
|
3370
|
+
apy: value.weightedSum / value.weight
|
|
3371
|
+
}));
|
|
3372
|
+
}
|
|
3373
|
+
function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
3374
|
+
const sums = {};
|
|
3375
|
+
const weights = {};
|
|
3376
|
+
for (const id of Object.keys(agentApys)) {
|
|
3377
|
+
const cells = agentApys[id].apyByChainAndAsset;
|
|
3378
|
+
const balance = agentBalances[id] ?? 0;
|
|
3379
|
+
if (!cells || balance <= 0) continue;
|
|
3380
|
+
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
3381
|
+
if (!perAsset) continue;
|
|
3382
|
+
const chainId = Number(chainKey);
|
|
3383
|
+
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
3384
|
+
const apy = Number(apyValue ?? 0);
|
|
3385
|
+
if (apy === 0) continue;
|
|
3386
|
+
sums[chainId] ??= {};
|
|
3387
|
+
weights[chainId] ??= {};
|
|
3388
|
+
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
3389
|
+
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2442
3390
|
}
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
};
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
const out = {};
|
|
3394
|
+
for (const chainKey of Object.keys(sums)) {
|
|
3395
|
+
const chainId = Number(chainKey);
|
|
3396
|
+
const perAssetOut = {};
|
|
3397
|
+
for (const asset of Object.keys(sums[chainId])) {
|
|
3398
|
+
const w = weights[chainId][asset];
|
|
3399
|
+
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
3400
|
+
}
|
|
3401
|
+
if (Object.keys(perAssetOut).length > 0) {
|
|
3402
|
+
out[chainId] = perAssetOut;
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
return out;
|
|
2446
3406
|
}
|
|
2447
3407
|
|
|
3408
|
+
// src/client.ts
|
|
3409
|
+
var import_viem9 = require("viem");
|
|
3410
|
+
var import_chains4 = require("viem/chains");
|
|
3411
|
+
|
|
2448
3412
|
// src/lib/sponsored-weth-deposit.ts
|
|
2449
3413
|
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
2450
3414
|
function makeSponsoredWethCallback(deps) {
|
|
2451
3415
|
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
2452
3416
|
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
2453
|
-
return
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
"CHAIN_UNSUPPORTED",
|
|
2459
|
-
`No sponsored WETH configured for chain ${chainId}`
|
|
2460
|
-
);
|
|
2461
|
-
}
|
|
2462
|
-
const amountWei = BigInt(amount);
|
|
2463
|
-
const pub = deps.getPublicClient(cid);
|
|
2464
|
-
const wallet = deps.getWalletClient(cid);
|
|
2465
|
-
await ensureWalletOnChain(pub, wallet, cid);
|
|
2466
|
-
try {
|
|
2467
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2468
|
-
if (balance < amountWei) {
|
|
3417
|
+
return makeVerificationAwareDepositCallback(
|
|
3418
|
+
async (smartWallet, chainId, amount, verification) => {
|
|
3419
|
+
const cid = chainId;
|
|
3420
|
+
const token2 = deps.tokenAddressByChain[cid];
|
|
3421
|
+
if (!token2) {
|
|
2469
3422
|
throw new OwneyError(
|
|
2470
|
-
"
|
|
2471
|
-
|
|
2472
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
3423
|
+
"CHAIN_UNSUPPORTED",
|
|
3424
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
2473
3425
|
);
|
|
2474
3426
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
3427
|
+
const amountWei = BigInt(amount);
|
|
3428
|
+
const pub = deps.getPublicClient(cid);
|
|
3429
|
+
const wallet = deps.getWalletClient(cid);
|
|
3430
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
3431
|
+
try {
|
|
3432
|
+
const balance = await readErc20Balance(pub, token2, deps.ownerAddress);
|
|
3433
|
+
if (balance < amountWei) {
|
|
3434
|
+
throw new OwneyError(
|
|
3435
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
3436
|
+
"Insufficient WETH balance for this deposit.",
|
|
3437
|
+
{ token: token2, chainId: cid, balance: balance.toString(), amount }
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
} catch (err) {
|
|
3441
|
+
if (err instanceof OwneyError) throw err;
|
|
3442
|
+
console.warn(
|
|
3443
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
3444
|
+
err instanceof Error ? err.message : String(err)
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
const allowance = await readPermit2Allowance(
|
|
3448
|
+
pub,
|
|
3449
|
+
token2,
|
|
3450
|
+
deps.ownerAddress
|
|
2488
3451
|
);
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const nonce = randomPermit2Nonce();
|
|
2496
|
-
const deadline = BigInt(
|
|
2497
|
-
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
2498
|
-
);
|
|
2499
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
2500
|
-
chainId: cid,
|
|
2501
|
-
message: {
|
|
2502
|
-
permitted: { token, amount: amountWei },
|
|
2503
|
-
spender: relayer,
|
|
2504
|
-
nonce,
|
|
2505
|
-
deadline
|
|
3452
|
+
if (allowance < amountWei) {
|
|
3453
|
+
throw new OwneyError(
|
|
3454
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
3455
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
3456
|
+
{ token: token2, chainId: cid, allowance: allowance.toString(), amount }
|
|
3457
|
+
);
|
|
2506
3458
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
3459
|
+
const relayer = await get({
|
|
3460
|
+
baseUrl: deps.baseUrl,
|
|
3461
|
+
apiKey: deps.apiKey,
|
|
3462
|
+
chainId: cid
|
|
3463
|
+
});
|
|
3464
|
+
const nonce = randomPermit2Nonce();
|
|
3465
|
+
const deadline = BigInt(
|
|
3466
|
+
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
3467
|
+
);
|
|
3468
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
2517
3469
|
chainId: cid,
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
3470
|
+
message: {
|
|
3471
|
+
permitted: { token: token2, amount: amountWei },
|
|
3472
|
+
spender: relayer,
|
|
3473
|
+
nonce,
|
|
3474
|
+
deadline
|
|
3475
|
+
}
|
|
3476
|
+
});
|
|
3477
|
+
const signature = await wallet.signTypedData({
|
|
3478
|
+
account: deps.ownerAddress,
|
|
3479
|
+
...typedData
|
|
3480
|
+
});
|
|
3481
|
+
deps.onApproved?.();
|
|
3482
|
+
const result = await post({
|
|
3483
|
+
baseUrl: deps.baseUrl,
|
|
3484
|
+
apiKey: deps.apiKey,
|
|
3485
|
+
...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
|
|
3486
|
+
body: {
|
|
3487
|
+
chainId: cid,
|
|
3488
|
+
token: token2,
|
|
3489
|
+
from: deps.ownerAddress,
|
|
3490
|
+
to: smartWallet,
|
|
3491
|
+
amount,
|
|
3492
|
+
nonce: nonce.toString(),
|
|
3493
|
+
deadline: deadline.toString(),
|
|
3494
|
+
signature
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3497
|
+
return result.txHash;
|
|
3498
|
+
}
|
|
3499
|
+
);
|
|
2529
3500
|
}
|
|
2530
3501
|
|
|
2531
3502
|
// src/lib/sponsored-calls-deposit.ts
|
|
2532
|
-
var
|
|
3503
|
+
var import_viem8 = require("viem");
|
|
2533
3504
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
2534
3505
|
var DEFAULT_MAX_POLLS = 30;
|
|
2535
3506
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -2537,7 +3508,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
2537
3508
|
method: "wallet_getCapabilities",
|
|
2538
3509
|
params: [owner]
|
|
2539
3510
|
});
|
|
2540
|
-
const forChain = caps?.[(0,
|
|
3511
|
+
const forChain = caps?.[(0, import_viem8.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
2541
3512
|
return Boolean(forChain?.paymasterService?.supported);
|
|
2542
3513
|
}
|
|
2543
3514
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -2557,8 +3528,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2557
3528
|
};
|
|
2558
3529
|
return async (smartWallet, chainId, amount) => {
|
|
2559
3530
|
const cid = chainId;
|
|
2560
|
-
const
|
|
2561
|
-
if (!
|
|
3531
|
+
const token2 = deps.tokenAddressByChain[cid];
|
|
3532
|
+
if (!token2) {
|
|
2562
3533
|
throw new OwneyError(
|
|
2563
3534
|
"CHAIN_UNSUPPORTED",
|
|
2564
3535
|
`No sponsored token configured for chain ${chainId}`
|
|
@@ -2571,8 +3542,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2571
3542
|
{ chainId }
|
|
2572
3543
|
);
|
|
2573
3544
|
}
|
|
2574
|
-
const data = (0,
|
|
2575
|
-
abi:
|
|
3545
|
+
const data = (0, import_viem8.encodeFunctionData)({
|
|
3546
|
+
abi: import_viem8.erc20Abi,
|
|
2576
3547
|
functionName: "transfer",
|
|
2577
3548
|
args: [smartWallet, BigInt(amount)]
|
|
2578
3549
|
});
|
|
@@ -2582,9 +3553,9 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2582
3553
|
{
|
|
2583
3554
|
version: "2.0.0",
|
|
2584
3555
|
from: deps.ownerAddress,
|
|
2585
|
-
chainId: (0,
|
|
3556
|
+
chainId: (0, import_viem8.toHex)(chainId),
|
|
2586
3557
|
atomicRequired: false,
|
|
2587
|
-
calls: [{ to:
|
|
3558
|
+
calls: [{ to: token2, value: "0x0", data }],
|
|
2588
3559
|
capabilities: {
|
|
2589
3560
|
paymasterService: { url: absolutePaymasterUrl() }
|
|
2590
3561
|
}
|
|
@@ -2623,10 +3594,10 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2623
3594
|
function encodeMultiAgentCursor(map) {
|
|
2624
3595
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
2625
3596
|
}
|
|
2626
|
-
function decodeMultiAgentCursor(
|
|
3597
|
+
function decodeMultiAgentCursor(token2) {
|
|
2627
3598
|
let parsed;
|
|
2628
3599
|
try {
|
|
2629
|
-
const json = Buffer.from(
|
|
3600
|
+
const json = Buffer.from(token2, "base64").toString("utf8");
|
|
2630
3601
|
parsed = JSON.parse(json);
|
|
2631
3602
|
} catch {
|
|
2632
3603
|
throw new InvalidHistoryCursorError(
|
|
@@ -2646,9 +3617,9 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
2646
3617
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
2647
3618
|
};
|
|
2648
3619
|
var VIEM_CHAIN2 = {
|
|
2649
|
-
8453:
|
|
2650
|
-
42161:
|
|
2651
|
-
1:
|
|
3620
|
+
8453: import_chains4.base,
|
|
3621
|
+
42161: import_chains4.arbitrum,
|
|
3622
|
+
1: import_chains4.mainnet
|
|
2652
3623
|
};
|
|
2653
3624
|
var SPONSORED_WETH_BY_CHAIN = {
|
|
2654
3625
|
8453: "0x4200000000000000000000000000000000000006",
|
|
@@ -2676,6 +3647,7 @@ var OwneySDK = class {
|
|
|
2676
3647
|
orgAgentConfig;
|
|
2677
3648
|
orgAgentConfigPromise = null;
|
|
2678
3649
|
zyfaiRpcUrls;
|
|
3650
|
+
yieldseekerApiBaseUrl;
|
|
2679
3651
|
routingApiBaseUrl;
|
|
2680
3652
|
referralSource;
|
|
2681
3653
|
cachedSponsoredCallback = null;
|
|
@@ -2687,6 +3659,7 @@ var OwneySDK = class {
|
|
|
2687
3659
|
this.apiKey = config.apiKey;
|
|
2688
3660
|
if (config.debug) setOwneyDebug(true);
|
|
2689
3661
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
3662
|
+
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
2690
3663
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
2691
3664
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
2692
3665
|
this.referralSource = config.referralSource;
|
|
@@ -2789,14 +3762,14 @@ var OwneySDK = class {
|
|
|
2789
3762
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2790
3763
|
// PublicClient/WalletClient param types — structurally identical at
|
|
2791
3764
|
// runtime, but the two share a name TS treats as unrelated.
|
|
2792
|
-
getPublicClient: (cid) => (0,
|
|
3765
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
2793
3766
|
chain: VIEM_CHAIN2[cid],
|
|
2794
|
-
transport: (0,
|
|
3767
|
+
transport: (0, import_viem9.custom)(provider)
|
|
2795
3768
|
}),
|
|
2796
|
-
getWalletClient: (cid) => (0,
|
|
3769
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
2797
3770
|
account: owner,
|
|
2798
3771
|
chain: VIEM_CHAIN2[cid],
|
|
2799
|
-
transport: (0,
|
|
3772
|
+
transport: (0, import_viem9.custom)(provider)
|
|
2800
3773
|
})
|
|
2801
3774
|
});
|
|
2802
3775
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
@@ -2842,14 +3815,14 @@ var OwneySDK = class {
|
|
|
2842
3815
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
2843
3816
|
// PublicClient/WalletClient param types — structurally identical at
|
|
2844
3817
|
// runtime, but the two share a name TS treats as unrelated.
|
|
2845
|
-
getPublicClient: (cid) => (0,
|
|
3818
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
2846
3819
|
chain: VIEM_CHAIN2[cid],
|
|
2847
|
-
transport: (0,
|
|
3820
|
+
transport: (0, import_viem9.custom)(provider)
|
|
2848
3821
|
}),
|
|
2849
|
-
getWalletClient: (cid) => (0,
|
|
3822
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
2850
3823
|
account: owner,
|
|
2851
3824
|
chain: VIEM_CHAIN2[cid],
|
|
2852
|
-
transport: (0,
|
|
3825
|
+
transport: (0, import_viem9.custom)(provider)
|
|
2853
3826
|
})
|
|
2854
3827
|
});
|
|
2855
3828
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -2877,12 +3850,13 @@ var OwneySDK = class {
|
|
|
2877
3850
|
async getOrgAgentConfig() {
|
|
2878
3851
|
if (this.orgAgentConfig !== void 0) return this.orgAgentConfig;
|
|
2879
3852
|
if (!this.orgAgentConfigPromise) {
|
|
2880
|
-
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
3853
|
+
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
3854
|
+
this.apiKey,
|
|
3855
|
+
this.routingApiBaseUrl
|
|
3856
|
+
).then((config) => {
|
|
3857
|
+
this.orgAgentConfig = config;
|
|
3858
|
+
return config;
|
|
3859
|
+
});
|
|
2886
3860
|
}
|
|
2887
3861
|
return this.orgAgentConfigPromise;
|
|
2888
3862
|
}
|
|
@@ -2917,9 +3891,19 @@ var OwneySDK = class {
|
|
|
2917
3891
|
if (this.agents.size > 0) return;
|
|
2918
3892
|
if (!this.initializingAgentsPromise) {
|
|
2919
3893
|
this.initializingAgentsPromise = (async () => {
|
|
2920
|
-
const agentKeys = await fetchAgentKeys(
|
|
3894
|
+
const agentKeys = await fetchAgentKeys(
|
|
3895
|
+
this.apiKey,
|
|
3896
|
+
this.routingApiBaseUrl
|
|
3897
|
+
);
|
|
2921
3898
|
this.disabledAgents.clear();
|
|
2922
|
-
for (const {
|
|
3899
|
+
for (const {
|
|
3900
|
+
key: key2,
|
|
3901
|
+
agent_type,
|
|
3902
|
+
is_enabled,
|
|
3903
|
+
is_configured
|
|
3904
|
+
} of agentKeys) {
|
|
3905
|
+
const configured = is_configured ?? Boolean(key2);
|
|
3906
|
+
if (!configured) continue;
|
|
2923
3907
|
const agent = this.createAgent(agent_type, key2);
|
|
2924
3908
|
if (!agent) continue;
|
|
2925
3909
|
this.agents.set(agent_type, agent);
|
|
@@ -2943,10 +3927,47 @@ var OwneySDK = class {
|
|
|
2943
3927
|
}
|
|
2944
3928
|
createAgent(agentId, key2) {
|
|
2945
3929
|
if (agentId === "zyfai") {
|
|
3930
|
+
if (!key2) return null;
|
|
2946
3931
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
2947
3932
|
}
|
|
3933
|
+
if (agentId === "yieldseeker") {
|
|
3934
|
+
return new YieldseekerAgent(this.apiKey, {
|
|
3935
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
2948
3938
|
return null;
|
|
2949
3939
|
}
|
|
3940
|
+
/**
|
|
3941
|
+
* Discover the agents actually returned by routing for this organization.
|
|
3942
|
+
* Consumers should use this instead of hardcoding a global agent roster.
|
|
3943
|
+
*/
|
|
3944
|
+
async getAvailableAgents(options = {}) {
|
|
3945
|
+
await this.ensureAgentsInitialized();
|
|
3946
|
+
const { chainId, asset, includeDisabled = false } = options;
|
|
3947
|
+
const available = [];
|
|
3948
|
+
for (const [id, agent] of this.agents) {
|
|
3949
|
+
const isEnabled = !this.isAgentDisabled(id);
|
|
3950
|
+
if (!includeDisabled && !isEnabled) continue;
|
|
3951
|
+
if (chainId !== void 0 && !agent.supportedChainIds.includes(chainId)) {
|
|
3952
|
+
continue;
|
|
3953
|
+
}
|
|
3954
|
+
if (asset !== void 0) {
|
|
3955
|
+
const supportsAsset = agent.supportedAssets.some(
|
|
3956
|
+
(entry) => (chainId === void 0 || entry.chainId === chainId) && entry.assets.some((candidate) => candidate.symbol === asset)
|
|
3957
|
+
);
|
|
3958
|
+
if (!supportsAsset) {
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
available.push({
|
|
3963
|
+
id,
|
|
3964
|
+
isEnabled,
|
|
3965
|
+
supportedChainIds: agent.supportedChainIds,
|
|
3966
|
+
supportedAssets: agent.supportedAssets
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3969
|
+
return available;
|
|
3970
|
+
}
|
|
2950
3971
|
// --- Account lifecycle ---
|
|
2951
3972
|
/**
|
|
2952
3973
|
* Activate the user's smart wallet for the specified agents, or all chain-compatible agents if omitted.
|
|
@@ -2958,7 +3979,7 @@ var OwneySDK = class {
|
|
|
2958
3979
|
* If provided, ALL specified agents must support the chainId or the call
|
|
2959
3980
|
* throws before activating any agent.
|
|
2960
3981
|
*/
|
|
2961
|
-
async activateAgent(chainId, agentId) {
|
|
3982
|
+
async activateAgent(chainId, agentId, asset) {
|
|
2962
3983
|
const state = this.requireState();
|
|
2963
3984
|
await this.ensureAgentsInitialized();
|
|
2964
3985
|
if (agentId !== void 0) {
|
|
@@ -2994,9 +4015,7 @@ var OwneySDK = class {
|
|
|
2994
4015
|
this.activeAgents.add(id);
|
|
2995
4016
|
}
|
|
2996
4017
|
state.chainId = chainId;
|
|
2997
|
-
this.activateAgentsInTurn(agents, state, chainId)
|
|
2998
|
-
console.error("activateAgent background init failed:", error);
|
|
2999
|
-
});
|
|
4018
|
+
await this.activateAgentsInTurn(agents, state, chainId, asset);
|
|
3000
4019
|
return;
|
|
3001
4020
|
}
|
|
3002
4021
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -3017,11 +4036,7 @@ var OwneySDK = class {
|
|
|
3017
4036
|
const enabledCompatible = compatible.filter(
|
|
3018
4037
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
3019
4038
|
);
|
|
3020
|
-
this.activateAgentsInTurn(enabledCompatible, state, chainId)
|
|
3021
|
-
(error) => {
|
|
3022
|
-
console.error("activateAgent background init failed:", error);
|
|
3023
|
-
}
|
|
3024
|
-
);
|
|
4039
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
|
|
3025
4040
|
}
|
|
3026
4041
|
/**
|
|
3027
4042
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -3041,11 +4056,11 @@ var OwneySDK = class {
|
|
|
3041
4056
|
* rethrown (matching the previous `Promise.all` rejection) once all agents
|
|
3042
4057
|
* have had a chance to activate.
|
|
3043
4058
|
*/
|
|
3044
|
-
async activateAgentsInTurn(agents, state, chainId) {
|
|
4059
|
+
async activateAgentsInTurn(agents, state, chainId, asset) {
|
|
3045
4060
|
let firstError = null;
|
|
3046
4061
|
for (const agent of agents) {
|
|
3047
4062
|
try {
|
|
3048
|
-
await agent.activateAgent(state, chainId);
|
|
4063
|
+
await agent.activateAgent(state, chainId, asset);
|
|
3049
4064
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
3050
4065
|
} catch (error) {
|
|
3051
4066
|
if (firstError === null) {
|
|
@@ -3173,7 +4188,8 @@ var OwneySDK = class {
|
|
|
3173
4188
|
amount,
|
|
3174
4189
|
asset,
|
|
3175
4190
|
callback
|
|
3176
|
-
)
|
|
4191
|
+
),
|
|
4192
|
+
this.routingApiBaseUrl
|
|
3177
4193
|
);
|
|
3178
4194
|
let approvalAttempted = false;
|
|
3179
4195
|
for (; ; ) {
|
|
@@ -3253,10 +4269,10 @@ var OwneySDK = class {
|
|
|
3253
4269
|
try {
|
|
3254
4270
|
const balance = await agent.getBalances(state, chainId);
|
|
3255
4271
|
const target = asset.toLowerCase();
|
|
3256
|
-
const
|
|
4272
|
+
const token2 = balance.tokens.find(
|
|
3257
4273
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
3258
4274
|
);
|
|
3259
|
-
return !!
|
|
4275
|
+
return !!token2 && Number(token2.amount) > 0;
|
|
3260
4276
|
} catch {
|
|
3261
4277
|
return false;
|
|
3262
4278
|
}
|
|
@@ -3307,9 +4323,9 @@ var OwneySDK = class {
|
|
|
3307
4323
|
const { asset, amount, agentId } = options;
|
|
3308
4324
|
const state = this.requireState();
|
|
3309
4325
|
const chainId = this.requireChainId();
|
|
3310
|
-
const
|
|
4326
|
+
const token2 = asset;
|
|
3311
4327
|
const assetInfo = SupportedAssets.find(
|
|
3312
|
-
(a) => a.chainId === chainId && a.symbol ===
|
|
4328
|
+
(a) => a.chainId === chainId && a.symbol === token2
|
|
3313
4329
|
);
|
|
3314
4330
|
if (!assetInfo) {
|
|
3315
4331
|
throw new OwneyError(
|
|
@@ -3323,7 +4339,8 @@ var OwneySDK = class {
|
|
|
3323
4339
|
return withFailureReporting(
|
|
3324
4340
|
this.apiKey,
|
|
3325
4341
|
agent.id,
|
|
3326
|
-
() => agent.withdraw(state, chainId,
|
|
4342
|
+
() => agent.withdraw(state, chainId, token2, amount),
|
|
4343
|
+
this.routingApiBaseUrl
|
|
3327
4344
|
);
|
|
3328
4345
|
}
|
|
3329
4346
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
@@ -3332,12 +4349,17 @@ var OwneySDK = class {
|
|
|
3332
4349
|
const agentErrors2 = {};
|
|
3333
4350
|
for (const agent of eligibleAgents) {
|
|
3334
4351
|
try {
|
|
3335
|
-
results2[agent.id] = await agent.withdraw(state, chainId,
|
|
4352
|
+
results2[agent.id] = await agent.withdraw(state, chainId, token2);
|
|
3336
4353
|
} catch (err) {
|
|
3337
4354
|
console.error(`withdraw failed for agent "${agent.id}":`, err);
|
|
3338
4355
|
agentErrors2[agent.id] = err instanceof Error ? err.message : String(err);
|
|
3339
4356
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
3340
|
-
void reportAgentFailure(
|
|
4357
|
+
void reportAgentFailure(
|
|
4358
|
+
this.apiKey,
|
|
4359
|
+
agent.id,
|
|
4360
|
+
code,
|
|
4361
|
+
this.routingApiBaseUrl
|
|
4362
|
+
);
|
|
3341
4363
|
}
|
|
3342
4364
|
}
|
|
3343
4365
|
if (Object.keys(results2).length === 0) {
|
|
@@ -3354,6 +4376,10 @@ var OwneySDK = class {
|
|
|
3354
4376
|
}
|
|
3355
4377
|
const requested = BigInt(amount);
|
|
3356
4378
|
const aggregated = await this.getBalances();
|
|
4379
|
+
const unavailableAgents = eligibleAgents.filter(
|
|
4380
|
+
(agent) => !(agent.id in aggregated.agentBalances)
|
|
4381
|
+
);
|
|
4382
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
3357
4383
|
const balances = projectAgentBalancesForAsset(
|
|
3358
4384
|
eligibleAgents,
|
|
3359
4385
|
aggregated.agentBalances,
|
|
@@ -3362,7 +4388,18 @@ var OwneySDK = class {
|
|
|
3362
4388
|
assetInfo.decimals
|
|
3363
4389
|
);
|
|
3364
4390
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
3365
|
-
if (totalAvailable
|
|
4391
|
+
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
4392
|
+
throw new OwneyError(
|
|
4393
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
4394
|
+
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
4395
|
+
{
|
|
4396
|
+
asset,
|
|
4397
|
+
unavailableAgents: unavailableAgentIds,
|
|
4398
|
+
agentErrors: aggregated.agentErrors
|
|
4399
|
+
}
|
|
4400
|
+
);
|
|
4401
|
+
}
|
|
4402
|
+
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
3366
4403
|
throw new OwneyError(
|
|
3367
4404
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3368
4405
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -3373,6 +4410,7 @@ var OwneySDK = class {
|
|
|
3373
4410
|
}
|
|
3374
4411
|
);
|
|
3375
4412
|
}
|
|
4413
|
+
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
3376
4414
|
const disabledBalances = balances.filter(
|
|
3377
4415
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
3378
4416
|
);
|
|
@@ -3381,7 +4419,7 @@ var OwneySDK = class {
|
|
|
3381
4419
|
);
|
|
3382
4420
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
3383
4421
|
disabledBalances,
|
|
3384
|
-
|
|
4422
|
+
plannedTarget
|
|
3385
4423
|
);
|
|
3386
4424
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
3387
4425
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -3391,7 +4429,9 @@ var OwneySDK = class {
|
|
|
3391
4429
|
}));
|
|
3392
4430
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
3393
4431
|
const results = {};
|
|
3394
|
-
const agentErrors = {
|
|
4432
|
+
const agentErrors = {
|
|
4433
|
+
...aggregated.agentErrors ?? {}
|
|
4434
|
+
};
|
|
3395
4435
|
for (let i = 0; i < plans.length; i++) {
|
|
3396
4436
|
const p = plans[i];
|
|
3397
4437
|
if (p.planned === 0n) continue;
|
|
@@ -3399,14 +4439,19 @@ var OwneySDK = class {
|
|
|
3399
4439
|
results[p.agent.id] = await p.agent.withdraw(
|
|
3400
4440
|
state,
|
|
3401
4441
|
chainId,
|
|
3402
|
-
|
|
4442
|
+
token2,
|
|
3403
4443
|
p.planned.toString()
|
|
3404
4444
|
);
|
|
3405
4445
|
} catch (err) {
|
|
3406
4446
|
console.error(`withdraw failed for agent "${p.agent.id}":`, err);
|
|
3407
4447
|
agentErrors[p.agent.id] = err instanceof Error ? err.message : String(err);
|
|
3408
4448
|
const code = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
3409
|
-
void reportAgentFailure(
|
|
4449
|
+
void reportAgentFailure(
|
|
4450
|
+
this.apiKey,
|
|
4451
|
+
p.agent.id,
|
|
4452
|
+
code,
|
|
4453
|
+
this.routingApiBaseUrl
|
|
4454
|
+
);
|
|
3410
4455
|
const failedAmount = p.planned;
|
|
3411
4456
|
p.planned = 0n;
|
|
3412
4457
|
redistributeShare(plans, i, failedAmount);
|
|
@@ -3433,7 +4478,8 @@ var OwneySDK = class {
|
|
|
3433
4478
|
requested: amount,
|
|
3434
4479
|
withdrawn: withdrawn.toString(),
|
|
3435
4480
|
partialResults: results,
|
|
3436
|
-
agentErrors
|
|
4481
|
+
agentErrors,
|
|
4482
|
+
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
3437
4483
|
}
|
|
3438
4484
|
);
|
|
3439
4485
|
}
|
|
@@ -3474,7 +4520,12 @@ var OwneySDK = class {
|
|
|
3474
4520
|
continue;
|
|
3475
4521
|
}
|
|
3476
4522
|
const reason = settledResult.reason;
|
|
3477
|
-
|
|
4523
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
4524
|
+
agentErrors[agentId2] = message;
|
|
4525
|
+
console.error(
|
|
4526
|
+
`[owney-sdk] Balance fetch failed for agent "${agentId2}":`,
|
|
4527
|
+
reason
|
|
4528
|
+
);
|
|
3478
4529
|
}
|
|
3479
4530
|
if (successCount === 0) {
|
|
3480
4531
|
throw new OwneyError(
|
|
@@ -3486,7 +4537,8 @@ var OwneySDK = class {
|
|
|
3486
4537
|
return {
|
|
3487
4538
|
totalBalance: String(totalBalance),
|
|
3488
4539
|
totalBalanceAsset: "usdc",
|
|
3489
|
-
agentBalances: results
|
|
4540
|
+
agentBalances: results,
|
|
4541
|
+
...Object.keys(agentErrors).length > 0 ? { agentErrors } : {}
|
|
3490
4542
|
};
|
|
3491
4543
|
}
|
|
3492
4544
|
/**
|
|
@@ -3580,7 +4632,10 @@ var OwneySDK = class {
|
|
|
3580
4632
|
Promise.all(
|
|
3581
4633
|
entries.map(async ([id, agent]) => {
|
|
3582
4634
|
const b = await agent.getBalances(state, chainId);
|
|
3583
|
-
return [
|
|
4635
|
+
return [
|
|
4636
|
+
id,
|
|
4637
|
+
balanceForApyScope(b, chainId, tokenSymbol)
|
|
4638
|
+
];
|
|
3584
4639
|
})
|
|
3585
4640
|
)
|
|
3586
4641
|
]);
|
|
@@ -3608,10 +4663,12 @@ var OwneySDK = class {
|
|
|
3608
4663
|
}
|
|
3609
4664
|
}
|
|
3610
4665
|
const apyByChainAndAsset = aggregateApyByChainAndAsset(results, balances);
|
|
4666
|
+
const history = aggregateApyHistory(results, balances);
|
|
3611
4667
|
return {
|
|
3612
4668
|
totalApy: String(totalApy),
|
|
3613
4669
|
agentApy: results,
|
|
3614
|
-
apyByChainAndAsset
|
|
4670
|
+
apyByChainAndAsset,
|
|
4671
|
+
history
|
|
3615
4672
|
};
|
|
3616
4673
|
}
|
|
3617
4674
|
/**
|
|
@@ -3733,30 +4790,30 @@ var OwneySDK = class {
|
|
|
3733
4790
|
const state = this.requireState();
|
|
3734
4791
|
const chainId = this.requireChainId();
|
|
3735
4792
|
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
3736
|
-
const
|
|
3737
|
-
if (!
|
|
4793
|
+
const token2 = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
4794
|
+
if (!token2) {
|
|
3738
4795
|
throw new OwneyError(
|
|
3739
4796
|
"CHAIN_UNSUPPORTED",
|
|
3740
4797
|
`No sponsored WETH on chain ${chainId}`
|
|
3741
4798
|
);
|
|
3742
4799
|
}
|
|
3743
4800
|
const provider = this.requireConnectedProvider();
|
|
3744
|
-
const wallet = (0,
|
|
4801
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
3745
4802
|
account: state.walletAddress,
|
|
3746
4803
|
chain: VIEM_CHAIN2[chainId],
|
|
3747
|
-
transport: (0,
|
|
4804
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3748
4805
|
});
|
|
3749
4806
|
const hash = await wallet.writeContract({
|
|
3750
|
-
address:
|
|
4807
|
+
address: token2,
|
|
3751
4808
|
abi: ERC20_ALLOWANCE_ABI,
|
|
3752
4809
|
functionName: "approve",
|
|
3753
4810
|
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
3754
4811
|
account: state.walletAddress,
|
|
3755
4812
|
chain: VIEM_CHAIN2[chainId]
|
|
3756
4813
|
});
|
|
3757
|
-
const publicClient = (0,
|
|
4814
|
+
const publicClient = (0, import_viem9.createPublicClient)({
|
|
3758
4815
|
chain: VIEM_CHAIN2[chainId],
|
|
3759
|
-
transport: (0,
|
|
4816
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3760
4817
|
});
|
|
3761
4818
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3762
4819
|
hash,
|
|
@@ -3792,7 +4849,9 @@ var OwneySDK = class {
|
|
|
3792
4849
|
return this.getAgent(agentId).getAgentApy(days, agentOptions);
|
|
3793
4850
|
}
|
|
3794
4851
|
const results = {};
|
|
3795
|
-
const agentEntries = [...this.agents.entries()]
|
|
4852
|
+
const agentEntries = [...this.agents.entries()].filter(
|
|
4853
|
+
([id]) => !this.isAgentDisabled(id)
|
|
4854
|
+
);
|
|
3796
4855
|
const apyResults = await Promise.all(
|
|
3797
4856
|
agentEntries.map(async ([id, agent]) => {
|
|
3798
4857
|
const apy = await agent.getAgentApy(days, agentOptions);
|
|
@@ -3865,13 +4924,13 @@ var OwneySDK = class {
|
|
|
3865
4924
|
};
|
|
3866
4925
|
|
|
3867
4926
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
3868
|
-
var
|
|
3869
|
-
var
|
|
4927
|
+
var import_viem10 = require("viem");
|
|
4928
|
+
var import_siwe2 = require("siwe");
|
|
3870
4929
|
var import_sdk2 = require("@zyfai/sdk");
|
|
3871
4930
|
|
|
3872
4931
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
3873
|
-
var
|
|
3874
|
-
var
|
|
4932
|
+
var KEY_PREFIX3 = "owney.siwx.session";
|
|
4933
|
+
var storage3 = () => {
|
|
3875
4934
|
if (typeof window === "undefined") return null;
|
|
3876
4935
|
try {
|
|
3877
4936
|
return window.localStorage;
|
|
@@ -3879,8 +4938,8 @@ var storage2 = () => {
|
|
|
3879
4938
|
return null;
|
|
3880
4939
|
}
|
|
3881
4940
|
};
|
|
3882
|
-
var
|
|
3883
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
4941
|
+
var buildKey3 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
|
|
4942
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
|
|
3884
4943
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
3885
4944
|
var readLegacySiwxSession = (store, address) => {
|
|
3886
4945
|
if (!store) return null;
|
|
@@ -3911,8 +4970,8 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
3911
4970
|
};
|
|
3912
4971
|
var readSiwxSession = (address, chainId) => {
|
|
3913
4972
|
if (typeof window === "undefined") return null;
|
|
3914
|
-
const key2 =
|
|
3915
|
-
const store =
|
|
4973
|
+
const key2 = buildKey3(address);
|
|
4974
|
+
const store = storage3();
|
|
3916
4975
|
let raw = null;
|
|
3917
4976
|
try {
|
|
3918
4977
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -3940,18 +4999,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
3940
4999
|
};
|
|
3941
5000
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
3942
5001
|
if (typeof window === "undefined") return;
|
|
3943
|
-
const key2 =
|
|
5002
|
+
const key2 = buildKey3(address);
|
|
3944
5003
|
memorySiwxSessions.set(key2, session);
|
|
3945
|
-
const store =
|
|
5004
|
+
const store = storage3();
|
|
3946
5005
|
try {
|
|
3947
5006
|
store?.setItem(key2, JSON.stringify(session));
|
|
3948
5007
|
} catch {
|
|
3949
5008
|
}
|
|
3950
5009
|
};
|
|
3951
5010
|
var clearSiwxSession = (address, _chainId) => {
|
|
3952
|
-
const key2 =
|
|
5011
|
+
const key2 = buildKey3(address);
|
|
3953
5012
|
memorySiwxSessions.delete(key2);
|
|
3954
|
-
const store =
|
|
5013
|
+
const store = storage3();
|
|
3955
5014
|
try {
|
|
3956
5015
|
store?.removeItem(key2);
|
|
3957
5016
|
} catch {
|
|
@@ -3991,8 +5050,8 @@ function buildSIWXConfig(deps) {
|
|
|
3991
5050
|
statement: STATEMENT,
|
|
3992
5051
|
issuedAt,
|
|
3993
5052
|
toString() {
|
|
3994
|
-
return new
|
|
3995
|
-
address: (0,
|
|
5053
|
+
return new import_siwe2.SiweMessage({
|
|
5054
|
+
address: (0, import_viem10.getAddress)(accountAddress),
|
|
3996
5055
|
chainId: numericChainId(chainId),
|
|
3997
5056
|
domain,
|
|
3998
5057
|
uri,
|
|
@@ -4034,7 +5093,7 @@ function buildSIWXConfig(deps) {
|
|
|
4034
5093
|
const persistSession = async (session) => {
|
|
4035
5094
|
const address = session.data.accountAddress;
|
|
4036
5095
|
const id = numericChainId(session.data.chainId);
|
|
4037
|
-
const message = new
|
|
5096
|
+
const message = new import_siwe2.SiweMessage(session.message);
|
|
4038
5097
|
const login = await post("/auth/login", {
|
|
4039
5098
|
message,
|
|
4040
5099
|
signature: session.signature,
|
|
@@ -4083,6 +5142,7 @@ function createOwneySIWX(config) {
|
|
|
4083
5142
|
NotConnectedError,
|
|
4084
5143
|
OwneyError,
|
|
4085
5144
|
OwneySDK,
|
|
5145
|
+
YieldseekerAgent,
|
|
4086
5146
|
createOwneySIWX,
|
|
4087
5147
|
setOwneyDebug
|
|
4088
5148
|
});
|