@owney/sdk 0.7.24 → 0.7.25-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 +9 -7
- package/dist/index.cjs +2701 -775
- package/dist/index.d.cts +144 -33
- package/dist/index.d.ts +144 -33
- package/dist/index.js +2714 -772
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
// src/lib/deposit-batch-callback.ts
|
|
2
|
+
var batchCallbacks = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
|
|
4
|
+
function toBatchTransfer(to, amount, verification) {
|
|
5
|
+
return {
|
|
6
|
+
to,
|
|
7
|
+
amount,
|
|
8
|
+
...verification ? {
|
|
9
|
+
yieldseeker: {
|
|
10
|
+
signature: verification.signature,
|
|
11
|
+
userId: verification.userId,
|
|
12
|
+
agentId: verification.yieldseekerAgentId
|
|
13
|
+
}
|
|
14
|
+
} : {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function registerDepositBatch(callback, transfer) {
|
|
18
|
+
batchCallbacks.set(callback, transfer);
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
// src/errors.ts
|
|
2
22
|
var OwneyError = class extends Error {
|
|
3
23
|
code;
|
|
@@ -277,18 +297,18 @@ function tokenDecimals(symbol, explicit) {
|
|
|
277
297
|
return explicit;
|
|
278
298
|
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
279
299
|
}
|
|
280
|
-
function mapDeposit(
|
|
300
|
+
function mapDeposit(raw2) {
|
|
281
301
|
return {
|
|
282
|
-
txHash:
|
|
283
|
-
smartWallet:
|
|
284
|
-
amount:
|
|
302
|
+
txHash: raw2.txHash,
|
|
303
|
+
smartWallet: raw2.smartWallet,
|
|
304
|
+
amount: raw2.amount
|
|
285
305
|
};
|
|
286
306
|
}
|
|
287
|
-
function mapWithdraw(
|
|
307
|
+
function mapWithdraw(raw2) {
|
|
288
308
|
return {
|
|
289
|
-
txHash:
|
|
290
|
-
type:
|
|
291
|
-
amount:
|
|
309
|
+
txHash: raw2.txHash,
|
|
310
|
+
type: raw2.type,
|
|
311
|
+
amount: raw2.amount
|
|
292
312
|
};
|
|
293
313
|
}
|
|
294
314
|
var CHAIN_ID_TO_NAME = {
|
|
@@ -307,10 +327,10 @@ function resolveChainId(chain) {
|
|
|
307
327
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
308
328
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
309
329
|
}
|
|
310
|
-
function mapPendingAllocations(
|
|
311
|
-
if (!Array.isArray(
|
|
330
|
+
function mapPendingAllocations(raw2) {
|
|
331
|
+
if (!Array.isArray(raw2)) return void 0;
|
|
312
332
|
const pending = [];
|
|
313
|
-
for (const entry of
|
|
333
|
+
for (const entry of raw2) {
|
|
314
334
|
if (typeof entry !== "object" || entry === null) continue;
|
|
315
335
|
const e = entry;
|
|
316
336
|
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
@@ -333,8 +353,8 @@ function mapPendingAllocations(raw) {
|
|
|
333
353
|
}
|
|
334
354
|
return pending.length > 0 ? pending : void 0;
|
|
335
355
|
}
|
|
336
|
-
function mapBalances(
|
|
337
|
-
const portfolio =
|
|
356
|
+
function mapBalances(raw2, _chainId, smartWallet) {
|
|
357
|
+
const portfolio = raw2.portfolio;
|
|
338
358
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
339
359
|
let totalBalance = 0;
|
|
340
360
|
const tokens = [];
|
|
@@ -403,8 +423,8 @@ function sumTokenValues(tokens) {
|
|
|
403
423
|
function sumTokenEarnings(tokens) {
|
|
404
424
|
return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
|
|
405
425
|
}
|
|
406
|
-
function mapEarnings(
|
|
407
|
-
const totalEarningsByChain =
|
|
426
|
+
function mapEarnings(raw2, smartWallet) {
|
|
427
|
+
const totalEarningsByChain = raw2.data.totalEarningsByChainWithFee ?? raw2.data.totalEarningsByChain ?? {};
|
|
408
428
|
const tokens = [];
|
|
409
429
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(
|
|
410
430
|
totalEarningsByChain
|
|
@@ -423,15 +443,15 @@ function mapEarnings(raw, smartWallet) {
|
|
|
423
443
|
return {
|
|
424
444
|
smartWallet,
|
|
425
445
|
lifetimeEarnings: sumTokenEarnings(
|
|
426
|
-
|
|
446
|
+
raw2.data.totalEarningsByTokenWithFee ?? raw2.data.totalEarningsByToken
|
|
427
447
|
),
|
|
428
448
|
tokens
|
|
429
449
|
};
|
|
430
450
|
}
|
|
431
|
-
function mapWeightedApyByChain(
|
|
432
|
-
if (!
|
|
451
|
+
function mapWeightedApyByChain(raw2) {
|
|
452
|
+
if (!raw2) return void 0;
|
|
433
453
|
const out = {};
|
|
434
|
-
for (const [chainKey, tokenApy] of Object.entries(
|
|
454
|
+
for (const [chainKey, tokenApy] of Object.entries(raw2)) {
|
|
435
455
|
const chainId = Number(chainKey);
|
|
436
456
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
437
457
|
const perAsset = {};
|
|
@@ -471,8 +491,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
|
471
491
|
}
|
|
472
492
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
473
493
|
}
|
|
474
|
-
function mapApyHistory(
|
|
475
|
-
const history = Object.entries(
|
|
494
|
+
function mapApyHistory(raw2, chainId, tokenSymbol) {
|
|
495
|
+
const history = Object.entries(raw2.history ?? {}).map(([date, entry]) => ({
|
|
476
496
|
date,
|
|
477
497
|
apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
|
|
478
498
|
// Provider position balances are treated as decimal amounts of the
|
|
@@ -488,9 +508,9 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
|
|
|
488
508
|
} : {}
|
|
489
509
|
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
490
510
|
return {
|
|
491
|
-
walletAddress:
|
|
492
|
-
weightedApyAfterFee:
|
|
493
|
-
apyByChainAndAsset: mapWeightedApyByChain(
|
|
511
|
+
walletAddress: raw2.walletAddress,
|
|
512
|
+
weightedApyAfterFee: raw2.weightedApyAfterFee ? sumTokenValues(raw2.weightedApyAfterFee) : void 0,
|
|
513
|
+
apyByChainAndAsset: mapWeightedApyByChain(raw2.weightedApyAfterFeeByChain),
|
|
494
514
|
history
|
|
495
515
|
};
|
|
496
516
|
}
|
|
@@ -586,23 +606,23 @@ function mapEntries(rawEntries, chainId) {
|
|
|
586
606
|
};
|
|
587
607
|
});
|
|
588
608
|
}
|
|
589
|
-
function mapUserProfile(
|
|
609
|
+
function mapUserProfile(raw2, userAddress) {
|
|
590
610
|
return {
|
|
591
611
|
address: userAddress,
|
|
592
|
-
smartWallet:
|
|
593
|
-
chains:
|
|
594
|
-
strategy:
|
|
595
|
-
hasActiveSessionKey:
|
|
596
|
-
protocols:
|
|
597
|
-
splitting:
|
|
598
|
-
minSplits:
|
|
612
|
+
smartWallet: raw2.smartWallet || "",
|
|
613
|
+
chains: raw2.chains || [],
|
|
614
|
+
strategy: raw2.strategy,
|
|
615
|
+
hasActiveSessionKey: raw2.hasActiveSessionKey || false,
|
|
616
|
+
protocols: raw2.protocols || [],
|
|
617
|
+
splitting: raw2.splitting,
|
|
618
|
+
minSplits: raw2.minSplits
|
|
599
619
|
};
|
|
600
620
|
}
|
|
601
|
-
function mapApyByStrategy(
|
|
621
|
+
function mapApyByStrategy(raw2) {
|
|
602
622
|
const apyPerAsset = {};
|
|
603
623
|
let apySum = 0;
|
|
604
624
|
let apyCount = 0;
|
|
605
|
-
for (const entry of
|
|
625
|
+
for (const entry of raw2.data) {
|
|
606
626
|
const supported = SupportedAssets.find(
|
|
607
627
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
608
628
|
);
|
|
@@ -714,9 +734,9 @@ function netDeltaForSnapshot(entry, chainId, asset) {
|
|
|
714
734
|
debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
|
|
715
735
|
return gross;
|
|
716
736
|
}
|
|
717
|
-
function mapDailyEarnings(
|
|
737
|
+
function mapDailyEarnings(raw2, chainId, tokenSymbol) {
|
|
718
738
|
const wanted = tokenSymbol?.toUpperCase();
|
|
719
|
-
const snapshots = [...
|
|
739
|
+
const snapshots = [...raw2.data ?? []].sort(
|
|
720
740
|
(a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
|
|
721
741
|
);
|
|
722
742
|
const byAsset = /* @__PURE__ */ new Map();
|
|
@@ -731,7 +751,7 @@ function mapDailyEarnings(raw, chainId, tokenSymbol) {
|
|
|
731
751
|
}
|
|
732
752
|
}
|
|
733
753
|
const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
|
|
734
|
-
return { walletAddress:
|
|
754
|
+
return { walletAddress: raw2.walletAddress, chainId, assets };
|
|
735
755
|
}
|
|
736
756
|
|
|
737
757
|
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
@@ -842,15 +862,15 @@ var readSession = (address, _chainId) => {
|
|
|
842
862
|
if (typeof window === "undefined") return null;
|
|
843
863
|
const key2 = buildKey(address);
|
|
844
864
|
const store = storage();
|
|
845
|
-
let
|
|
865
|
+
let raw2 = null;
|
|
846
866
|
try {
|
|
847
|
-
|
|
867
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
848
868
|
} catch {
|
|
849
|
-
|
|
869
|
+
raw2 = null;
|
|
850
870
|
}
|
|
851
|
-
if (
|
|
871
|
+
if (raw2) {
|
|
852
872
|
try {
|
|
853
|
-
const parsed = JSON.parse(
|
|
873
|
+
const parsed = JSON.parse(raw2);
|
|
854
874
|
if (isFreshSession(parsed)) return parsed;
|
|
855
875
|
} catch {
|
|
856
876
|
}
|
|
@@ -1018,8 +1038,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
|
|
|
1018
1038
|
}
|
|
1019
1039
|
return result;
|
|
1020
1040
|
}
|
|
1021
|
-
function flattenAvailablePools(
|
|
1022
|
-
const byChain =
|
|
1041
|
+
function flattenAvailablePools(raw2) {
|
|
1042
|
+
const byChain = raw2 ?? {};
|
|
1023
1043
|
const names = [];
|
|
1024
1044
|
for (const byToken of Object.values(byChain ?? {})) {
|
|
1025
1045
|
for (const entry of Object.values(byToken ?? {})) {
|
|
@@ -1522,8 +1542,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1522
1542
|
const poolResults = await Promise.all(
|
|
1523
1543
|
universe.map(async (protocol) => {
|
|
1524
1544
|
try {
|
|
1525
|
-
const
|
|
1526
|
-
return [protocol.id, flattenAvailablePools(
|
|
1545
|
+
const raw2 = await this.sdk.getAvailablePools(protocol.id, strategy);
|
|
1546
|
+
return [protocol.id, flattenAvailablePools(raw2)];
|
|
1527
1547
|
} catch (error) {
|
|
1528
1548
|
console.warn(
|
|
1529
1549
|
`[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
|
|
@@ -1589,14 +1609,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1589
1609
|
async readWalletState(ownerAddress) {
|
|
1590
1610
|
try {
|
|
1591
1611
|
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1592
|
-
const
|
|
1612
|
+
const raw2 = portfolio;
|
|
1593
1613
|
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1594
|
-
predeployed:
|
|
1595
|
-
hasActiveSessionKey:
|
|
1614
|
+
predeployed: raw2?.predeployed,
|
|
1615
|
+
hasActiveSessionKey: raw2?.hasActiveSessionKey
|
|
1596
1616
|
});
|
|
1597
1617
|
return {
|
|
1598
|
-
predeployed:
|
|
1599
|
-
hasActiveSessionKey:
|
|
1618
|
+
predeployed: raw2?.predeployed,
|
|
1619
|
+
hasActiveSessionKey: raw2?.hasActiveSessionKey
|
|
1600
1620
|
};
|
|
1601
1621
|
} catch (error) {
|
|
1602
1622
|
console.warn(
|
|
@@ -1882,14 +1902,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1882
1902
|
return { txHash, smartWallet, amount };
|
|
1883
1903
|
}
|
|
1884
1904
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
1885
|
-
const
|
|
1905
|
+
const raw2 = await this.sdk.depositFunds(
|
|
1886
1906
|
this.getAddress(),
|
|
1887
1907
|
validChainId,
|
|
1888
1908
|
amount,
|
|
1889
1909
|
asset,
|
|
1890
1910
|
"aggressive"
|
|
1891
1911
|
);
|
|
1892
|
-
return mapDeposit(
|
|
1912
|
+
return mapDeposit(raw2);
|
|
1893
1913
|
} catch (error) {
|
|
1894
1914
|
throw error;
|
|
1895
1915
|
}
|
|
@@ -1898,27 +1918,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1898
1918
|
async withdraw(state, chainId, token, amount) {
|
|
1899
1919
|
const validChainId = isValidChainId(chainId);
|
|
1900
1920
|
await this.ensureConnected(state, validChainId);
|
|
1901
|
-
const
|
|
1921
|
+
const raw2 = await this.sdk.withdrawFunds(
|
|
1902
1922
|
this.getAddress(),
|
|
1903
1923
|
validChainId,
|
|
1904
1924
|
amount,
|
|
1905
1925
|
token
|
|
1906
1926
|
);
|
|
1907
|
-
if (!
|
|
1927
|
+
if (!raw2.success) {
|
|
1908
1928
|
throw new OwneyError(
|
|
1909
1929
|
"WITHDRAW_FAILED",
|
|
1910
|
-
|
|
1911
|
-
{ chainId: validChainId, token, amount, response:
|
|
1930
|
+
raw2.message || "Zyfai withdraw failed.",
|
|
1931
|
+
{ chainId: validChainId, token, amount, response: raw2 },
|
|
1912
1932
|
this.id
|
|
1913
1933
|
);
|
|
1914
1934
|
}
|
|
1915
|
-
return mapWithdraw(
|
|
1935
|
+
return mapWithdraw(raw2);
|
|
1916
1936
|
}
|
|
1917
1937
|
// --- IAgent: Portfolio reads ---
|
|
1918
1938
|
async getBalances(state, chainId) {
|
|
1919
1939
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
1920
|
-
const
|
|
1921
|
-
return mapBalances(
|
|
1940
|
+
const raw2 = await this.sdk.getPortfolio(this.getAddress());
|
|
1941
|
+
return mapBalances(raw2, validChainId, smartWallet);
|
|
1922
1942
|
}
|
|
1923
1943
|
earningsKey(state, chainId, smartWallet) {
|
|
1924
1944
|
return JSON.stringify([
|
|
@@ -1931,11 +1951,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1931
1951
|
const existing = this.earningsReads.get(key2);
|
|
1932
1952
|
if (existing) return existing;
|
|
1933
1953
|
const generation = this.earningsGeneration;
|
|
1934
|
-
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((
|
|
1954
|
+
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw2) => {
|
|
1935
1955
|
if (generation === this.earningsGeneration) {
|
|
1936
|
-
this.earningsSnapshot = { key: key2, raw, at: Date.now() };
|
|
1956
|
+
this.earningsSnapshot = { key: key2, raw: raw2, at: Date.now() };
|
|
1937
1957
|
}
|
|
1938
|
-
return
|
|
1958
|
+
return raw2;
|
|
1939
1959
|
}).finally(() => {
|
|
1940
1960
|
if (this.earningsReads.get(key2) === pending)
|
|
1941
1961
|
this.earningsReads.delete(key2);
|
|
@@ -1945,11 +1965,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1945
1965
|
}
|
|
1946
1966
|
async getEarnings(state, chainId) {
|
|
1947
1967
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1948
|
-
const
|
|
1968
|
+
const raw2 = await this.readEarnings(
|
|
1949
1969
|
this.earningsKey(state, chainId, smartWallet),
|
|
1950
1970
|
smartWallet
|
|
1951
1971
|
);
|
|
1952
|
-
return mapEarnings(
|
|
1972
|
+
return mapEarnings(raw2, smartWallet);
|
|
1953
1973
|
}
|
|
1954
1974
|
async refreshEarnings(state, chainId) {
|
|
1955
1975
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -1976,17 +1996,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1976
1996
|
}
|
|
1977
1997
|
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
1978
1998
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1979
|
-
const
|
|
1980
|
-
return mapApyHistory(
|
|
1999
|
+
const raw2 = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
2000
|
+
return mapApyHistory(raw2, chainId, tokenSymbol);
|
|
1981
2001
|
}
|
|
1982
2002
|
async getDailyEarnings(state, chainId, days, tokenSymbol) {
|
|
1983
2003
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
1984
2004
|
const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
|
|
1985
|
-
const
|
|
2005
|
+
const raw2 = await this.sdk.getDailyEarnings(
|
|
1986
2006
|
smartWallet,
|
|
1987
2007
|
start.toISOString().slice(0, 10)
|
|
1988
2008
|
);
|
|
1989
|
-
return mapDailyEarnings(
|
|
2009
|
+
return mapDailyEarnings(raw2, chainId, tokenSymbol);
|
|
1990
2010
|
}
|
|
1991
2011
|
/**
|
|
1992
2012
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
@@ -2021,7 +2041,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2021
2041
|
const matched = [];
|
|
2022
2042
|
let backendExhausted = false;
|
|
2023
2043
|
for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
|
|
2024
|
-
const
|
|
2044
|
+
const raw2 = await this.sdk.getHistory(smartWallet, validChainId, {
|
|
2025
2045
|
limit: backendPageSize,
|
|
2026
2046
|
offset,
|
|
2027
2047
|
fromDate: options?.fromDate,
|
|
@@ -2032,13 +2052,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2032
2052
|
// asset's rows and handing back a page that filters to nothing.
|
|
2033
2053
|
assetType
|
|
2034
2054
|
});
|
|
2035
|
-
|
|
2055
|
+
raw2.data.forEach((entry, idx) => {
|
|
2036
2056
|
if (entry.chainId === validChainId) {
|
|
2037
2057
|
matched.push({ entry, rawIdx: offset + idx });
|
|
2038
2058
|
}
|
|
2039
2059
|
});
|
|
2040
|
-
offset +=
|
|
2041
|
-
if (
|
|
2060
|
+
offset += raw2.data.length;
|
|
2061
|
+
if (raw2.data.length < backendPageSize) {
|
|
2042
2062
|
backendExhausted = true;
|
|
2043
2063
|
break;
|
|
2044
2064
|
}
|
|
@@ -2060,18 +2080,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2060
2080
|
}
|
|
2061
2081
|
async getUserProfile(state, chainId) {
|
|
2062
2082
|
await this.connectAuth(state, chainId);
|
|
2063
|
-
const
|
|
2083
|
+
const raw2 = await this.sdk.getUserDetails();
|
|
2064
2084
|
debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
|
|
2065
2085
|
asset: "USDC (default \u2014 no asset passed)",
|
|
2066
|
-
splitting:
|
|
2067
|
-
minSplits:
|
|
2068
|
-
strategy:
|
|
2069
|
-
chains:
|
|
2070
|
-
protocolCount:
|
|
2071
|
-
hasActiveSessionKey:
|
|
2072
|
-
smartWallet:
|
|
2086
|
+
splitting: raw2.splitting,
|
|
2087
|
+
minSplits: raw2.minSplits,
|
|
2088
|
+
strategy: raw2.strategy,
|
|
2089
|
+
chains: raw2.chains,
|
|
2090
|
+
protocolCount: raw2.protocols?.length,
|
|
2091
|
+
hasActiveSessionKey: raw2.hasActiveSessionKey,
|
|
2092
|
+
smartWallet: raw2.smartWallet
|
|
2073
2093
|
});
|
|
2074
|
-
return mapUserProfile(
|
|
2094
|
+
return mapUserProfile(raw2, this.connectedAddress);
|
|
2075
2095
|
}
|
|
2076
2096
|
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
2077
2097
|
await this.connectAuth(state, chainId);
|
|
@@ -2090,201 +2110,2076 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2090
2110
|
}
|
|
2091
2111
|
// --- IAgent: Discovery (no wallet required) ---
|
|
2092
2112
|
async getAgentApy(days, options) {
|
|
2093
|
-
const
|
|
2113
|
+
const raw2 = await this.sdk.getAPYPerStrategy(
|
|
2094
2114
|
false,
|
|
2095
2115
|
DayFilterMapping[days],
|
|
2096
2116
|
"aggressive",
|
|
2097
2117
|
options?.chainId,
|
|
2098
2118
|
options?.tokenSymbol
|
|
2099
2119
|
);
|
|
2100
|
-
return mapApyByStrategy(
|
|
2120
|
+
return mapApyByStrategy(raw2);
|
|
2101
2121
|
}
|
|
2102
2122
|
};
|
|
2103
2123
|
|
|
2104
|
-
// src/
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2124
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
2125
|
+
import {
|
|
2126
|
+
createPublicClient as createPublicClient3,
|
|
2127
|
+
createWalletClient as createWalletClient2,
|
|
2128
|
+
custom as custom2,
|
|
2129
|
+
encodeFunctionData,
|
|
2130
|
+
erc20Abi,
|
|
2131
|
+
getAddress as getAddress2,
|
|
2132
|
+
isAddress as isAddress2
|
|
2133
|
+
} from "viem";
|
|
2134
|
+
import { base as base3 } from "viem/chains";
|
|
2135
|
+
|
|
2136
|
+
// src/lib/chain-guard.ts
|
|
2137
|
+
var CHAIN_NAMES = {
|
|
2138
|
+
1: "Ethereum",
|
|
2139
|
+
8453: "Base",
|
|
2140
|
+
42161: "Arbitrum"
|
|
2141
|
+
};
|
|
2142
|
+
function chainName(chainId) {
|
|
2143
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2144
|
+
}
|
|
2145
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2146
|
+
const actual = await pub.getChainId();
|
|
2147
|
+
if (actual === expected) return;
|
|
2108
2148
|
try {
|
|
2109
|
-
|
|
2110
|
-
method: "GET",
|
|
2111
|
-
headers: {
|
|
2112
|
-
"Content-Type": "application/json",
|
|
2113
|
-
"x-owney-api-key": `${apiKey}`
|
|
2114
|
-
}
|
|
2115
|
-
});
|
|
2116
|
-
if (!res.ok) {
|
|
2117
|
-
if (res.status !== 404) {
|
|
2118
|
-
console.warn(
|
|
2119
|
-
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
2120
|
-
);
|
|
2121
|
-
}
|
|
2122
|
-
return null;
|
|
2123
|
-
}
|
|
2124
|
-
const json = await res.json();
|
|
2125
|
-
const policy = json.success ? json.data ?? null : null;
|
|
2126
|
-
debugLog(
|
|
2127
|
-
"owney-sdk",
|
|
2128
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
2129
|
-
policy ?? void 0
|
|
2130
|
-
);
|
|
2131
|
-
return policy;
|
|
2149
|
+
await wallet.switchChain({ id: expected });
|
|
2132
2150
|
} catch (error) {
|
|
2133
|
-
console.warn(
|
|
2134
|
-
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
2135
|
-
error instanceof Error ? error.message : String(error)
|
|
2136
|
-
);
|
|
2137
|
-
return null;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2141
|
-
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
2142
|
-
const res = await fetch(url, {
|
|
2143
|
-
method: "GET",
|
|
2144
|
-
headers: {
|
|
2145
|
-
"Content-Type": "application/json",
|
|
2146
|
-
"x-owney-api-key": `${apiKey}`
|
|
2147
|
-
}
|
|
2148
|
-
});
|
|
2149
|
-
if (!res.ok) {
|
|
2150
|
-
const text = await res.text().catch(() => "");
|
|
2151
2151
|
throw new OwneyError(
|
|
2152
|
-
"
|
|
2153
|
-
`
|
|
2154
|
-
{
|
|
2152
|
+
"CHAIN_MISMATCH",
|
|
2153
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2154
|
+
{
|
|
2155
|
+
expectedChainId: expected,
|
|
2156
|
+
actualChainId: actual,
|
|
2157
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2158
|
+
}
|
|
2155
2159
|
);
|
|
2156
2160
|
}
|
|
2157
|
-
const
|
|
2158
|
-
if (
|
|
2161
|
+
const after = await pub.getChainId();
|
|
2162
|
+
if (after !== expected) {
|
|
2159
2163
|
throw new OwneyError(
|
|
2160
|
-
"
|
|
2161
|
-
`
|
|
2162
|
-
{
|
|
2164
|
+
"CHAIN_MISMATCH",
|
|
2165
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2166
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2163
2167
|
);
|
|
2164
2168
|
}
|
|
2165
|
-
return json.data;
|
|
2166
2169
|
}
|
|
2167
2170
|
|
|
2168
|
-
// src/lib/
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
+
// src/lib/transfer-auth.ts
|
|
2172
|
+
import { bytesToHex } from "viem";
|
|
2173
|
+
|
|
2174
|
+
// src/lib/sponsor-client.ts
|
|
2175
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2176
|
+
async function postPaymasterIntent(input) {
|
|
2177
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2178
|
+
let res;
|
|
2171
2179
|
try {
|
|
2172
|
-
await fetch(`${
|
|
2180
|
+
res = await fetch(`${base5}/api/v1/sponsor/paymaster-intent`, {
|
|
2173
2181
|
method: "POST",
|
|
2174
2182
|
headers: {
|
|
2175
|
-
"
|
|
2176
|
-
"x-owney-api-key": apiKey
|
|
2183
|
+
"content-type": "application/json",
|
|
2184
|
+
"x-owney-api-key": input.apiKey,
|
|
2185
|
+
Authorization: `Signature ${input.yieldseekerSignature}`
|
|
2177
2186
|
},
|
|
2178
|
-
body: JSON.stringify(
|
|
2179
|
-
agent_type: agentType,
|
|
2180
|
-
error_code: errorCode,
|
|
2181
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2182
|
-
})
|
|
2187
|
+
body: JSON.stringify(input.body)
|
|
2183
2188
|
});
|
|
2184
|
-
} catch (
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2189
|
+
} catch (networkError) {
|
|
2190
|
+
throw new OwneyError(
|
|
2191
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2192
|
+
`Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2193
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
2188
2194
|
);
|
|
2189
2195
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2196
|
+
const text = await res.text();
|
|
2197
|
+
let parsed = null;
|
|
2192
2198
|
try {
|
|
2193
|
-
|
|
2194
|
-
} catch
|
|
2195
|
-
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
2196
|
-
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
2197
|
-
throw err;
|
|
2199
|
+
parsed = JSON.parse(text);
|
|
2200
|
+
} catch {
|
|
2198
2201
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2202
|
+
if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
|
|
2203
|
+
throw new OwneyError(
|
|
2204
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2205
|
+
`Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2206
|
+
{
|
|
2207
|
+
statusCode: res.status,
|
|
2208
|
+
responseBody: text.slice(0, 500),
|
|
2209
|
+
safeToFallback: true
|
|
2210
|
+
}
|
|
2209
2211
|
);
|
|
2210
|
-
if (!tokenBalance) return { agent, balance: 0n };
|
|
2211
|
-
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
function planProportionalShares(balances, requested, totalAvailable) {
|
|
2215
|
-
const plans = balances.map(({ agent, balance }) => ({
|
|
2216
|
-
agent,
|
|
2217
|
-
balance,
|
|
2218
|
-
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
2219
|
-
}));
|
|
2220
|
-
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
2221
|
-
let remainder = requested - assigned;
|
|
2222
|
-
const byHeadroom = [...plans].sort((a, b) => {
|
|
2223
|
-
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
2224
|
-
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
2225
|
-
});
|
|
2226
|
-
for (const p of byHeadroom) {
|
|
2227
|
-
if (remainder === 0n) break;
|
|
2228
|
-
const headroom = p.balance - p.planned;
|
|
2229
|
-
if (headroom <= 0n) continue;
|
|
2230
|
-
const take = headroom < remainder ? headroom : remainder;
|
|
2231
|
-
p.planned += take;
|
|
2232
|
-
remainder -= take;
|
|
2233
2212
|
}
|
|
2234
|
-
return
|
|
2213
|
+
return parsed.data;
|
|
2235
2214
|
}
|
|
2236
|
-
function
|
|
2237
|
-
const
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2215
|
+
async function getSponsorRelayerAddress(input) {
|
|
2216
|
+
const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
|
|
2217
|
+
let res;
|
|
2218
|
+
try {
|
|
2219
|
+
res = await fetch(
|
|
2220
|
+
`${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2221
|
+
{
|
|
2222
|
+
headers: { "x-owney-api-key": input.apiKey }
|
|
2223
|
+
}
|
|
2224
|
+
);
|
|
2225
|
+
} catch (networkError) {
|
|
2226
|
+
throw new OwneyError(
|
|
2227
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2228
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2229
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
2230
|
+
);
|
|
2248
2231
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
const totalHeadroom = candidates.reduce(
|
|
2255
|
-
(s, c) => s + (c.balance - c.planned),
|
|
2256
|
-
0n
|
|
2257
|
-
);
|
|
2258
|
-
if (totalHeadroom === 0n) return;
|
|
2259
|
-
let distributed = 0n;
|
|
2260
|
-
for (const c of candidates) {
|
|
2261
|
-
const headroom = c.balance - c.planned;
|
|
2262
|
-
const proportional = headroom * amount / totalHeadroom;
|
|
2263
|
-
const give = proportional > headroom ? headroom : proportional;
|
|
2264
|
-
c.planned += give;
|
|
2265
|
-
distributed += give;
|
|
2232
|
+
const text = await res.text();
|
|
2233
|
+
let parsed = null;
|
|
2234
|
+
try {
|
|
2235
|
+
parsed = JSON.parse(text);
|
|
2236
|
+
} catch {
|
|
2266
2237
|
}
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2238
|
+
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
2239
|
+
throw new OwneyError(
|
|
2240
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2241
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2242
|
+
{
|
|
2243
|
+
statusCode: res.status,
|
|
2244
|
+
responseBody: text.slice(0, 500),
|
|
2245
|
+
safeToFallback: true
|
|
2246
|
+
}
|
|
2247
|
+
);
|
|
2275
2248
|
}
|
|
2249
|
+
return parsed.data.relayer;
|
|
2276
2250
|
}
|
|
2277
|
-
function
|
|
2278
|
-
|
|
2251
|
+
async function postSponsorBatchTransfer(input) {
|
|
2252
|
+
let res;
|
|
2253
|
+
try {
|
|
2254
|
+
res = await fetch(
|
|
2255
|
+
`${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
|
|
2256
|
+
{
|
|
2257
|
+
method: "POST",
|
|
2258
|
+
headers: {
|
|
2259
|
+
"content-type": "application/json",
|
|
2260
|
+
"x-owney-api-key": input.apiKey
|
|
2261
|
+
},
|
|
2262
|
+
body: JSON.stringify(input.body)
|
|
2263
|
+
}
|
|
2264
|
+
);
|
|
2265
|
+
} catch {
|
|
2266
|
+
throw new OwneyError(
|
|
2267
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2268
|
+
"Deposit status is unknown. Retry the same amount to check it.",
|
|
2269
|
+
{ safeToFallback: false }
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
const parsed = await res.json().catch(() => null);
|
|
2273
|
+
if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
|
|
2274
|
+
throw new OwneyError(
|
|
2275
|
+
"SPONSOR_REQUEST_FAILED",
|
|
2276
|
+
"Deposit could not be confirmed. Retry the same amount to check its status.",
|
|
2277
|
+
{
|
|
2278
|
+
statusCode: res.status,
|
|
2279
|
+
safeToFallback: false,
|
|
2280
|
+
notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
|
|
2281
|
+
}
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
return parsed.data;
|
|
2279
2285
|
}
|
|
2280
2286
|
|
|
2281
|
-
// src/lib/
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2287
|
+
// src/lib/permit2.ts
|
|
2288
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2289
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2290
|
+
function permit2ApprovalAmount(requiredAmount) {
|
|
2291
|
+
if (requiredAmount <= 0n) {
|
|
2292
|
+
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2293
|
+
}
|
|
2294
|
+
return requiredAmount;
|
|
2295
|
+
}
|
|
2296
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
2297
|
+
{
|
|
2298
|
+
type: "function",
|
|
2299
|
+
name: "allowance",
|
|
2300
|
+
stateMutability: "view",
|
|
2301
|
+
inputs: [
|
|
2302
|
+
{ name: "owner", type: "address" },
|
|
2303
|
+
{ name: "spender", type: "address" }
|
|
2304
|
+
],
|
|
2305
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2306
|
+
},
|
|
2307
|
+
{
|
|
2308
|
+
type: "function",
|
|
2309
|
+
name: "approve",
|
|
2310
|
+
stateMutability: "nonpayable",
|
|
2311
|
+
inputs: [
|
|
2312
|
+
{ name: "spender", type: "address" },
|
|
2313
|
+
{ name: "amount", type: "uint256" }
|
|
2314
|
+
],
|
|
2315
|
+
outputs: [{ name: "", type: "bool" }]
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
type: "function",
|
|
2319
|
+
name: "balanceOf",
|
|
2320
|
+
stateMutability: "view",
|
|
2321
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2322
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2323
|
+
}
|
|
2324
|
+
];
|
|
2325
|
+
function randomPermit2Nonce() {
|
|
2326
|
+
const bytes = new Uint8Array(32);
|
|
2327
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2328
|
+
return BigInt(bytesToHex2(bytes));
|
|
2329
|
+
}
|
|
2330
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2331
|
+
return publicClient.readContract({
|
|
2332
|
+
address: token,
|
|
2333
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2334
|
+
functionName: "allowance",
|
|
2335
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2339
|
+
return publicClient.readContract({
|
|
2340
|
+
address: token,
|
|
2341
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2342
|
+
functionName: "balanceOf",
|
|
2343
|
+
args: [owner]
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/lib/sponsored-deposit.ts
|
|
2348
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2349
|
+
var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
|
|
2350
|
+
function provideDepositVerificationContext(callback, context) {
|
|
2351
|
+
callback[verificationSetter]?.(context);
|
|
2352
|
+
}
|
|
2353
|
+
function makeVerificationAwareDepositCallback(implementation) {
|
|
2354
|
+
let nextVerification;
|
|
2355
|
+
const callback = async (smartWallet, chainId, amount) => {
|
|
2356
|
+
const verification = nextVerification;
|
|
2357
|
+
nextVerification = void 0;
|
|
2358
|
+
return implementation(smartWallet, chainId, amount, verification);
|
|
2359
|
+
};
|
|
2360
|
+
Object.defineProperty(callback, verificationSetter, {
|
|
2361
|
+
value: (context) => {
|
|
2362
|
+
nextVerification = context;
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
return callback;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2369
|
+
import { SiweMessage, generateNonce } from "siwe";
|
|
2370
|
+
import {
|
|
2371
|
+
createPublicClient as createPublicClient2,
|
|
2372
|
+
createWalletClient,
|
|
2373
|
+
custom,
|
|
2374
|
+
getAddress
|
|
2375
|
+
} from "viem";
|
|
2376
|
+
import { base as base2 } from "viem/chains";
|
|
2377
|
+
|
|
2378
|
+
// src/agents/yieldseeker/yieldseeker.auth-cache.ts
|
|
2379
|
+
var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
|
|
2380
|
+
var INVALIDATED_KEY_PREFIXES = [
|
|
2381
|
+
"owney.yieldseeker.session",
|
|
2382
|
+
"owney.yieldseeker.session.v3",
|
|
2383
|
+
"owney.yieldseeker.session.v4"
|
|
2384
|
+
];
|
|
2385
|
+
var storage2 = () => {
|
|
2386
|
+
if (typeof window === "undefined") return null;
|
|
2387
|
+
try {
|
|
2388
|
+
return window.localStorage;
|
|
2389
|
+
} catch {
|
|
2390
|
+
return null;
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2394
|
+
var invalidatedKeys = (address, chainId) => INVALIDATED_KEY_PREFIXES.map(
|
|
2395
|
+
(prefix) => `${prefix}:${address.toLowerCase()}:${chainId}`
|
|
2396
|
+
);
|
|
2397
|
+
var clearInvalidatedSessions = (store, address, chainId) => {
|
|
2398
|
+
for (const key2 of invalidatedKeys(address, chainId)) {
|
|
2399
|
+
memorySessions2.delete(key2);
|
|
2400
|
+
try {
|
|
2401
|
+
store?.removeItem(key2);
|
|
2402
|
+
} catch {
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2407
|
+
var isValidSession = (session) => {
|
|
2408
|
+
if (!session?.token) return false;
|
|
2409
|
+
try {
|
|
2410
|
+
const parsed = JSON.parse(atob(session.token));
|
|
2411
|
+
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2412
|
+
} catch {
|
|
2413
|
+
return false;
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
var readYieldseekerSession = (address, chainId) => {
|
|
2417
|
+
if (typeof window === "undefined") return null;
|
|
2418
|
+
const key2 = buildKey2(address, chainId);
|
|
2419
|
+
const store = storage2();
|
|
2420
|
+
clearInvalidatedSessions(store, address, chainId);
|
|
2421
|
+
let raw2 = null;
|
|
2422
|
+
try {
|
|
2423
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
2424
|
+
} catch {
|
|
2425
|
+
raw2 = null;
|
|
2426
|
+
}
|
|
2427
|
+
if (raw2) {
|
|
2428
|
+
try {
|
|
2429
|
+
const parsed = JSON.parse(raw2);
|
|
2430
|
+
if (isValidSession(parsed)) return parsed.token;
|
|
2431
|
+
} catch {
|
|
2432
|
+
}
|
|
2433
|
+
memorySessions2.delete(key2);
|
|
2434
|
+
try {
|
|
2435
|
+
store?.removeItem(key2);
|
|
2436
|
+
} catch {
|
|
2437
|
+
}
|
|
2438
|
+
return null;
|
|
2439
|
+
}
|
|
2440
|
+
const cached = memorySessions2.get(key2);
|
|
2441
|
+
if (isValidSession(cached)) return cached.token;
|
|
2442
|
+
if (cached) memorySessions2.delete(key2);
|
|
2443
|
+
return null;
|
|
2444
|
+
};
|
|
2445
|
+
var writeYieldseekerSession = (address, chainId, token) => {
|
|
2446
|
+
if (typeof window === "undefined") return;
|
|
2447
|
+
const session = { token };
|
|
2448
|
+
if (!isValidSession(session)) return;
|
|
2449
|
+
const key2 = buildKey2(address, chainId);
|
|
2450
|
+
memorySessions2.set(key2, session);
|
|
2451
|
+
const store = storage2();
|
|
2452
|
+
try {
|
|
2453
|
+
store?.setItem(key2, JSON.stringify(session));
|
|
2454
|
+
} catch {
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
var clearYieldseekerSession = (address, chainId) => {
|
|
2458
|
+
const key2 = buildKey2(address, chainId);
|
|
2459
|
+
memorySessions2.delete(key2);
|
|
2460
|
+
const store = storage2();
|
|
2461
|
+
clearInvalidatedSessions(store, address, chainId);
|
|
2462
|
+
try {
|
|
2463
|
+
store?.removeItem(key2);
|
|
2464
|
+
} catch {
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
|
|
2468
|
+
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2469
|
+
function resolveSiweOrigin(override) {
|
|
2470
|
+
const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
|
|
2471
|
+
if (!origin || origin === "null") {
|
|
2472
|
+
throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
|
|
2473
|
+
}
|
|
2474
|
+
const url = new URL(origin);
|
|
2475
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2476
|
+
throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
|
|
2477
|
+
}
|
|
2478
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
2479
|
+
throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
|
|
2480
|
+
}
|
|
2481
|
+
return url;
|
|
2482
|
+
}
|
|
2483
|
+
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2484
|
+
const url = resolveSiweOrigin(dependencies.origin);
|
|
2485
|
+
return new SiweMessage({
|
|
2486
|
+
scheme: url.protocol.slice(0, -1),
|
|
2487
|
+
domain: url.host,
|
|
2488
|
+
address: getAddress(address),
|
|
2489
|
+
uri: url.origin,
|
|
2490
|
+
version: "1",
|
|
2491
|
+
chainId,
|
|
2492
|
+
nonce: (dependencies.nonce ?? generateNonce)(),
|
|
2493
|
+
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2494
|
+
}).prepareMessage();
|
|
2495
|
+
}
|
|
2496
|
+
function encodeYieldseekerAuthToken(token) {
|
|
2497
|
+
const bytes = new TextEncoder().encode(JSON.stringify(token));
|
|
2498
|
+
let binary = "";
|
|
2499
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2500
|
+
return btoa(binary);
|
|
2501
|
+
}
|
|
2502
|
+
var YieldseekerAuth = class {
|
|
2503
|
+
constructor(dependencies = {}) {
|
|
2504
|
+
this.dependencies = dependencies;
|
|
2505
|
+
}
|
|
2506
|
+
dependencies;
|
|
2507
|
+
tokens = /* @__PURE__ */ new Map();
|
|
2508
|
+
pending = /* @__PURE__ */ new Map();
|
|
2509
|
+
scopes = /* @__PURE__ */ new Map();
|
|
2510
|
+
key(state, chainId) {
|
|
2511
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
2512
|
+
}
|
|
2513
|
+
async getToken(state, chainId) {
|
|
2514
|
+
const key2 = this.key(state, chainId);
|
|
2515
|
+
const scope = { address: state.walletAddress, chainId };
|
|
2516
|
+
this.scopes.set(key2, scope);
|
|
2517
|
+
const cached = this.tokens.get(key2);
|
|
2518
|
+
if (cached) return cached;
|
|
2519
|
+
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2520
|
+
if (persisted && this.matchesOrigin(persisted)) {
|
|
2521
|
+
this.tokens.set(key2, persisted);
|
|
2522
|
+
return persisted;
|
|
2523
|
+
}
|
|
2524
|
+
if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
|
|
2525
|
+
const inFlight = this.pending.get(key2);
|
|
2526
|
+
if (inFlight) return inFlight;
|
|
2527
|
+
const request = this.sign(state, chainId).then((token) => {
|
|
2528
|
+
if (this.pending.get(key2) !== request) throw new Error("Wallet sign-in session changed. Please try again.");
|
|
2529
|
+
this.tokens.set(key2, token);
|
|
2530
|
+
writeYieldseekerSession(scope.address, scope.chainId, token);
|
|
2531
|
+
return token;
|
|
2532
|
+
});
|
|
2533
|
+
this.pending.set(key2, request);
|
|
2534
|
+
try {
|
|
2535
|
+
return await request;
|
|
2536
|
+
} finally {
|
|
2537
|
+
if (this.pending.get(key2) === request) this.pending.delete(key2);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
async refreshToken(state, chainId, rejectedToken) {
|
|
2541
|
+
const key2 = this.key(state, chainId);
|
|
2542
|
+
if (this.tokens.get(key2) === rejectedToken) {
|
|
2543
|
+
this.tokens.delete(key2);
|
|
2544
|
+
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2545
|
+
}
|
|
2546
|
+
return this.getToken(state, chainId);
|
|
2547
|
+
}
|
|
2548
|
+
matchesOrigin(token) {
|
|
2549
|
+
try {
|
|
2550
|
+
const message = new SiweMessage(JSON.parse(atob(token)).message);
|
|
2551
|
+
const url = resolveSiweOrigin(this.dependencies.origin);
|
|
2552
|
+
return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
|
|
2553
|
+
} catch {
|
|
2554
|
+
return false;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
clear(state, chainId) {
|
|
2558
|
+
if (!state || chainId === void 0) {
|
|
2559
|
+
for (const scope of this.scopes.values()) {
|
|
2560
|
+
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2561
|
+
}
|
|
2562
|
+
this.tokens.clear();
|
|
2563
|
+
this.pending.clear();
|
|
2564
|
+
this.scopes.clear();
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
const key2 = this.key(state, chainId);
|
|
2568
|
+
this.tokens.delete(key2);
|
|
2569
|
+
this.pending.delete(key2);
|
|
2570
|
+
this.scopes.delete(key2);
|
|
2571
|
+
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2572
|
+
}
|
|
2573
|
+
async sign(state, chainId) {
|
|
2574
|
+
const account = getAddress(state.walletAddress);
|
|
2575
|
+
const publicClient = createPublicClient2({
|
|
2576
|
+
chain: base2,
|
|
2577
|
+
transport: custom(state.provider)
|
|
2578
|
+
});
|
|
2579
|
+
const walletClient = createWalletClient({
|
|
2580
|
+
account,
|
|
2581
|
+
chain: base2,
|
|
2582
|
+
transport: custom(state.provider)
|
|
2583
|
+
});
|
|
2584
|
+
await ensureWalletOnChain(
|
|
2585
|
+
publicClient,
|
|
2586
|
+
walletClient,
|
|
2587
|
+
8453
|
|
2588
|
+
);
|
|
2589
|
+
const message = createYieldseekerSiweMessage(
|
|
2590
|
+
account,
|
|
2591
|
+
chainId,
|
|
2592
|
+
this.dependencies
|
|
2593
|
+
);
|
|
2594
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
2595
|
+
return encodeYieldseekerAuthToken({ message, signature });
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
|
|
2599
|
+
// src/agents/yieldseeker/yieldseeker.identity-cache.ts
|
|
2600
|
+
var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
|
|
2601
|
+
var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2602
|
+
var memoryIdentities = /* @__PURE__ */ new Map();
|
|
2603
|
+
var storage3 = () => {
|
|
2604
|
+
if (typeof window === "undefined") return null;
|
|
2605
|
+
try {
|
|
2606
|
+
return window.localStorage;
|
|
2607
|
+
} catch {
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
|
|
2612
|
+
function valid(value, walletAddress, chainId, now) {
|
|
2613
|
+
return Boolean(
|
|
2614
|
+
value && typeof value.userId === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value.userId) && value.walletAddress.toLowerCase() === walletAddress.toLowerCase() && value.chainId === chainId && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
|
|
2618
|
+
if (typeof window === "undefined") return null;
|
|
2619
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2620
|
+
const store = storage3();
|
|
2621
|
+
let parsed = null;
|
|
2622
|
+
try {
|
|
2623
|
+
const raw2 = store?.getItem(key2);
|
|
2624
|
+
parsed = raw2 ? JSON.parse(raw2) : null;
|
|
2625
|
+
} catch {
|
|
2626
|
+
parsed = null;
|
|
2627
|
+
}
|
|
2628
|
+
const candidate = parsed ?? memoryIdentities.get(key2);
|
|
2629
|
+
if (valid(candidate, walletAddress, chainId, now)) {
|
|
2630
|
+
memoryIdentities.set(key2, candidate);
|
|
2631
|
+
return { userId: candidate.userId };
|
|
2632
|
+
}
|
|
2633
|
+
memoryIdentities.delete(key2);
|
|
2634
|
+
try {
|
|
2635
|
+
store?.removeItem(key2);
|
|
2636
|
+
} catch {
|
|
2637
|
+
}
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
|
|
2641
|
+
if (typeof window === "undefined") return;
|
|
2642
|
+
const identity = {
|
|
2643
|
+
userId,
|
|
2644
|
+
walletAddress,
|
|
2645
|
+
chainId,
|
|
2646
|
+
expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
|
|
2647
|
+
};
|
|
2648
|
+
if (!valid(identity, walletAddress, chainId, now)) return;
|
|
2649
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2650
|
+
memoryIdentities.set(key2, identity);
|
|
2651
|
+
try {
|
|
2652
|
+
storage3()?.setItem(key2, JSON.stringify(identity));
|
|
2653
|
+
} catch {
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
function clearYieldseekerIdentity(walletAddress, chainId) {
|
|
2657
|
+
const key2 = keyFor(walletAddress, chainId);
|
|
2658
|
+
memoryIdentities.delete(key2);
|
|
2659
|
+
try {
|
|
2660
|
+
storage3()?.removeItem(key2);
|
|
2661
|
+
} catch {
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2666
|
+
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2667
|
+
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2668
|
+
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2669
|
+
}
|
|
2670
|
+
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2671
|
+
var YieldseekerApiError = class extends Error {
|
|
2672
|
+
constructor(status, providerCode, responseFields) {
|
|
2673
|
+
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2674
|
+
this.status = status;
|
|
2675
|
+
this.providerCode = providerCode;
|
|
2676
|
+
this.responseFields = responseFields;
|
|
2677
|
+
this.name = "YieldseekerApiError";
|
|
2678
|
+
}
|
|
2679
|
+
status;
|
|
2680
|
+
providerCode;
|
|
2681
|
+
responseFields;
|
|
2682
|
+
get isAuthenticationError() {
|
|
2683
|
+
return this.status === 401 || this.status === 403;
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
function providerError(body, fallback) {
|
|
2687
|
+
if (!body || typeof body !== "object") return { code: fallback };
|
|
2688
|
+
const record = body;
|
|
2689
|
+
return {
|
|
2690
|
+
code: typeof record.message === "string" ? record.message : fallback,
|
|
2691
|
+
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
var YieldseekerApiClient = class {
|
|
2695
|
+
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch.bind(globalThis)) {
|
|
2696
|
+
this.owneyApiKey = owneyApiKey;
|
|
2697
|
+
this.baseUrl = baseUrl;
|
|
2698
|
+
this.fetchFn = fetchFn;
|
|
2699
|
+
}
|
|
2700
|
+
owneyApiKey;
|
|
2701
|
+
baseUrl;
|
|
2702
|
+
fetchFn;
|
|
2703
|
+
async request(path, options = {}) {
|
|
2704
|
+
const controller = new AbortController();
|
|
2705
|
+
const timer = setTimeout(
|
|
2706
|
+
() => controller.abort(),
|
|
2707
|
+
options.timeoutMs ?? 15e3
|
|
2708
|
+
);
|
|
2709
|
+
try {
|
|
2710
|
+
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2711
|
+
method: options.method ?? "GET",
|
|
2712
|
+
headers: {
|
|
2713
|
+
"Content-Type": "application/json",
|
|
2714
|
+
"x-owney-api-key": this.owneyApiKey,
|
|
2715
|
+
...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
|
|
2716
|
+
},
|
|
2717
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2718
|
+
signal: controller.signal
|
|
2719
|
+
});
|
|
2720
|
+
const payload = await response.json().catch(() => null);
|
|
2721
|
+
if (!response.ok) {
|
|
2722
|
+
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2723
|
+
throw new YieldseekerApiError(
|
|
2724
|
+
response.status,
|
|
2725
|
+
error.code,
|
|
2726
|
+
error.fields
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2730
|
+
return payload.data;
|
|
2731
|
+
}
|
|
2732
|
+
return payload;
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
if (error instanceof YieldseekerApiError) throw error;
|
|
2735
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2736
|
+
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2737
|
+
}
|
|
2738
|
+
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2739
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2740
|
+
});
|
|
2741
|
+
} finally {
|
|
2742
|
+
clearTimeout(timer);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
|
|
2747
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2748
|
+
import { formatUnits, isAddress } from "viem";
|
|
2749
|
+
|
|
2750
|
+
// src/lib/helpers/snapshot-apy.ts
|
|
2751
|
+
var DAY_MS = 864e5;
|
|
2752
|
+
function snapshotTime(date) {
|
|
2753
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
|
|
2754
|
+
const time = Date.parse(date);
|
|
2755
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
|
|
2756
|
+
}
|
|
2757
|
+
function returnFactor(value) {
|
|
2758
|
+
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
2759
|
+
if (typeof value === "string" && value.trim() === "") return void 0;
|
|
2760
|
+
const factor = Number(value);
|
|
2761
|
+
return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
|
|
2762
|
+
}
|
|
2763
|
+
function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
|
|
2764
|
+
if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2767
|
+
const points = snapshots.flatMap((snapshot) => {
|
|
2768
|
+
const time = snapshotTime(snapshot.date);
|
|
2769
|
+
return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
|
|
2770
|
+
}).sort((a, b) => a.time - b.time);
|
|
2771
|
+
const end = points.at(-1);
|
|
2772
|
+
if (!end) return void 0;
|
|
2773
|
+
const cutoff = end.time - lookbackDays * DAY_MS;
|
|
2774
|
+
const start = points.find((point) => point.time >= cutoff);
|
|
2775
|
+
const actualDays = (end.time - start.time) / DAY_MS;
|
|
2776
|
+
if (actualDays <= 0) return void 0;
|
|
2777
|
+
const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
|
|
2778
|
+
const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
|
|
2779
|
+
if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
|
|
2780
|
+
return void 0;
|
|
2781
|
+
}
|
|
2782
|
+
const periodReturn = endFactor / startFactor - 1;
|
|
2783
|
+
const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
|
|
2784
|
+
return Number.isFinite(apy) ? apy : void 0;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/agents/yieldseeker/yieldseeker.types.ts
|
|
2788
|
+
var YIELDSEEKER_ASSET_METADATA = {
|
|
2789
|
+
USDC: {
|
|
2790
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2791
|
+
decimals: 6
|
|
2792
|
+
},
|
|
2793
|
+
WETH: {
|
|
2794
|
+
address: "0x4200000000000000000000000000000000000006",
|
|
2795
|
+
decimals: 18
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
|
|
2799
|
+
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2800
|
+
function invalid(endpoint, detail) {
|
|
2801
|
+
throw new OwneyError(
|
|
2802
|
+
"AGENT_INVALID_RESPONSE",
|
|
2803
|
+
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2804
|
+
{ endpoint, detail },
|
|
2805
|
+
"yieldseeker"
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
function raw(value, endpoint) {
|
|
2809
|
+
if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
|
|
2810
|
+
return invalid(endpoint, "expected a base-10 integer string");
|
|
2811
|
+
}
|
|
2812
|
+
return BigInt(value);
|
|
2813
|
+
}
|
|
2814
|
+
function decimal(value, decimals, endpoint) {
|
|
2815
|
+
return formatUnits(raw(value, endpoint), decimals);
|
|
2816
|
+
}
|
|
2817
|
+
function usd(rawAmount, decimals, price) {
|
|
2818
|
+
return Number(formatUnits(rawAmount, decimals)) * price;
|
|
2819
|
+
}
|
|
2820
|
+
function percent(value) {
|
|
2821
|
+
const result = Number(value);
|
|
2822
|
+
return Number.isFinite(result) ? result * 100 : 0;
|
|
2823
|
+
}
|
|
2824
|
+
var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
|
|
2825
|
+
function publicApyAfterYieldseekerFee(value) {
|
|
2826
|
+
const grossPercent = percent(value);
|
|
2827
|
+
if (grossPercent <= 0) return grossPercent;
|
|
2828
|
+
const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
|
|
2829
|
+
return Math.round(netPercent * 1e12) / 1e12;
|
|
2830
|
+
}
|
|
2831
|
+
function riskAdjustedApyForDays(option, days) {
|
|
2832
|
+
if (days === "7D") return option.riskAdjustedApy7dAverage;
|
|
2833
|
+
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
2834
|
+
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
2835
|
+
}
|
|
2836
|
+
function assetAddressValue(record, address) {
|
|
2837
|
+
const entry = Object.entries(record).find(
|
|
2838
|
+
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
2839
|
+
);
|
|
2840
|
+
return entry?.[1] ?? "0";
|
|
2841
|
+
}
|
|
2842
|
+
function position(value, asset, baseAssetDecimals) {
|
|
2843
|
+
const option = value?.yieldOption;
|
|
2844
|
+
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !isAddress(option.address)) {
|
|
2845
|
+
return invalid("yield positions", "missing vault metadata");
|
|
2846
|
+
}
|
|
2847
|
+
return {
|
|
2848
|
+
chain: "BASE",
|
|
2849
|
+
protocol: option.provider,
|
|
2850
|
+
protocolId: option.address,
|
|
2851
|
+
pool: option.name,
|
|
2852
|
+
asset,
|
|
2853
|
+
// `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
|
|
2854
|
+
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2855
|
+
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2856
|
+
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2857
|
+
// share quantity separately because withdraw-from-position expects it.
|
|
2858
|
+
amount: decimal(
|
|
2859
|
+
value.assetsBase,
|
|
2860
|
+
baseAssetDecimals,
|
|
2861
|
+
"yield positions"
|
|
2862
|
+
),
|
|
2863
|
+
amountRaw: String(value.assetsRaw),
|
|
2864
|
+
apy: percent(option.riskAdjustedApy),
|
|
2865
|
+
tvl: Number(option.totalDepositsUsd),
|
|
2866
|
+
liquidity: Number(option.withdrawableDepositsUsd)
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
function mapYieldseekerBalances(contexts) {
|
|
2870
|
+
const tokens = [];
|
|
2871
|
+
const assetBalances = [];
|
|
2872
|
+
const positions = [];
|
|
2873
|
+
let totalUsd = 0;
|
|
2874
|
+
for (const context of contexts) {
|
|
2875
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
2876
|
+
assetBalances.push({
|
|
2877
|
+
chain: "BASE",
|
|
2878
|
+
chainId: 8453,
|
|
2879
|
+
asset: context.asset,
|
|
2880
|
+
amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
|
|
2881
|
+
});
|
|
2882
|
+
const idle = assetAddressValue(
|
|
2883
|
+
context.snapshot.tokenBalances,
|
|
2884
|
+
metadata.address
|
|
2885
|
+
);
|
|
2886
|
+
tokens.push({
|
|
2887
|
+
chain: "BASE",
|
|
2888
|
+
chainId: 8453,
|
|
2889
|
+
asset: context.asset,
|
|
2890
|
+
amount: decimal(idle, metadata.decimals, "snapshot")
|
|
2891
|
+
});
|
|
2892
|
+
positions.push(
|
|
2893
|
+
...context.positions.map(
|
|
2894
|
+
(entry) => position(
|
|
2895
|
+
entry,
|
|
2896
|
+
context.asset,
|
|
2897
|
+
context.snapshot.baseAssetDecimals
|
|
2898
|
+
)
|
|
2899
|
+
)
|
|
2900
|
+
);
|
|
2901
|
+
totalUsd += usd(
|
|
2902
|
+
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
2903
|
+
context.snapshot.baseAssetDecimals,
|
|
2904
|
+
context.snapshot.baseAssetPriceUsd
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
return {
|
|
2908
|
+
...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
|
|
2909
|
+
totalBalance: String(totalUsd),
|
|
2910
|
+
totalBalanceAsset: "usdc",
|
|
2911
|
+
assetBalances,
|
|
2912
|
+
tokens,
|
|
2913
|
+
positions
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
function mapYieldseekerEarnings(contexts) {
|
|
2917
|
+
const tokens = [];
|
|
2918
|
+
let lifetimeEarnings = 0;
|
|
2919
|
+
for (const context of contexts) {
|
|
2920
|
+
const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
|
|
2921
|
+
tokens.push({
|
|
2922
|
+
chain: "BASE",
|
|
2923
|
+
chainId: 8453,
|
|
2924
|
+
asset: context.asset,
|
|
2925
|
+
amount: formatUnits(amount, context.snapshot.baseAssetDecimals)
|
|
2926
|
+
});
|
|
2927
|
+
lifetimeEarnings += usd(
|
|
2928
|
+
amount,
|
|
2929
|
+
context.snapshot.baseAssetDecimals,
|
|
2930
|
+
context.snapshot.baseAssetPriceUsd
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
2935
|
+
lifetimeEarnings,
|
|
2936
|
+
tokens
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
function apyForDays(context, days, now) {
|
|
2940
|
+
if (days === "7D") return percent(context.snapshot.apy7d);
|
|
2941
|
+
if (days === "30D") return percent(context.snapshot.apy30d);
|
|
2942
|
+
const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
|
|
2943
|
+
const apyPercent = apy === void 0 ? void 0 : apy * 100;
|
|
2944
|
+
return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
|
|
2945
|
+
}
|
|
2946
|
+
function dailyApy(point) {
|
|
2947
|
+
const total = raw(point.totalValueBase, "historic position");
|
|
2948
|
+
const earned = raw(point.dailyYieldBase, "historic position");
|
|
2949
|
+
const principal = total - earned;
|
|
2950
|
+
if (principal <= 0n || earned === 0n) return 0;
|
|
2951
|
+
return Number(earned) / Number(principal) * 365 * 100;
|
|
2952
|
+
}
|
|
2953
|
+
function aggregateHistory(contexts, dayCount, now) {
|
|
2954
|
+
const today = new Date(now).toISOString().slice(0, 10);
|
|
2955
|
+
const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
|
|
2956
|
+
const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
|
|
2957
|
+
const unit = assets.size === 1 ? [...assets][0] : "USD";
|
|
2958
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
2959
|
+
for (const context of contexts) {
|
|
2960
|
+
const points = context.historic?.dailyYieldSnapshots ?? [];
|
|
2961
|
+
for (const point of points) {
|
|
2962
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
|
|
2963
|
+
const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
|
|
2964
|
+
const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
|
|
2965
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
2966
|
+
invalid("historic position", "expected a finite non-negative balance");
|
|
2967
|
+
}
|
|
2968
|
+
const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
|
|
2969
|
+
current.weighted += dailyApy(point) * amount;
|
|
2970
|
+
current.amount += amount;
|
|
2971
|
+
byDate.set(point.date, current);
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
2975
|
+
date,
|
|
2976
|
+
apy: value.amount > 0 ? value.weighted / value.amount : 0,
|
|
2977
|
+
historicalBalance: { amount: value.amount, unit }
|
|
2978
|
+
}));
|
|
2979
|
+
}
|
|
2980
|
+
function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
|
|
2981
|
+
let weighted = 0;
|
|
2982
|
+
let totalUsd = 0;
|
|
2983
|
+
const byAsset = {};
|
|
2984
|
+
for (const context of contexts) {
|
|
2985
|
+
const valueUsd = usd(
|
|
2986
|
+
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
2987
|
+
context.snapshot.baseAssetDecimals,
|
|
2988
|
+
context.snapshot.baseAssetPriceUsd
|
|
2989
|
+
);
|
|
2990
|
+
const apy = apyForDays(context, days, now);
|
|
2991
|
+
if (apy === void 0) continue;
|
|
2992
|
+
weighted += apy * valueUsd;
|
|
2993
|
+
totalUsd += valueUsd;
|
|
2994
|
+
byAsset[context.asset] = apy;
|
|
2995
|
+
}
|
|
2996
|
+
const dayCount = Number(days.slice(0, -1));
|
|
2997
|
+
return {
|
|
2998
|
+
walletAddress,
|
|
2999
|
+
...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
|
|
3000
|
+
apyByChainAndAsset: { 8453: byAsset },
|
|
3001
|
+
history: aggregateHistory(contexts, dayCount, now)
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
function actionType(value) {
|
|
3005
|
+
const normalized = value.toLowerCase();
|
|
3006
|
+
if (normalized.includes("deposit")) return "Deposit";
|
|
3007
|
+
if (normalized.includes("withdraw")) return "Withdraw";
|
|
3008
|
+
if (normalized.includes("yield") || normalized.includes("earn"))
|
|
3009
|
+
return "Earned";
|
|
3010
|
+
return "Rebalance";
|
|
3011
|
+
}
|
|
3012
|
+
function transactionHashes(details) {
|
|
3013
|
+
if (!details) return [];
|
|
3014
|
+
const values = [
|
|
3015
|
+
details.transactionHash,
|
|
3016
|
+
details.txHash,
|
|
3017
|
+
...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
|
|
3018
|
+
...Array.isArray(details.txHashes) ? details.txHashes : []
|
|
3019
|
+
];
|
|
3020
|
+
return values.filter(
|
|
3021
|
+
(value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
|
|
3022
|
+
).filter((value, index, all) => all.indexOf(value) === index);
|
|
3023
|
+
}
|
|
3024
|
+
function actionEntry(action) {
|
|
3025
|
+
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3026
|
+
return {
|
|
3027
|
+
agent: "yieldseeker",
|
|
3028
|
+
action: actionType(action.actionType),
|
|
3029
|
+
date: action.createdDate,
|
|
3030
|
+
oldApy: null,
|
|
3031
|
+
newApy: null,
|
|
3032
|
+
transactions: [
|
|
3033
|
+
{
|
|
3034
|
+
txHashes: transactionHashes(action.details),
|
|
3035
|
+
chainId: 8453
|
|
3036
|
+
}
|
|
3037
|
+
],
|
|
3038
|
+
rebalanceLog: []
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
function depositDestination(context, movement) {
|
|
3042
|
+
const to = movement.toAddress.toLowerCase();
|
|
3043
|
+
const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
|
|
3044
|
+
if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
|
|
3045
|
+
const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
|
|
3046
|
+
const minted = receipt && context.historic?.movements.some((candidate) => candidate.chainId === movement.chainId && candidate.transactionHash.toLowerCase() === movement.transactionHash.toLowerCase() && candidate.assetAddress.toLowerCase() === receipt.address.toLowerCase() && candidate.fromAddress.toLowerCase() === "0x0000000000000000000000000000000000000000" && candidate.toAddress.toLowerCase() === context.wallet.walletAddress.toLowerCase() && raw(candidate.assetAmount, "historic position") > 0n);
|
|
3047
|
+
if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
|
|
3048
|
+
return void 0;
|
|
3049
|
+
}
|
|
3050
|
+
function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
|
|
3051
|
+
const from = movement.fromAddress.toLowerCase();
|
|
3052
|
+
const to = movement.toAddress.toLowerCase();
|
|
3053
|
+
const owner = ownerAddress.toLowerCase();
|
|
3054
|
+
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3055
|
+
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3056
|
+
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3057
|
+
return void 0;
|
|
3058
|
+
}
|
|
3059
|
+
let action;
|
|
3060
|
+
if (to === agentWallet && !vaultAddresses.has(from)) {
|
|
3061
|
+
action = "Top up";
|
|
3062
|
+
} else if (from === agentWallet && to === owner) {
|
|
3063
|
+
action = "Withdraw";
|
|
3064
|
+
} else if (from === agentWallet && destination) {
|
|
3065
|
+
action = "Deposit";
|
|
3066
|
+
}
|
|
3067
|
+
if (!action) return void 0;
|
|
3068
|
+
return {
|
|
3069
|
+
agent: "yieldseeker",
|
|
3070
|
+
action,
|
|
3071
|
+
...action === "Deposit" && destination ? { positions: [{
|
|
3072
|
+
...destination,
|
|
3073
|
+
amount: decimal(movement.assetAmount, YIELDSEEKER_ASSET_METADATA[asset].decimals, "historic position")
|
|
3074
|
+
}] } : {},
|
|
3075
|
+
date: movement.blockDate,
|
|
3076
|
+
oldApy: null,
|
|
3077
|
+
newApy: null,
|
|
3078
|
+
transactions: [
|
|
3079
|
+
{
|
|
3080
|
+
txHashes: [movement.transactionHash],
|
|
3081
|
+
chainId: agent.chainId,
|
|
3082
|
+
tokenSymbol: asset,
|
|
3083
|
+
amount: decimal(
|
|
3084
|
+
movement.assetAmount,
|
|
3085
|
+
YIELDSEEKER_ASSET_METADATA[asset].decimals,
|
|
3086
|
+
"historic position"
|
|
3087
|
+
)
|
|
3088
|
+
}
|
|
3089
|
+
],
|
|
3090
|
+
rebalanceLog: []
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
function mapYieldseekerHistory(contexts, options) {
|
|
3094
|
+
const entries = contexts.flatMap((context) => {
|
|
3095
|
+
const seenMovements = /* @__PURE__ */ new Set();
|
|
3096
|
+
const movements = (context.historic?.movements ?? []).filter((movement) => {
|
|
3097
|
+
const key2 = `${movement.chainId}:${movement.transactionHash.toLowerCase()}:${movement.logIndex}`;
|
|
3098
|
+
if (seenMovements.has(key2)) return false;
|
|
3099
|
+
seenMovements.add(key2);
|
|
3100
|
+
return true;
|
|
3101
|
+
});
|
|
3102
|
+
return [
|
|
3103
|
+
...movements.map(
|
|
3104
|
+
(movement) => movementEntry(
|
|
3105
|
+
movement,
|
|
3106
|
+
context.wallet,
|
|
3107
|
+
context.agent,
|
|
3108
|
+
context.asset,
|
|
3109
|
+
options.ownerAddress,
|
|
3110
|
+
options.vaultAddresses,
|
|
3111
|
+
depositDestination(context, movement)
|
|
3112
|
+
)
|
|
3113
|
+
),
|
|
3114
|
+
...(context.actions ?? []).map(actionEntry)
|
|
3115
|
+
].filter((entry) => entry !== void 0);
|
|
3116
|
+
});
|
|
3117
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3118
|
+
const ungrouped = [];
|
|
3119
|
+
for (const entry of entries) {
|
|
3120
|
+
const tx = entry.transactions[0];
|
|
3121
|
+
const hash = tx?.txHashes[0];
|
|
3122
|
+
if (!hash) {
|
|
3123
|
+
ungrouped.push(entry);
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
|
|
3127
|
+
const previous = grouped.get(key2);
|
|
3128
|
+
if (!previous) {
|
|
3129
|
+
grouped.set(key2, entry);
|
|
3130
|
+
continue;
|
|
3131
|
+
}
|
|
3132
|
+
if (entry.action === "Deposit" && entry.positions?.length) {
|
|
3133
|
+
if (!previous.positions?.length) {
|
|
3134
|
+
grouped.set(key2, entry);
|
|
3135
|
+
continue;
|
|
3136
|
+
}
|
|
3137
|
+
previous.positions.push(...entry.positions);
|
|
3138
|
+
previous.transactions.push(...entry.transactions);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
const filtered = [...grouped.values(), ...ungrouped].filter((entry) => (!options.fromDate || entry.date >= options.fromDate) && (!options.toDate || entry.date <= options.toDate)).sort((left, right) => right.date.localeCompare(left.date));
|
|
3142
|
+
return {
|
|
3143
|
+
data: filtered.slice(0, options.limit),
|
|
3144
|
+
// v1 returns the whole action/movement collection and defines no cursor.
|
|
3145
|
+
// Report a terminal page so callers never loop over the same prefix.
|
|
3146
|
+
hasMore: false
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
function mapYieldseekerProfile(address, contexts) {
|
|
3150
|
+
const protocols = /* @__PURE__ */ new Set();
|
|
3151
|
+
for (const context of contexts) {
|
|
3152
|
+
for (const current of context.positions) {
|
|
3153
|
+
if (current.yieldOption?.provider) {
|
|
3154
|
+
protocols.add(String(current.yieldOption.provider));
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
return {
|
|
3159
|
+
address,
|
|
3160
|
+
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3161
|
+
chains: contexts.length > 0 ? [8453] : [],
|
|
3162
|
+
hasActiveSessionKey: contexts.some(
|
|
3163
|
+
(context) => context.wallet.initializedDate != null
|
|
3164
|
+
),
|
|
3165
|
+
protocols: [...protocols]
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
function mapYieldseekerAgentApy(options, days) {
|
|
3169
|
+
const perAsset = {};
|
|
3170
|
+
const all = [];
|
|
3171
|
+
for (const entry of options) {
|
|
3172
|
+
const apys = entry.yieldOptions.map(
|
|
3173
|
+
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
3174
|
+
).filter(Number.isFinite);
|
|
3175
|
+
if (apys.length === 0) continue;
|
|
3176
|
+
const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
|
|
3177
|
+
perAsset[entry.asset] = average;
|
|
3178
|
+
all.push(average);
|
|
3179
|
+
}
|
|
3180
|
+
return {
|
|
3181
|
+
averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
|
|
3182
|
+
detailedApys: { apyPerAsset: { 8453: perAsset } }
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3187
|
+
var OWNEY_AGENT_NAME = "owney";
|
|
3188
|
+
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3189
|
+
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3190
|
+
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3191
|
+
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3192
|
+
function generateYieldseekerUsername() {
|
|
3193
|
+
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3194
|
+
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
3195
|
+
}
|
|
3196
|
+
function isUsernameConflict(error) {
|
|
3197
|
+
if (!(error instanceof YieldseekerApiError)) return false;
|
|
3198
|
+
const code = error.providerCode.toUpperCase();
|
|
3199
|
+
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
3200
|
+
}
|
|
3201
|
+
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
3202
|
+
{
|
|
3203
|
+
type: "function",
|
|
3204
|
+
name: "withdrawAssetToUser",
|
|
3205
|
+
stateMutability: "nonpayable",
|
|
3206
|
+
inputs: [
|
|
3207
|
+
{ name: "recipient", type: "address" },
|
|
3208
|
+
{ name: "asset", type: "address" },
|
|
3209
|
+
{ name: "amount", type: "uint256" }
|
|
3210
|
+
],
|
|
3211
|
+
outputs: []
|
|
3212
|
+
},
|
|
3213
|
+
{
|
|
3214
|
+
type: "function",
|
|
3215
|
+
name: "withdrawAllAssetToUser",
|
|
3216
|
+
stateMutability: "nonpayable",
|
|
3217
|
+
inputs: [
|
|
3218
|
+
{ name: "recipient", type: "address" },
|
|
3219
|
+
{ name: "asset", type: "address" }
|
|
3220
|
+
],
|
|
3221
|
+
outputs: []
|
|
3222
|
+
}
|
|
3223
|
+
];
|
|
3224
|
+
function query(params) {
|
|
3225
|
+
const search = new URLSearchParams();
|
|
3226
|
+
for (const [key2, value] of Object.entries(params)) {
|
|
3227
|
+
if (value !== void 0) search.set(key2, String(value));
|
|
3228
|
+
}
|
|
3229
|
+
const encoded = search.toString();
|
|
3230
|
+
return encoded ? `?${encoded}` : "";
|
|
3231
|
+
}
|
|
3232
|
+
var YieldseekerAgent = class {
|
|
3233
|
+
id = "yieldseeker";
|
|
3234
|
+
balanceComposition = "tokens-plus-positions";
|
|
3235
|
+
supportedChainIds = [8453];
|
|
3236
|
+
supportedAssets = [
|
|
3237
|
+
{
|
|
3238
|
+
chainId: 8453,
|
|
3239
|
+
chain: "BASE",
|
|
3240
|
+
assets: [
|
|
3241
|
+
{ symbol: "USDC", minDepositAmount: "10000000" },
|
|
3242
|
+
{ symbol: "WETH", minDepositAmount: "1" }
|
|
3243
|
+
]
|
|
3244
|
+
}
|
|
3245
|
+
];
|
|
3246
|
+
api;
|
|
3247
|
+
auth;
|
|
3248
|
+
transactionExecutor;
|
|
3249
|
+
unwindReceiptWaiter;
|
|
3250
|
+
agentContexts = /* @__PURE__ */ new Map();
|
|
3251
|
+
users = /* @__PURE__ */ new Map();
|
|
3252
|
+
pendingAgents = /* @__PURE__ */ new Map();
|
|
3253
|
+
yieldOptions = /* @__PURE__ */ new Map();
|
|
3254
|
+
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3255
|
+
constructor(owneyApiKey, options = {}) {
|
|
3256
|
+
this.api = new YieldseekerApiClient(
|
|
3257
|
+
owneyApiKey,
|
|
3258
|
+
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
3259
|
+
options.fetchFn
|
|
3260
|
+
);
|
|
3261
|
+
this.auth = new YieldseekerAuth(options.auth);
|
|
3262
|
+
this.transactionExecutor = options.transactionExecutor;
|
|
3263
|
+
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3264
|
+
}
|
|
3265
|
+
async disconnect() {
|
|
3266
|
+
this.auth.clear();
|
|
3267
|
+
for (const key2 of this.users.keys()) {
|
|
3268
|
+
const [walletAddress, chainId] = key2.split(":");
|
|
3269
|
+
clearYieldseekerIdentity(walletAddress, Number(chainId));
|
|
3270
|
+
}
|
|
3271
|
+
this.users.clear();
|
|
3272
|
+
this.agentContexts.clear();
|
|
3273
|
+
this.pendingAgents.clear();
|
|
3274
|
+
}
|
|
3275
|
+
async activateAgent(state, chainId, asset) {
|
|
3276
|
+
this.assertChain(chainId);
|
|
3277
|
+
const targetAsset = asset ?? "USDC";
|
|
3278
|
+
this.assertAsset(targetAsset);
|
|
3279
|
+
await this.ensureAgent(state, chainId, targetAsset);
|
|
3280
|
+
}
|
|
3281
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3282
|
+
this.assertChain(chainId);
|
|
3283
|
+
this.assertAsset(asset);
|
|
3284
|
+
if (BigInt(amount) <= 0n) {
|
|
3285
|
+
throw new OwneyError(
|
|
3286
|
+
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
3287
|
+
"Yieldseeker deposits must be greater than zero.",
|
|
3288
|
+
{ amount, minDepositAmount: "1" },
|
|
3289
|
+
this.id
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
const context = await this.ensureAgent(state, chainId, asset);
|
|
3293
|
+
let txHash;
|
|
3294
|
+
try {
|
|
3295
|
+
if (depositCallback) {
|
|
3296
|
+
provideDepositVerificationContext(depositCallback, {
|
|
3297
|
+
agentId: "yieldseeker",
|
|
3298
|
+
signature: await this.auth.getToken(state, chainId),
|
|
3299
|
+
userId: context.user.userId,
|
|
3300
|
+
yieldseekerAgentId: context.agent.agentId
|
|
3301
|
+
});
|
|
3302
|
+
txHash = await depositCallback(
|
|
3303
|
+
context.wallet.walletAddress,
|
|
3304
|
+
chainId,
|
|
3305
|
+
amount
|
|
3306
|
+
);
|
|
3307
|
+
await this.waitForReceipt(state, chainId, txHash);
|
|
3308
|
+
} else {
|
|
3309
|
+
txHash = await this.submitTransaction(state, chainId, {
|
|
3310
|
+
from: getAddress2(state.walletAddress),
|
|
3311
|
+
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3312
|
+
data: encodeFunctionData({
|
|
3313
|
+
abi: erc20Abi,
|
|
3314
|
+
functionName: "transfer",
|
|
3315
|
+
args: [getAddress2(context.wallet.walletAddress), BigInt(amount)]
|
|
3316
|
+
}),
|
|
3317
|
+
value: "0",
|
|
3318
|
+
chainId
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
} finally {
|
|
3322
|
+
await this.refreshSnapshotAfterMovement(
|
|
3323
|
+
state,
|
|
3324
|
+
chainId,
|
|
3325
|
+
context,
|
|
3326
|
+
"deposit"
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
return {
|
|
3330
|
+
txHash,
|
|
3331
|
+
smartWallet: context.wallet.walletAddress,
|
|
3332
|
+
amount
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
async withdraw(state, chainId, asset, amount) {
|
|
3336
|
+
this.assertChain(chainId);
|
|
3337
|
+
this.assertAsset(asset);
|
|
3338
|
+
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
3339
|
+
throw new OwneyError(
|
|
3340
|
+
"WITHDRAW_FAILED",
|
|
3341
|
+
"Yieldseeker withdrawals must be greater than zero.",
|
|
3342
|
+
{ amount },
|
|
3343
|
+
this.id
|
|
3344
|
+
);
|
|
3345
|
+
}
|
|
3346
|
+
const context = await this.findAgent(state, chainId, asset);
|
|
3347
|
+
if (!context) {
|
|
3348
|
+
throw new OwneyError(
|
|
3349
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3350
|
+
`No Yieldseeker ${asset} agent exists for this wallet.`,
|
|
3351
|
+
{ asset, available: "0" },
|
|
3352
|
+
this.id
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
try {
|
|
3356
|
+
const portfolio = await this.loadPortfolioContext(
|
|
3357
|
+
state,
|
|
3358
|
+
chainId,
|
|
3359
|
+
context
|
|
3360
|
+
);
|
|
3361
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3362
|
+
const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
|
|
3363
|
+
([address]) => address.toLowerCase() === metadata.address.toLowerCase()
|
|
3364
|
+
);
|
|
3365
|
+
const idle = BigInt(idleEntry?.[1] ?? "0");
|
|
3366
|
+
const deployed = portfolio.positions.reduce(
|
|
3367
|
+
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3368
|
+
0n
|
|
3369
|
+
);
|
|
3370
|
+
const totalAvailable = idle + deployed;
|
|
3371
|
+
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3372
|
+
if (requested > totalAvailable) {
|
|
3373
|
+
throw new OwneyError(
|
|
3374
|
+
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3375
|
+
`Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
|
|
3376
|
+
{
|
|
3377
|
+
asset,
|
|
3378
|
+
requested: requested.toString(),
|
|
3379
|
+
available: totalAvailable.toString()
|
|
3380
|
+
},
|
|
3381
|
+
this.id
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
let remaining = requested > idle ? requested - idle : 0n;
|
|
3385
|
+
for (const position2 of portfolio.positions) {
|
|
3386
|
+
if (remaining === 0n) break;
|
|
3387
|
+
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
3388
|
+
if (available <= 0n) continue;
|
|
3389
|
+
const assetsRaw = available < remaining ? available : remaining;
|
|
3390
|
+
const response = await this.walletRequest(
|
|
3391
|
+
state,
|
|
3392
|
+
chainId,
|
|
3393
|
+
this.agentPath(context, "withdraw-from-position"),
|
|
3394
|
+
{
|
|
3395
|
+
method: "POST",
|
|
3396
|
+
body: {
|
|
3397
|
+
chainId,
|
|
3398
|
+
vaultAddress: position2.yieldOption.address,
|
|
3399
|
+
assetsRaw: assetsRaw.toString()
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
);
|
|
3403
|
+
if (!this.isTransactionHash(response?.transactionHash)) {
|
|
3404
|
+
throw this.invalidResponse("position withdrawal");
|
|
3405
|
+
}
|
|
3406
|
+
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3407
|
+
remaining -= assetsRaw;
|
|
3408
|
+
}
|
|
3409
|
+
if (remaining > 0n) {
|
|
3410
|
+
throw this.invalidResponse("yield positions", {
|
|
3411
|
+
reason: "Withdrawable positions could not cover the request.",
|
|
3412
|
+
remaining: remaining.toString()
|
|
3413
|
+
});
|
|
3414
|
+
}
|
|
3415
|
+
const account = getAddress2(state.walletAddress);
|
|
3416
|
+
const txHash = await this.submitTransaction(state, chainId, {
|
|
3417
|
+
from: account,
|
|
3418
|
+
to: getAddress2(context.wallet.walletAddress),
|
|
3419
|
+
data: amount === void 0 ? encodeFunctionData({
|
|
3420
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3421
|
+
functionName: "withdrawAllAssetToUser",
|
|
3422
|
+
args: [account, metadata.address]
|
|
3423
|
+
}) : encodeFunctionData({
|
|
3424
|
+
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3425
|
+
functionName: "withdrawAssetToUser",
|
|
3426
|
+
args: [account, metadata.address, requested]
|
|
3427
|
+
}),
|
|
3428
|
+
value: "0",
|
|
3429
|
+
chainId
|
|
3430
|
+
});
|
|
3431
|
+
return {
|
|
3432
|
+
txHash,
|
|
3433
|
+
type: amount === void 0 ? "full" : "partial",
|
|
3434
|
+
amount: requested.toString()
|
|
3435
|
+
};
|
|
3436
|
+
} finally {
|
|
3437
|
+
await this.refreshSnapshotAfterMovement(
|
|
3438
|
+
state,
|
|
3439
|
+
chainId,
|
|
3440
|
+
context,
|
|
3441
|
+
"withdrawal"
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
async getBalances(state, chainId) {
|
|
3446
|
+
this.assertChain(chainId);
|
|
3447
|
+
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
|
|
3448
|
+
}
|
|
3449
|
+
async getEarnings(state, chainId) {
|
|
3450
|
+
this.assertChain(chainId);
|
|
3451
|
+
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
3452
|
+
}
|
|
3453
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
3454
|
+
this.assertChain(chainId);
|
|
3455
|
+
const asset = tokenSymbol?.toUpperCase();
|
|
3456
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
3457
|
+
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3458
|
+
...asset ? { asset } : {},
|
|
3459
|
+
historic: true
|
|
3460
|
+
});
|
|
3461
|
+
return mapYieldseekerApy(state.walletAddress, contexts, days);
|
|
3462
|
+
}
|
|
3463
|
+
async getHistory(state, chainId, options) {
|
|
3464
|
+
this.assertChain(chainId);
|
|
3465
|
+
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3466
|
+
if (asset !== void 0) this.assertAsset(asset);
|
|
3467
|
+
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3468
|
+
...asset ? { asset } : {},
|
|
3469
|
+
historic: true,
|
|
3470
|
+
actions: true
|
|
3471
|
+
});
|
|
3472
|
+
const catalog = await Promise.all(
|
|
3473
|
+
[...new Set(contexts.map((context) => context.asset))].map(
|
|
3474
|
+
(contextAsset) => this.loadYieldOptions(contextAsset)
|
|
3475
|
+
)
|
|
3476
|
+
);
|
|
3477
|
+
const vaultAddresses = new Set(
|
|
3478
|
+
catalog.flat().filter(
|
|
3479
|
+
(yieldOption) => yieldOption.chainId === chainId && isAddress2(yieldOption.address)
|
|
3480
|
+
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
3481
|
+
);
|
|
3482
|
+
return mapYieldseekerHistory(contexts, {
|
|
3483
|
+
limit: options?.limit ?? 10,
|
|
3484
|
+
ownerAddress: state.walletAddress,
|
|
3485
|
+
vaultAddresses,
|
|
3486
|
+
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
3487
|
+
...options?.toDate ? { toDate: options.toDate } : {}
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
async getUserProfile(state, chainId) {
|
|
3491
|
+
this.assertChain(chainId);
|
|
3492
|
+
return mapYieldseekerProfile(
|
|
3493
|
+
state.walletAddress,
|
|
3494
|
+
await this.loadPortfolio(state, chainId, {})
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
async getAgentApy(days, options) {
|
|
3498
|
+
this.assertOptionalChain(options?.chainId);
|
|
3499
|
+
const requested = options?.tokenSymbol?.toUpperCase();
|
|
3500
|
+
if (requested !== void 0) this.assertAsset(requested);
|
|
3501
|
+
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
3502
|
+
const values = await Promise.all(
|
|
3503
|
+
assets.map(async (asset) => {
|
|
3504
|
+
return { asset, yieldOptions: await this.loadYieldOptions(asset) };
|
|
3505
|
+
})
|
|
3506
|
+
);
|
|
3507
|
+
return mapYieldseekerAgentApy(values, days);
|
|
3508
|
+
}
|
|
3509
|
+
async loadYieldOptions(asset) {
|
|
3510
|
+
const cached = this.yieldOptions.get(asset);
|
|
3511
|
+
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
3512
|
+
const pending = this.pendingYieldOptions.get(asset);
|
|
3513
|
+
if (pending) return pending;
|
|
3514
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3515
|
+
const request = this.api.request(
|
|
3516
|
+
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3517
|
+
).then((response) => {
|
|
3518
|
+
if (!Array.isArray(response?.yieldOptions)) {
|
|
3519
|
+
throw this.invalidResponse("yield options");
|
|
3520
|
+
}
|
|
3521
|
+
this.yieldOptions.set(asset, {
|
|
3522
|
+
expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
|
|
3523
|
+
value: response.yieldOptions
|
|
3524
|
+
});
|
|
3525
|
+
return response.yieldOptions;
|
|
3526
|
+
}).finally(() => this.pendingYieldOptions.delete(asset));
|
|
3527
|
+
this.pendingYieldOptions.set(asset, request);
|
|
3528
|
+
return request;
|
|
3529
|
+
}
|
|
3530
|
+
userKey(state, chainId) {
|
|
3531
|
+
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
3532
|
+
}
|
|
3533
|
+
contextKey(state, chainId, asset) {
|
|
3534
|
+
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3535
|
+
}
|
|
3536
|
+
async resolveUser(state, chainId) {
|
|
3537
|
+
const key2 = this.userKey(state, chainId);
|
|
3538
|
+
const inMemory = this.users.get(key2);
|
|
3539
|
+
if (inMemory) return inMemory;
|
|
3540
|
+
const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
|
|
3541
|
+
if (persisted) {
|
|
3542
|
+
this.users.set(key2, persisted);
|
|
3543
|
+
return persisted;
|
|
3544
|
+
}
|
|
3545
|
+
const walletAddress = getAddress2(state.walletAddress);
|
|
3546
|
+
let user = null;
|
|
3547
|
+
try {
|
|
3548
|
+
const login = await this.providerRequest(
|
|
3549
|
+
state,
|
|
3550
|
+
chainId,
|
|
3551
|
+
"/users/login-with-wallet",
|
|
3552
|
+
{ method: "POST", body: { walletAddress } }
|
|
3553
|
+
);
|
|
3554
|
+
user = login?.user ?? null;
|
|
3555
|
+
if (!user) {
|
|
3556
|
+
throw this.invalidResponse("wallet login", {
|
|
3557
|
+
reason: "A successful login returned no user."
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
|
|
3562
|
+
if (error instanceof OwneyError) throw error;
|
|
3563
|
+
throw this.mapApiError(error);
|
|
3564
|
+
}
|
|
3565
|
+
let created;
|
|
3566
|
+
for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
|
|
3567
|
+
try {
|
|
3568
|
+
created = await this.providerRequest(
|
|
3569
|
+
state,
|
|
3570
|
+
chainId,
|
|
3571
|
+
"/users",
|
|
3572
|
+
{
|
|
3573
|
+
method: "POST",
|
|
3574
|
+
body: {
|
|
3575
|
+
walletAddress,
|
|
3576
|
+
username: generateYieldseekerUsername()
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
);
|
|
3580
|
+
break;
|
|
3581
|
+
} catch (createError) {
|
|
3582
|
+
const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
|
|
3583
|
+
if (canRetry) continue;
|
|
3584
|
+
throw this.mapApiError(createError);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
user = created?.user ?? null;
|
|
3588
|
+
}
|
|
3589
|
+
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3590
|
+
throw this.invalidResponse("wallet identity");
|
|
3591
|
+
}
|
|
3592
|
+
const resolved = { userId: user.userId };
|
|
3593
|
+
this.users.set(key2, resolved);
|
|
3594
|
+
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
3595
|
+
return resolved;
|
|
3596
|
+
}
|
|
3597
|
+
forgetUser(state, chainId) {
|
|
3598
|
+
this.users.delete(this.userKey(state, chainId));
|
|
3599
|
+
clearYieldseekerIdentity(state.walletAddress, chainId);
|
|
3600
|
+
}
|
|
3601
|
+
async ensureAgent(state, chainId, asset) {
|
|
3602
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3603
|
+
const cached = this.agentContexts.get(key2);
|
|
3604
|
+
if (cached) return cached;
|
|
3605
|
+
const pending = this.pendingAgents.get(key2);
|
|
3606
|
+
if (pending) return pending;
|
|
3607
|
+
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3608
|
+
async (context) => {
|
|
3609
|
+
if (!context) throw this.invalidResponse("agent creation");
|
|
3610
|
+
await this.deployAgent(state, chainId, context);
|
|
3611
|
+
this.agentContexts.set(key2, context);
|
|
3612
|
+
return context;
|
|
3613
|
+
}
|
|
3614
|
+
);
|
|
3615
|
+
this.pendingAgents.set(key2, request);
|
|
3616
|
+
try {
|
|
3617
|
+
return await request;
|
|
3618
|
+
} finally {
|
|
3619
|
+
this.pendingAgents.delete(key2);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
async findAgent(state, chainId, asset) {
|
|
3623
|
+
const key2 = this.contextKey(state, chainId, asset);
|
|
3624
|
+
const cached = this.agentContexts.get(key2);
|
|
3625
|
+
if (cached) return cached;
|
|
3626
|
+
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3627
|
+
if (context) this.agentContexts.set(key2, context);
|
|
3628
|
+
return context;
|
|
3629
|
+
}
|
|
3630
|
+
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3631
|
+
const user = await this.resolveUser(state, chainId);
|
|
3632
|
+
const response = await this.walletRequest(
|
|
3633
|
+
state,
|
|
3634
|
+
chainId,
|
|
3635
|
+
`/users/${user.userId}/agents`
|
|
3636
|
+
);
|
|
3637
|
+
if (!Array.isArray(response?.agents)) {
|
|
3638
|
+
throw this.invalidResponse("agent list");
|
|
3639
|
+
}
|
|
3640
|
+
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3641
|
+
let agent = response.agents.find(
|
|
3642
|
+
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3643
|
+
);
|
|
3644
|
+
if (!agent && createIfMissing) {
|
|
3645
|
+
const created = await this.walletRequest(
|
|
3646
|
+
state,
|
|
3647
|
+
chainId,
|
|
3648
|
+
`/users/${user.userId}/agents`,
|
|
3649
|
+
{
|
|
3650
|
+
method: "POST",
|
|
3651
|
+
body: {
|
|
3652
|
+
name: OWNEY_AGENT_NAME,
|
|
3653
|
+
emoji: "\u{1F989}",
|
|
3654
|
+
chainId,
|
|
3655
|
+
assetAddress: metadata.address,
|
|
3656
|
+
type: "vault",
|
|
3657
|
+
rulePreset: null
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
);
|
|
3661
|
+
agent = created?.agent;
|
|
3662
|
+
}
|
|
3663
|
+
if (!agent) return null;
|
|
3664
|
+
this.assertAgent(agent);
|
|
3665
|
+
const walletResponse = await this.walletRequest(
|
|
3666
|
+
state,
|
|
3667
|
+
chainId,
|
|
3668
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3669
|
+
);
|
|
3670
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3671
|
+
throw this.invalidResponse("agent wallet");
|
|
3672
|
+
}
|
|
3673
|
+
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
3674
|
+
}
|
|
3675
|
+
async loadPortfolio(state, chainId, options) {
|
|
3676
|
+
const user = await this.resolveUser(state, chainId);
|
|
3677
|
+
const response = await this.walletRequest(
|
|
3678
|
+
state,
|
|
3679
|
+
chainId,
|
|
3680
|
+
`/users/${user.userId}/agents`
|
|
3681
|
+
);
|
|
3682
|
+
if (!Array.isArray(response?.agents)) {
|
|
3683
|
+
throw this.invalidResponse("agent list");
|
|
3684
|
+
}
|
|
3685
|
+
const contexts = [];
|
|
3686
|
+
for (const agent of response.agents) {
|
|
3687
|
+
const asset = this.assetForAgent(agent);
|
|
3688
|
+
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
this.assertAgent(agent);
|
|
3692
|
+
const walletResponse = await this.walletRequest(
|
|
3693
|
+
state,
|
|
3694
|
+
chainId,
|
|
3695
|
+
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3696
|
+
);
|
|
3697
|
+
if (!walletResponse?.agentWallet || !isAddress2(walletResponse.agentWallet.walletAddress)) {
|
|
3698
|
+
throw this.invalidResponse("agent wallet");
|
|
3699
|
+
}
|
|
3700
|
+
const context = {
|
|
3701
|
+
user,
|
|
3702
|
+
agent,
|
|
3703
|
+
wallet: walletResponse.agentWallet,
|
|
3704
|
+
asset
|
|
3705
|
+
};
|
|
3706
|
+
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3707
|
+
contexts.push(context);
|
|
3708
|
+
}
|
|
3709
|
+
return Promise.all(
|
|
3710
|
+
contexts.map(
|
|
3711
|
+
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3712
|
+
)
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3715
|
+
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3716
|
+
const [snapshot, positions, historic, actions] = await Promise.all([
|
|
3717
|
+
this.walletRequest(
|
|
3718
|
+
state,
|
|
3719
|
+
chainId,
|
|
3720
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3721
|
+
shouldOnlyUseRecentValue: true,
|
|
3722
|
+
shouldAllowStaleOnError: true
|
|
3723
|
+
})}`
|
|
3724
|
+
),
|
|
3725
|
+
this.walletRequest(
|
|
3726
|
+
state,
|
|
3727
|
+
chainId,
|
|
3728
|
+
this.agentPath(context, "yield-positions")
|
|
3729
|
+
),
|
|
3730
|
+
options.historic ? this.walletRequest(
|
|
3731
|
+
state,
|
|
3732
|
+
chainId,
|
|
3733
|
+
this.agentPath(context, "wallet/historic-position")
|
|
3734
|
+
) : Promise.resolve(void 0),
|
|
3735
|
+
options.actions ? this.walletRequest(
|
|
3736
|
+
state,
|
|
3737
|
+
chainId,
|
|
3738
|
+
this.agentPath(context, "actions")
|
|
3739
|
+
) : Promise.resolve(void 0)
|
|
3740
|
+
]);
|
|
3741
|
+
if (!snapshot?.agentSnapshot) {
|
|
3742
|
+
throw this.invalidResponse("agent snapshot");
|
|
3743
|
+
}
|
|
3744
|
+
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3745
|
+
throw this.invalidResponse("yield positions");
|
|
3746
|
+
}
|
|
3747
|
+
return {
|
|
3748
|
+
...context,
|
|
3749
|
+
snapshot: snapshot.agentSnapshot,
|
|
3750
|
+
positions: positions.yieldPositions,
|
|
3751
|
+
...historic?.position ? { historic: historic.position } : {},
|
|
3752
|
+
...actions?.actions ? { actions: actions.actions } : {}
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
async deployAgent(state, chainId, context) {
|
|
3756
|
+
if (context.wallet.initializedDate != null) return;
|
|
3757
|
+
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
3758
|
+
const deployed = await this.walletRequest(
|
|
3759
|
+
state,
|
|
3760
|
+
chainId,
|
|
3761
|
+
this.agentPath(context, "deploy"),
|
|
3762
|
+
{ method: "POST", body: {} }
|
|
3763
|
+
);
|
|
3764
|
+
if (!deployed?.agentWallet || !isAddress2(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
3765
|
+
throw this.invalidResponse("agent deployment", {
|
|
3766
|
+
reason: "Deploy did not return the expected Agent Wallet."
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
context.wallet = deployed.agentWallet;
|
|
3770
|
+
}
|
|
3771
|
+
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
3772
|
+
try {
|
|
3773
|
+
const response = await this.walletRequest(
|
|
3774
|
+
state,
|
|
3775
|
+
chainId,
|
|
3776
|
+
`${this.agentPath(context, "snapshot")}${query({
|
|
3777
|
+
shouldForceRefresh: true
|
|
3778
|
+
})}`
|
|
3779
|
+
);
|
|
3780
|
+
if (!response?.agentSnapshot) {
|
|
3781
|
+
throw this.invalidResponse("agent snapshot refresh");
|
|
3782
|
+
}
|
|
3783
|
+
} catch (error) {
|
|
3784
|
+
console.warn(
|
|
3785
|
+
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
3786
|
+
error
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
agentPath(context, suffix) {
|
|
3791
|
+
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3792
|
+
}
|
|
3793
|
+
async walletRequest(state, chainId, path, options = {}) {
|
|
3794
|
+
try {
|
|
3795
|
+
return await this.providerRequest(state, chainId, path, options);
|
|
3796
|
+
} catch (error) {
|
|
3797
|
+
throw this.mapApiError(error);
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
async providerRequest(state, chainId, path, options = {}) {
|
|
3801
|
+
this.assertChain(chainId);
|
|
3802
|
+
const request = (signature2) => this.api.request(path, {
|
|
3803
|
+
...options,
|
|
3804
|
+
signature: signature2
|
|
3805
|
+
});
|
|
3806
|
+
let signature = await this.auth.getToken(state, chainId);
|
|
3807
|
+
try {
|
|
3808
|
+
return await request(signature);
|
|
3809
|
+
} catch (error) {
|
|
3810
|
+
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
3811
|
+
if (error.providerCode === "NO_USER") throw error;
|
|
3812
|
+
if (!error.isAuthenticationError) throw error;
|
|
3813
|
+
signature = await this.auth.refreshToken(state, chainId, signature);
|
|
3814
|
+
try {
|
|
3815
|
+
return await request(signature);
|
|
3816
|
+
} catch (retryError) {
|
|
3817
|
+
if (retryError instanceof YieldseekerApiError && retryError.isAuthenticationError) {
|
|
3818
|
+
this.forgetUser(state, chainId);
|
|
3819
|
+
}
|
|
3820
|
+
throw retryError;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
mapApiError(error) {
|
|
3825
|
+
if (!(error instanceof YieldseekerApiError)) {
|
|
3826
|
+
return new OwneyError(
|
|
3827
|
+
"AGENT_API_ERROR",
|
|
3828
|
+
"Yieldseeker request failed.",
|
|
3829
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
3830
|
+
this.id
|
|
3831
|
+
);
|
|
3832
|
+
}
|
|
3833
|
+
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
3834
|
+
return new OwneyError(
|
|
3835
|
+
code,
|
|
3836
|
+
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3837
|
+
{
|
|
3838
|
+
statusCode: error.status,
|
|
3839
|
+
providerCode: error.providerCode,
|
|
3840
|
+
...error.responseFields ? { fields: error.responseFields } : {}
|
|
3841
|
+
},
|
|
3842
|
+
this.id
|
|
3843
|
+
);
|
|
3844
|
+
}
|
|
3845
|
+
async submitTransaction(state, chainId, transaction) {
|
|
3846
|
+
if (this.transactionExecutor) {
|
|
3847
|
+
return this.transactionExecutor(state, chainId, transaction);
|
|
3848
|
+
}
|
|
3849
|
+
this.assertTransaction(transaction, state, chainId);
|
|
3850
|
+
const account = getAddress2(state.walletAddress);
|
|
3851
|
+
const walletClient = createWalletClient2({
|
|
3852
|
+
account,
|
|
3853
|
+
chain: base3,
|
|
3854
|
+
transport: custom2(state.provider)
|
|
3855
|
+
});
|
|
3856
|
+
const publicClient = createPublicClient3({
|
|
3857
|
+
chain: base3,
|
|
3858
|
+
transport: custom2(state.provider)
|
|
3859
|
+
});
|
|
3860
|
+
await ensureWalletOnChain(
|
|
3861
|
+
publicClient,
|
|
3862
|
+
walletClient,
|
|
3863
|
+
8453
|
|
3864
|
+
);
|
|
3865
|
+
const hash = await walletClient.sendTransaction({
|
|
3866
|
+
account,
|
|
3867
|
+
chain: base3,
|
|
3868
|
+
to: getAddress2(transaction.to),
|
|
3869
|
+
data: transaction.data,
|
|
3870
|
+
value: BigInt(transaction.value)
|
|
3871
|
+
});
|
|
3872
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3873
|
+
hash,
|
|
3874
|
+
confirmations: 1
|
|
3875
|
+
});
|
|
3876
|
+
if (receipt.status !== "success") {
|
|
3877
|
+
throw new OwneyError(
|
|
3878
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3879
|
+
`Yieldseeker transaction reverted (${hash}).`,
|
|
3880
|
+
{ transactionHash: hash },
|
|
3881
|
+
this.id
|
|
3882
|
+
);
|
|
3883
|
+
}
|
|
3884
|
+
return hash;
|
|
3885
|
+
}
|
|
3886
|
+
async waitForReceipt(state, chainId, transactionHash) {
|
|
3887
|
+
if (this.unwindReceiptWaiter) {
|
|
3888
|
+
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
const publicClient = createPublicClient3({
|
|
3892
|
+
chain: base3,
|
|
3893
|
+
transport: custom2(state.provider)
|
|
3894
|
+
});
|
|
3895
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3896
|
+
hash: transactionHash,
|
|
3897
|
+
confirmations: 1
|
|
3898
|
+
});
|
|
3899
|
+
if (receipt.status !== "success") {
|
|
3900
|
+
throw new OwneyError(
|
|
3901
|
+
"AGENT_TRANSACTION_REVERTED",
|
|
3902
|
+
`Yieldseeker transaction reverted (${transactionHash}).`,
|
|
3903
|
+
{ transactionHash },
|
|
3904
|
+
this.id
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
assertTransaction(transaction, state, chainId) {
|
|
3909
|
+
if (!transaction || typeof transaction.from !== "string" || !isAddress2(transaction.from) || typeof transaction.to !== "string" || !isAddress2(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || getAddress2(transaction.from) !== getAddress2(state.walletAddress)) {
|
|
3910
|
+
throw this.invalidResponse("transaction");
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
assertAgent(agent) {
|
|
3914
|
+
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !isAddress2(agent.assetAddress) || agent.chainId !== 8453) {
|
|
3915
|
+
throw this.invalidResponse("agent");
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
isOwneyAgent(agent) {
|
|
3919
|
+
return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
|
|
3920
|
+
}
|
|
3921
|
+
assetForAgent(agent) {
|
|
3922
|
+
for (const asset of ["USDC", "WETH"]) {
|
|
3923
|
+
if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
|
|
3924
|
+
return asset;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
return null;
|
|
3928
|
+
}
|
|
3929
|
+
isTransactionHash(value) {
|
|
3930
|
+
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3931
|
+
}
|
|
3932
|
+
assertChain(chainId) {
|
|
3933
|
+
if (chainId !== 8453) {
|
|
3934
|
+
throw new OwneyError(
|
|
3935
|
+
"CHAIN_UNSUPPORTED",
|
|
3936
|
+
`Yieldseeker does not support chain ${chainId}.`,
|
|
3937
|
+
{ chainId, supportedChainIds: [8453] },
|
|
3938
|
+
this.id
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
assertOptionalChain(chainId) {
|
|
3943
|
+
if (chainId !== void 0) this.assertChain(chainId);
|
|
3944
|
+
}
|
|
3945
|
+
assertAsset(asset) {
|
|
3946
|
+
if (asset !== "USDC" && asset !== "WETH") {
|
|
3947
|
+
throw new OwneyError(
|
|
3948
|
+
"ASSET_UNSUPPORTED",
|
|
3949
|
+
`Yieldseeker does not support asset ${asset} in the Owney rollout.`,
|
|
3950
|
+
{
|
|
3951
|
+
asset,
|
|
3952
|
+
supportedAssets: ["USDC", "WETH"],
|
|
3953
|
+
providerAlsoAdvertises: ["cbBTC"]
|
|
3954
|
+
},
|
|
3955
|
+
this.id
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
invalidResponse(operation, details = {}) {
|
|
3960
|
+
return new OwneyError(
|
|
3961
|
+
"AGENT_INVALID_RESPONSE",
|
|
3962
|
+
`Yieldseeker returned an invalid ${operation} response.`,
|
|
3963
|
+
details,
|
|
3964
|
+
this.id
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3967
|
+
};
|
|
3968
|
+
|
|
3969
|
+
// src/lib/routing-api.ts
|
|
3970
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3971
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3972
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
3973
|
+
try {
|
|
3974
|
+
const res = await fetch(url, {
|
|
3975
|
+
method: "GET",
|
|
3976
|
+
headers: {
|
|
3977
|
+
"Content-Type": "application/json",
|
|
3978
|
+
"x-owney-api-key": `${apiKey}`
|
|
3979
|
+
}
|
|
3980
|
+
});
|
|
3981
|
+
if (!res.ok) {
|
|
3982
|
+
if (res.status !== 404) {
|
|
3983
|
+
console.warn(
|
|
3984
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
3985
|
+
);
|
|
3986
|
+
}
|
|
3987
|
+
return null;
|
|
3988
|
+
}
|
|
3989
|
+
const json = await res.json();
|
|
3990
|
+
const policy = json.success ? json.data ?? null : null;
|
|
3991
|
+
debugLog(
|
|
3992
|
+
"owney-sdk",
|
|
3993
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
3994
|
+
policy ?? void 0
|
|
3995
|
+
);
|
|
3996
|
+
return policy;
|
|
3997
|
+
} catch (error) {
|
|
3998
|
+
console.warn(
|
|
3999
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
4000
|
+
error instanceof Error ? error.message : String(error)
|
|
4001
|
+
);
|
|
4002
|
+
return null;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4006
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
4007
|
+
const res = await fetch(url, {
|
|
4008
|
+
method: "GET",
|
|
4009
|
+
headers: {
|
|
4010
|
+
"Content-Type": "application/json",
|
|
4011
|
+
"x-owney-api-key": `${apiKey}`
|
|
4012
|
+
}
|
|
4013
|
+
});
|
|
4014
|
+
if (!res.ok) {
|
|
4015
|
+
const text = await res.text().catch(() => "");
|
|
4016
|
+
throw new OwneyError(
|
|
4017
|
+
"API_ROUTING_ERROR",
|
|
4018
|
+
`Routing API error ${res.status}: ${text}`,
|
|
4019
|
+
{ statusCode: res.status, responseBody: text }
|
|
4020
|
+
);
|
|
4021
|
+
}
|
|
4022
|
+
const json = await res.json();
|
|
4023
|
+
if (!json.success) {
|
|
4024
|
+
throw new OwneyError(
|
|
4025
|
+
"API_ROUTING_FAILED",
|
|
4026
|
+
`Routing API request failed: ${json.message}`,
|
|
4027
|
+
{ message: json.message }
|
|
4028
|
+
);
|
|
4029
|
+
}
|
|
4030
|
+
return json.data;
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
// src/lib/health-report.ts
|
|
4034
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
4035
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL3) {
|
|
4036
|
+
try {
|
|
4037
|
+
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
4038
|
+
method: "POST",
|
|
4039
|
+
headers: {
|
|
4040
|
+
"Content-Type": "application/json",
|
|
4041
|
+
"x-owney-api-key": apiKey
|
|
4042
|
+
},
|
|
4043
|
+
body: JSON.stringify({
|
|
4044
|
+
agent_type: agentType,
|
|
4045
|
+
error_code: errorCode,
|
|
4046
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4047
|
+
})
|
|
4048
|
+
});
|
|
4049
|
+
} catch (err) {
|
|
4050
|
+
console.warn(
|
|
4051
|
+
`[owney-sdk] health-report failed for agent "${agentType}":`,
|
|
4052
|
+
err instanceof Error ? err.message : err
|
|
4053
|
+
);
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
4057
|
+
try {
|
|
4058
|
+
return await fn();
|
|
4059
|
+
} catch (err) {
|
|
4060
|
+
const errorCode = err instanceof OwneyError ? err.code : "SDK_AGENT_FAILURE";
|
|
4061
|
+
void reportAgentFailure(apiKey, agentType, errorCode, baseUrl);
|
|
4062
|
+
throw err;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
// src/lib/helpers/withdraw-helper.ts
|
|
4067
|
+
import { parseUnits } from "viem";
|
|
4068
|
+
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
4069
|
+
const target = asset.toUpperCase();
|
|
4070
|
+
return agents.map((agent) => {
|
|
4071
|
+
const agentBalance = aggregated[agent.id];
|
|
4072
|
+
const tokenBalance = agentBalance?.tokens.find(
|
|
4073
|
+
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4074
|
+
);
|
|
4075
|
+
let balance = tokenBalance ? parseUnits(tokenBalance.amount, decimals) : 0n;
|
|
4076
|
+
if (agent.balanceComposition === "tokens-plus-positions") {
|
|
4077
|
+
const chainNameById = {
|
|
4078
|
+
1: "ETHEREUM",
|
|
4079
|
+
8453: "BASE",
|
|
4080
|
+
42161: "ARBITRUM"
|
|
4081
|
+
};
|
|
4082
|
+
const targetChain = chainNameById[chainId];
|
|
4083
|
+
for (const position2 of agentBalance?.positions ?? []) {
|
|
4084
|
+
const positionChain = position2.chain.trim().toUpperCase();
|
|
4085
|
+
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4086
|
+
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4087
|
+
if (position2.amountRaw !== void 0) {
|
|
4088
|
+
try {
|
|
4089
|
+
balance += BigInt(position2.amountRaw);
|
|
4090
|
+
continue;
|
|
4091
|
+
} catch {
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
balance += parseUnits(position2.amount, decimals);
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
return { agent, balance };
|
|
4098
|
+
});
|
|
4099
|
+
}
|
|
4100
|
+
function planProportionalShares(balances, requested, totalAvailable) {
|
|
4101
|
+
const plans = balances.map(({ agent, balance }) => ({
|
|
4102
|
+
agent,
|
|
4103
|
+
balance,
|
|
4104
|
+
planned: totalAvailable > 0n ? balance * requested / totalAvailable : 0n
|
|
4105
|
+
}));
|
|
4106
|
+
const assigned = plans.reduce((s, p) => s + p.planned, 0n);
|
|
4107
|
+
let remainder = requested - assigned;
|
|
4108
|
+
const byHeadroom = [...plans].sort((a, b) => {
|
|
4109
|
+
const diff = b.balance - b.planned - (a.balance - a.planned);
|
|
4110
|
+
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
4111
|
+
});
|
|
4112
|
+
for (const p of byHeadroom) {
|
|
4113
|
+
if (remainder === 0n) break;
|
|
4114
|
+
const headroom = p.balance - p.planned;
|
|
4115
|
+
if (headroom <= 0n) continue;
|
|
4116
|
+
const take = headroom < remainder ? headroom : remainder;
|
|
4117
|
+
p.planned += take;
|
|
4118
|
+
remainder -= take;
|
|
4119
|
+
}
|
|
4120
|
+
return plans;
|
|
4121
|
+
}
|
|
4122
|
+
function planDisabledDrain(disabled, requested) {
|
|
4123
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
4124
|
+
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
4125
|
+
);
|
|
4126
|
+
const plans = [];
|
|
4127
|
+
let remaining = requested;
|
|
4128
|
+
for (const { agent, balance } of sorted) {
|
|
4129
|
+
if (remaining === 0n) {
|
|
4130
|
+
plans.push({ agent, balance, planned: 0n });
|
|
4131
|
+
continue;
|
|
4132
|
+
}
|
|
4133
|
+
const take = balance < remaining ? balance : remaining;
|
|
4134
|
+
plans.push({ agent, balance, planned: take });
|
|
4135
|
+
remaining -= take;
|
|
4136
|
+
}
|
|
4137
|
+
return { plans, remaining };
|
|
4138
|
+
}
|
|
4139
|
+
function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
4140
|
+
const pool = candidatePool ?? plans.slice(fromIndex + 1);
|
|
4141
|
+
const candidates = pool.filter((c) => c.balance - c.planned > 0n);
|
|
4142
|
+
const totalHeadroom = candidates.reduce(
|
|
4143
|
+
(s, c) => s + (c.balance - c.planned),
|
|
4144
|
+
0n
|
|
4145
|
+
);
|
|
4146
|
+
if (totalHeadroom === 0n) return;
|
|
4147
|
+
let distributed = 0n;
|
|
4148
|
+
for (const c of candidates) {
|
|
4149
|
+
const headroom = c.balance - c.planned;
|
|
4150
|
+
const proportional = headroom * amount / totalHeadroom;
|
|
4151
|
+
const give = proportional > headroom ? headroom : proportional;
|
|
4152
|
+
c.planned += give;
|
|
4153
|
+
distributed += give;
|
|
4154
|
+
}
|
|
4155
|
+
let leftover = amount - distributed;
|
|
4156
|
+
for (const c of candidates) {
|
|
4157
|
+
if (leftover === 0n) break;
|
|
4158
|
+
const headroom = c.balance - c.planned;
|
|
4159
|
+
if (headroom <= 0n) continue;
|
|
4160
|
+
const take = headroom < leftover ? headroom : leftover;
|
|
4161
|
+
c.planned += take;
|
|
4162
|
+
leftover -= take;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
function sumWithdrawnAmount(results) {
|
|
4166
|
+
return Object.values(results).reduce((sum, r) => sum + BigInt(r.amount), 0n);
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
// src/lib/helpers/account-apy-helper.ts
|
|
4170
|
+
function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
4171
|
+
if (!tokenSymbol) {
|
|
4172
|
+
const total = Number(balance.totalBalance);
|
|
2285
4173
|
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
2286
4174
|
}
|
|
2287
4175
|
const normalizedToken = tokenSymbol.toUpperCase();
|
|
4176
|
+
const snapshots = balance.assetBalances?.filter(
|
|
4177
|
+
(token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
|
|
4178
|
+
);
|
|
4179
|
+
if (snapshots?.length) {
|
|
4180
|
+
const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
|
|
4181
|
+
if (Number.isFinite(amount)) return Math.max(0, amount);
|
|
4182
|
+
}
|
|
2288
4183
|
return balance.tokens.reduce((total, token) => {
|
|
2289
4184
|
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
2290
4185
|
return total;
|
|
@@ -2326,471 +4221,342 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
2326
4221
|
const cells = agentApys[id].apyByChainAndAsset;
|
|
2327
4222
|
const balance = agentBalances[id] ?? 0;
|
|
2328
4223
|
if (!cells || balance <= 0) continue;
|
|
2329
|
-
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
2330
|
-
if (!perAsset) continue;
|
|
2331
|
-
const chainId = Number(chainKey);
|
|
2332
|
-
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
2333
|
-
const apy = Number(apyValue ?? 0);
|
|
2334
|
-
if (apy === 0) continue;
|
|
2335
|
-
sums[chainId] ??= {};
|
|
2336
|
-
weights[chainId] ??= {};
|
|
2337
|
-
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
2338
|
-
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
const out = {};
|
|
2343
|
-
for (const chainKey of Object.keys(sums)) {
|
|
2344
|
-
const chainId = Number(chainKey);
|
|
2345
|
-
const perAssetOut = {};
|
|
2346
|
-
for (const asset of Object.keys(sums[chainId])) {
|
|
2347
|
-
const w = weights[chainId][asset];
|
|
2348
|
-
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
2349
|
-
}
|
|
2350
|
-
if (Object.keys(perAssetOut).length > 0) {
|
|
2351
|
-
out[chainId] = perAssetOut;
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
return out;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
// src/client.ts
|
|
2358
|
-
import {
|
|
2359
|
-
createPublicClient as createPublicClient2,
|
|
2360
|
-
createWalletClient,
|
|
2361
|
-
custom
|
|
2362
|
-
} from "viem";
|
|
2363
|
-
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
2364
|
-
|
|
2365
|
-
// src/lib/transfer-auth.ts
|
|
2366
|
-
import { bytesToHex } from "viem";
|
|
2367
|
-
var ERC20_META_ABI = [
|
|
2368
|
-
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2369
|
-
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
2370
|
-
];
|
|
2371
|
-
function buildTransferWithAuthorizationTypedData(input) {
|
|
2372
|
-
return {
|
|
2373
|
-
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
2374
|
-
types: {
|
|
2375
|
-
TransferWithAuthorization: [
|
|
2376
|
-
{ name: "from", type: "address" },
|
|
2377
|
-
{ name: "to", type: "address" },
|
|
2378
|
-
{ name: "value", type: "uint256" },
|
|
2379
|
-
{ name: "validAfter", type: "uint256" },
|
|
2380
|
-
{ name: "validBefore", type: "uint256" },
|
|
2381
|
-
{ name: "nonce", type: "bytes32" }
|
|
2382
|
-
]
|
|
2383
|
-
},
|
|
2384
|
-
primaryType: "TransferWithAuthorization",
|
|
2385
|
-
message: input.message
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
async function readTokenMeta(publicClient, token) {
|
|
2389
|
-
const [tokenName, tokenVersion] = await Promise.all([
|
|
2390
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
2391
|
-
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
2392
|
-
]);
|
|
2393
|
-
return { tokenName, tokenVersion };
|
|
2394
|
-
}
|
|
2395
|
-
function randomAuthNonce() {
|
|
2396
|
-
const bytes = new Uint8Array(32);
|
|
2397
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2398
|
-
return bytesToHex(bytes);
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
// src/lib/sponsor-client.ts
|
|
2402
|
-
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2403
|
-
async function postSponsorTransferAuth(input) {
|
|
2404
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2405
|
-
let res;
|
|
2406
|
-
try {
|
|
2407
|
-
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
2408
|
-
method: "POST",
|
|
2409
|
-
headers: {
|
|
2410
|
-
"content-type": "application/json",
|
|
2411
|
-
"x-owney-api-key": input.apiKey
|
|
2412
|
-
},
|
|
2413
|
-
body: JSON.stringify(input.body)
|
|
2414
|
-
});
|
|
2415
|
-
} catch (networkError) {
|
|
2416
|
-
throw new OwneyError(
|
|
2417
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2418
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2419
|
-
{ cause: String(networkError) }
|
|
2420
|
-
);
|
|
2421
|
-
}
|
|
2422
|
-
const text = await res.text();
|
|
2423
|
-
let parsed = null;
|
|
2424
|
-
try {
|
|
2425
|
-
parsed = JSON.parse(text);
|
|
2426
|
-
} catch {
|
|
2427
|
-
}
|
|
2428
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2429
|
-
throw new OwneyError(
|
|
2430
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2431
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2432
|
-
{
|
|
2433
|
-
statusCode: res.status,
|
|
2434
|
-
responseBody: text.slice(0, 500),
|
|
2435
|
-
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
2436
|
-
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
2437
|
-
safeToFallback: res.status === 503
|
|
2438
|
-
}
|
|
2439
|
-
);
|
|
2440
|
-
}
|
|
2441
|
-
return parsed.data;
|
|
2442
|
-
}
|
|
2443
|
-
async function postSponsorPermit2Transfer(input) {
|
|
2444
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2445
|
-
let res;
|
|
2446
|
-
try {
|
|
2447
|
-
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
2448
|
-
method: "POST",
|
|
2449
|
-
headers: {
|
|
2450
|
-
"content-type": "application/json",
|
|
2451
|
-
"x-owney-api-key": input.apiKey
|
|
2452
|
-
},
|
|
2453
|
-
body: JSON.stringify(input.body)
|
|
2454
|
-
});
|
|
2455
|
-
} catch (networkError) {
|
|
2456
|
-
throw new OwneyError(
|
|
2457
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2458
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2459
|
-
{ cause: String(networkError), safeToFallback: false }
|
|
2460
|
-
);
|
|
2461
|
-
}
|
|
2462
|
-
const text = await res.text();
|
|
2463
|
-
let parsed = null;
|
|
2464
|
-
try {
|
|
2465
|
-
parsed = JSON.parse(text);
|
|
2466
|
-
} catch {
|
|
2467
|
-
}
|
|
2468
|
-
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
2469
|
-
throw new OwneyError(
|
|
2470
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2471
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2472
|
-
{
|
|
2473
|
-
statusCode: res.status,
|
|
2474
|
-
responseBody: text.slice(0, 500),
|
|
2475
|
-
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
2476
|
-
}
|
|
2477
|
-
);
|
|
2478
|
-
}
|
|
2479
|
-
return parsed.data;
|
|
2480
|
-
}
|
|
2481
|
-
async function getSponsorRelayerAddress(input) {
|
|
2482
|
-
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
2483
|
-
let res;
|
|
2484
|
-
try {
|
|
2485
|
-
res = await fetch(
|
|
2486
|
-
`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
2487
|
-
{
|
|
2488
|
-
headers: { "x-owney-api-key": input.apiKey }
|
|
2489
|
-
}
|
|
2490
|
-
);
|
|
2491
|
-
} catch (networkError) {
|
|
2492
|
-
throw new OwneyError(
|
|
2493
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2494
|
-
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
2495
|
-
{ cause: String(networkError), safeToFallback: true }
|
|
2496
|
-
);
|
|
2497
|
-
}
|
|
2498
|
-
const text = await res.text();
|
|
2499
|
-
let parsed = null;
|
|
2500
|
-
try {
|
|
2501
|
-
parsed = JSON.parse(text);
|
|
2502
|
-
} catch {
|
|
2503
|
-
}
|
|
2504
|
-
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
2505
|
-
throw new OwneyError(
|
|
2506
|
-
"SPONSOR_REQUEST_FAILED",
|
|
2507
|
-
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
2508
|
-
{
|
|
2509
|
-
statusCode: res.status,
|
|
2510
|
-
responseBody: text.slice(0, 500),
|
|
2511
|
-
safeToFallback: true
|
|
2512
|
-
}
|
|
2513
|
-
);
|
|
2514
|
-
}
|
|
2515
|
-
return parsed.data.relayer;
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
// src/lib/permit2.ts
|
|
2519
|
-
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2520
|
-
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2521
|
-
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2522
|
-
var ERC20_ALLOWANCE_ABI = [
|
|
2523
|
-
{
|
|
2524
|
-
type: "function",
|
|
2525
|
-
name: "allowance",
|
|
2526
|
-
stateMutability: "view",
|
|
2527
|
-
inputs: [
|
|
2528
|
-
{ name: "owner", type: "address" },
|
|
2529
|
-
{ name: "spender", type: "address" }
|
|
2530
|
-
],
|
|
2531
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2532
|
-
},
|
|
2533
|
-
{
|
|
2534
|
-
type: "function",
|
|
2535
|
-
name: "approve",
|
|
2536
|
-
stateMutability: "nonpayable",
|
|
2537
|
-
inputs: [
|
|
2538
|
-
{ name: "spender", type: "address" },
|
|
2539
|
-
{ name: "amount", type: "uint256" }
|
|
2540
|
-
],
|
|
2541
|
-
outputs: [{ name: "", type: "bool" }]
|
|
2542
|
-
},
|
|
2543
|
-
{
|
|
2544
|
-
type: "function",
|
|
2545
|
-
name: "balanceOf",
|
|
2546
|
-
stateMutability: "view",
|
|
2547
|
-
inputs: [{ name: "account", type: "address" }],
|
|
2548
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
4224
|
+
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
4225
|
+
if (!perAsset) continue;
|
|
4226
|
+
const chainId = Number(chainKey);
|
|
4227
|
+
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
4228
|
+
const apy = Number(apyValue ?? 0);
|
|
4229
|
+
if (apy === 0) continue;
|
|
4230
|
+
sums[chainId] ??= {};
|
|
4231
|
+
weights[chainId] ??= {};
|
|
4232
|
+
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
4233
|
+
weights[chainId][asset] = (weights[chainId][asset] ?? 0) + balance;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
2549
4236
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
4237
|
+
const out = {};
|
|
4238
|
+
for (const chainKey of Object.keys(sums)) {
|
|
4239
|
+
const chainId = Number(chainKey);
|
|
4240
|
+
const perAssetOut = {};
|
|
4241
|
+
for (const asset of Object.keys(sums[chainId])) {
|
|
4242
|
+
const w = weights[chainId][asset];
|
|
4243
|
+
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
4244
|
+
}
|
|
4245
|
+
if (Object.keys(perAssetOut).length > 0) {
|
|
4246
|
+
out[chainId] = perAssetOut;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
return out;
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4252
|
+
// src/client.ts
|
|
4253
|
+
import {
|
|
4254
|
+
createPublicClient as createPublicClient4,
|
|
4255
|
+
createWalletClient as createWalletClient3,
|
|
4256
|
+
custom as custom3
|
|
4257
|
+
} from "viem";
|
|
4258
|
+
import { base as base4, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
4259
|
+
|
|
4260
|
+
// src/lib/sponsored-token-batch.ts
|
|
4261
|
+
import {
|
|
4262
|
+
isAddressEqual,
|
|
4263
|
+
keccak256,
|
|
4264
|
+
toBytes
|
|
4265
|
+
} from "viem";
|
|
4266
|
+
|
|
4267
|
+
// src/lib/permit2-batch.ts
|
|
4268
|
+
import { parseAbi as parseAbi2, hashStruct } from "viem";
|
|
4269
|
+
var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
4270
|
+
var PERMIT_BATCH_TYPES = {
|
|
4271
|
+
PermitBatchWitnessTransferFrom: [
|
|
4272
|
+
{ name: "permitted", type: "TokenPermissions[]" },
|
|
4273
|
+
{ name: "spender", type: "address" },
|
|
4274
|
+
{ name: "nonce", type: "uint256" },
|
|
4275
|
+
{ name: "deadline", type: "uint256" },
|
|
4276
|
+
{ name: "witness", type: "Deposit" }
|
|
4277
|
+
],
|
|
4278
|
+
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
4279
|
+
TokenPermissions: [
|
|
4280
|
+
{ name: "token", type: "address" },
|
|
4281
|
+
{ name: "amount", type: "uint256" }
|
|
4282
|
+
]
|
|
4283
|
+
};
|
|
4284
|
+
var PERMIT2_BATCH_ABI = parseAbi2([
|
|
4285
|
+
"struct TokenPermissions { address token; uint256 amount; }",
|
|
4286
|
+
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
4287
|
+
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
4288
|
+
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
4289
|
+
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
4290
|
+
]);
|
|
4291
|
+
function batchPermit(b) {
|
|
4292
|
+
return {
|
|
4293
|
+
permitted: b.transfers.map((t) => ({
|
|
4294
|
+
token: b.token,
|
|
4295
|
+
amount: BigInt(t.amount)
|
|
4296
|
+
})),
|
|
4297
|
+
nonce: BigInt(b.nonce),
|
|
4298
|
+
deadline: BigInt(b.deadline)
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4301
|
+
function batchTypedData(b, spender) {
|
|
2552
4302
|
return {
|
|
2553
4303
|
domain: {
|
|
2554
4304
|
name: "Permit2",
|
|
2555
|
-
chainId:
|
|
2556
|
-
verifyingContract:
|
|
4305
|
+
chainId: b.chainId,
|
|
4306
|
+
verifyingContract: BATCH_PERMIT2_ADDRESS
|
|
2557
4307
|
},
|
|
2558
|
-
types:
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
TokenPermissions: [
|
|
2566
|
-
{ name: "token", type: "address" },
|
|
2567
|
-
{ name: "amount", type: "uint256" }
|
|
2568
|
-
]
|
|
2569
|
-
},
|
|
2570
|
-
primaryType: "PermitTransferFrom",
|
|
2571
|
-
message: input.message
|
|
4308
|
+
types: PERMIT_BATCH_TYPES,
|
|
4309
|
+
primaryType: "PermitBatchWitnessTransferFrom",
|
|
4310
|
+
message: {
|
|
4311
|
+
...batchPermit(b),
|
|
4312
|
+
spender,
|
|
4313
|
+
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
4314
|
+
}
|
|
2572
4315
|
};
|
|
2573
4316
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
4317
|
+
|
|
4318
|
+
// src/lib/sponsored-token-batch.ts
|
|
4319
|
+
var memory = /* @__PURE__ */ new Map();
|
|
4320
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
4321
|
+
var planOf = (transfers) => JSON.stringify(
|
|
4322
|
+
transfers.map((t) => ({
|
|
4323
|
+
to: t.to.toLowerCase(),
|
|
4324
|
+
amount: BigInt(t.amount).toString()
|
|
4325
|
+
}))
|
|
4326
|
+
);
|
|
4327
|
+
function read(key2) {
|
|
4328
|
+
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
2578
4329
|
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
functionName: "allowance",
|
|
2584
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
4330
|
+
function save(key2, body) {
|
|
4331
|
+
const value = JSON.stringify({
|
|
4332
|
+
...body,
|
|
4333
|
+
transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
|
|
2585
4334
|
});
|
|
4335
|
+
if (typeof window === "undefined") memory.set(key2, value);
|
|
4336
|
+
else window.localStorage.setItem(key2, value);
|
|
2586
4337
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2591
|
-
functionName: "balanceOf",
|
|
2592
|
-
args: [owner]
|
|
2593
|
-
});
|
|
4338
|
+
function clear(key2) {
|
|
4339
|
+
if (typeof window === "undefined") memory.delete(key2);
|
|
4340
|
+
else window.localStorage.removeItem(key2);
|
|
2594
4341
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
4342
|
+
function sponsorTokenBatch(i) {
|
|
4343
|
+
const key2 = `owney.token-batch.v1:${keccak256(toBytes(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
|
|
4344
|
+
const plan = planOf(i.transfers);
|
|
4345
|
+
const active = inflight.get(key2);
|
|
4346
|
+
if (active) {
|
|
4347
|
+
if (active.plan !== plan)
|
|
4348
|
+
return Promise.reject(
|
|
4349
|
+
new Error(
|
|
4350
|
+
"A token deposit is already in progress. Wait for its result before depositing again."
|
|
4351
|
+
)
|
|
4352
|
+
);
|
|
4353
|
+
return active.promise;
|
|
4354
|
+
}
|
|
4355
|
+
const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
|
|
4356
|
+
inflight.set(key2, { plan, promise });
|
|
4357
|
+
return promise;
|
|
2604
4358
|
}
|
|
2605
|
-
async function
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
{
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
4359
|
+
async function execute(i, key2, plan) {
|
|
4360
|
+
if (!i.transfers.length || i.transfers.length > 16 || i.transfers.some(
|
|
4361
|
+
(t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
|
|
4362
|
+
) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
|
|
4363
|
+
throw new Error("Invalid token deposit shares.");
|
|
4364
|
+
const send = async (initial) => {
|
|
4365
|
+
let body = initial;
|
|
4366
|
+
save(key2, body);
|
|
4367
|
+
try {
|
|
4368
|
+
if (!body.serializedTransaction) {
|
|
4369
|
+
const prepared = await postSponsorBatchTransfer({
|
|
4370
|
+
apiKey: i.apiKey,
|
|
4371
|
+
baseUrl: i.baseUrl,
|
|
4372
|
+
body
|
|
4373
|
+
});
|
|
4374
|
+
if (!prepared.serializedTransaction || keccak256(prepared.serializedTransaction) !== prepared.txHash)
|
|
4375
|
+
throw new Error(
|
|
4376
|
+
"Sponsorship API did not return a valid prepared transaction."
|
|
4377
|
+
);
|
|
4378
|
+
body = {
|
|
4379
|
+
...body,
|
|
4380
|
+
serializedTransaction: prepared.serializedTransaction
|
|
4381
|
+
};
|
|
4382
|
+
save(key2, body);
|
|
2618
4383
|
}
|
|
2619
|
-
|
|
4384
|
+
const result = await postSponsorBatchTransfer({
|
|
4385
|
+
apiKey: i.apiKey,
|
|
4386
|
+
baseUrl: i.baseUrl,
|
|
4387
|
+
body
|
|
4388
|
+
});
|
|
4389
|
+
if (result.txHash !== keccak256(body.serializedTransaction))
|
|
4390
|
+
throw new Error(
|
|
4391
|
+
"Sponsorship receipt does not match the pending transaction."
|
|
4392
|
+
);
|
|
4393
|
+
clear(key2);
|
|
4394
|
+
return result.txHash;
|
|
4395
|
+
} catch (error) {
|
|
4396
|
+
if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
|
|
4397
|
+
clear(key2);
|
|
4398
|
+
throw error;
|
|
4399
|
+
}
|
|
4400
|
+
};
|
|
4401
|
+
const saved = read(key2);
|
|
4402
|
+
if (saved) {
|
|
4403
|
+
const previous = JSON.parse(saved);
|
|
4404
|
+
if (previous.chainId !== i.chainId || !isAddressEqual(previous.from, i.owner) || !isAddressEqual(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
4405
|
+
throw new Error(
|
|
4406
|
+
"Retry the previous token deposit and agent split first to reconcile its status."
|
|
4407
|
+
);
|
|
4408
|
+
i.onApproved?.();
|
|
4409
|
+
return send({ ...previous, transfers: i.transfers });
|
|
2620
4410
|
}
|
|
2621
|
-
const
|
|
2622
|
-
|
|
4411
|
+
const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
|
|
4412
|
+
const [balance, allowance] = await Promise.all([
|
|
4413
|
+
readErc20Balance(i.pub, i.token, i.owner),
|
|
4414
|
+
readPermit2Allowance(i.pub, i.token, i.owner)
|
|
4415
|
+
]);
|
|
4416
|
+
if (balance < total)
|
|
2623
4417
|
throw new OwneyError(
|
|
2624
|
-
"
|
|
2625
|
-
|
|
2626
|
-
{ expectedChainId: expected, actualChainId: after }
|
|
4418
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
4419
|
+
"Insufficient token balance for this deposit."
|
|
2627
4420
|
);
|
|
2628
|
-
|
|
4421
|
+
if (allowance < total)
|
|
4422
|
+
throw new OwneyError(
|
|
4423
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
4424
|
+
"token deposits need a one-time Permit2 approval."
|
|
4425
|
+
);
|
|
4426
|
+
const relayer = await getSponsorRelayerAddress({
|
|
4427
|
+
apiKey: i.apiKey,
|
|
4428
|
+
baseUrl: i.baseUrl,
|
|
4429
|
+
chainId: i.chainId
|
|
4430
|
+
});
|
|
4431
|
+
const now = (await i.pub.getBlock()).timestamp;
|
|
4432
|
+
const unsigned = {
|
|
4433
|
+
chainId: i.chainId,
|
|
4434
|
+
token: i.token,
|
|
4435
|
+
from: i.owner,
|
|
4436
|
+
transfers: i.transfers,
|
|
4437
|
+
nonce: randomPermit2Nonce().toString(),
|
|
4438
|
+
deadline: (now + 900n).toString()
|
|
4439
|
+
};
|
|
4440
|
+
const signature = await i.wallet.signTypedData({
|
|
4441
|
+
account: i.owner,
|
|
4442
|
+
...batchTypedData(unsigned, relayer)
|
|
4443
|
+
});
|
|
4444
|
+
i.onApproved?.();
|
|
4445
|
+
return send({ ...unsigned, signature });
|
|
2629
4446
|
}
|
|
2630
4447
|
|
|
2631
|
-
// src/lib/sponsored-deposit.ts
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
return async (smartWallet, chainId, amount) => {
|
|
2636
|
-
const cid = chainId;
|
|
2637
|
-
const token = deps.tokenAddressByChain[cid];
|
|
2638
|
-
if (!token) {
|
|
4448
|
+
// src/lib/sponsored-token-deposit.ts
|
|
4449
|
+
function makeSponsoredTokenCallback(deps) {
|
|
4450
|
+
const batch = async (chainId, transfers) => {
|
|
4451
|
+
if (chainId !== 8453 && chainId !== 42161 && chainId !== 1)
|
|
2639
4452
|
throw new OwneyError(
|
|
2640
4453
|
"CHAIN_UNSUPPORTED",
|
|
2641
4454
|
`No sponsored token configured for chain ${chainId}`
|
|
2642
4455
|
);
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
2649
|
-
if (balance < BigInt(amount)) {
|
|
2650
|
-
throw new OwneyError(
|
|
2651
|
-
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
2652
|
-
"Insufficient balance for this deposit.",
|
|
2653
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2654
|
-
);
|
|
2655
|
-
}
|
|
2656
|
-
} catch (err) {
|
|
2657
|
-
if (err instanceof OwneyError) throw err;
|
|
2658
|
-
console.warn(
|
|
2659
|
-
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
2660
|
-
err instanceof Error ? err.message : String(err)
|
|
4456
|
+
const token = deps.tokenAddressByChain[chainId];
|
|
4457
|
+
if (!token)
|
|
4458
|
+
throw new OwneyError(
|
|
4459
|
+
"CHAIN_UNSUPPORTED",
|
|
4460
|
+
`No sponsored token configured for chain ${chainId}`
|
|
2661
4461
|
);
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
const validBefore = BigInt(
|
|
2666
|
-
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
2667
|
-
);
|
|
2668
|
-
const nonce = randomAuthNonce();
|
|
2669
|
-
const typedData = buildTransferWithAuthorizationTypedData({
|
|
2670
|
-
token,
|
|
2671
|
-
chainId: cid,
|
|
2672
|
-
tokenName,
|
|
2673
|
-
tokenVersion,
|
|
2674
|
-
message: {
|
|
2675
|
-
from: deps.ownerAddress,
|
|
2676
|
-
to: smartWallet,
|
|
2677
|
-
value: BigInt(amount),
|
|
2678
|
-
validAfter,
|
|
2679
|
-
validBefore,
|
|
2680
|
-
nonce
|
|
2681
|
-
}
|
|
2682
|
-
});
|
|
2683
|
-
const authSignature = await wallet.signTypedData({
|
|
2684
|
-
account: deps.ownerAddress,
|
|
2685
|
-
...typedData
|
|
2686
|
-
});
|
|
2687
|
-
deps.onApproved?.();
|
|
2688
|
-
const result = await post({
|
|
2689
|
-
baseUrl: deps.baseUrl,
|
|
4462
|
+
const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
|
|
4463
|
+
await ensureWalletOnChain(pub, wallet, chainId);
|
|
4464
|
+
return sponsorTokenBatch({
|
|
2690
4465
|
apiKey: deps.apiKey,
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
nonce,
|
|
2700
|
-
authSignature,
|
|
2701
|
-
tokenName,
|
|
2702
|
-
tokenVersion
|
|
2703
|
-
}
|
|
4466
|
+
baseUrl: deps.baseUrl,
|
|
4467
|
+
owner: deps.ownerAddress,
|
|
4468
|
+
token,
|
|
4469
|
+
chainId,
|
|
4470
|
+
transfers,
|
|
4471
|
+
pub,
|
|
4472
|
+
wallet,
|
|
4473
|
+
onApproved: deps.onApproved
|
|
2704
4474
|
});
|
|
2705
|
-
return result.txHash;
|
|
2706
4475
|
};
|
|
4476
|
+
const callback = makeVerificationAwareDepositCallback(
|
|
4477
|
+
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
4478
|
+
);
|
|
4479
|
+
registerDepositBatch(callback, batch);
|
|
4480
|
+
return callback;
|
|
2707
4481
|
}
|
|
2708
4482
|
|
|
2709
|
-
// src/lib/
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
4483
|
+
// src/lib/agent-deposit-batch.ts
|
|
4484
|
+
function deferred() {
|
|
4485
|
+
let resolve, reject;
|
|
4486
|
+
const promise = new Promise((yes, no) => {
|
|
4487
|
+
resolve = yes;
|
|
4488
|
+
reject = no;
|
|
4489
|
+
});
|
|
4490
|
+
void promise.catch(() => {
|
|
4491
|
+
});
|
|
4492
|
+
return { promise, resolve, reject };
|
|
4493
|
+
}
|
|
4494
|
+
async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
4495
|
+
const funding = deferred();
|
|
4496
|
+
const tasks = [];
|
|
4497
|
+
const transfers = [];
|
|
4498
|
+
try {
|
|
4499
|
+
for (const leg of legs) {
|
|
4500
|
+
const ready = deferred();
|
|
4501
|
+
let entered = false;
|
|
4502
|
+
const callback = makeVerificationAwareDepositCallback(
|
|
4503
|
+
(to, cid, amount, verification) => {
|
|
4504
|
+
if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
|
|
4505
|
+
const error = new Error(
|
|
4506
|
+
"Agent changed its prepared deposit share."
|
|
4507
|
+
);
|
|
4508
|
+
ready.reject(error);
|
|
4509
|
+
throw error;
|
|
4510
|
+
}
|
|
4511
|
+
entered = true;
|
|
4512
|
+
ready.resolve(toBatchTransfer(to, amount, verification));
|
|
4513
|
+
return funding.promise;
|
|
4514
|
+
}
|
|
2721
4515
|
);
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
"Insufficient WETH balance for this deposit.",
|
|
2733
|
-
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
2734
|
-
);
|
|
2735
|
-
}
|
|
2736
|
-
} catch (err) {
|
|
2737
|
-
if (err instanceof OwneyError) throw err;
|
|
2738
|
-
console.warn(
|
|
2739
|
-
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
2740
|
-
err instanceof Error ? err.message : String(err)
|
|
4516
|
+
const task = Promise.resolve().then(() => leg.run(callback));
|
|
4517
|
+
tasks.push(task);
|
|
4518
|
+
void task.then(
|
|
4519
|
+
() => {
|
|
4520
|
+
if (!entered)
|
|
4521
|
+
ready.reject(
|
|
4522
|
+
new Error("Agent did not prepare a deposit transfer.")
|
|
4523
|
+
);
|
|
4524
|
+
},
|
|
4525
|
+
(error) => ready.reject(error)
|
|
2741
4526
|
);
|
|
4527
|
+
transfers.push(await ready.promise);
|
|
4528
|
+
}
|
|
4529
|
+
const txHash = await transfer(chainId, transfers);
|
|
4530
|
+
funding.resolve(txHash);
|
|
4531
|
+
const settled = await Promise.allSettled(tasks);
|
|
4532
|
+
const agentResults = {};
|
|
4533
|
+
const failures = [];
|
|
4534
|
+
for (const [index, result] of settled.entries()) {
|
|
4535
|
+
if (result.status === "fulfilled")
|
|
4536
|
+
agentResults[legs[index].id] = result.value;
|
|
4537
|
+
else failures.push(legs[index].id);
|
|
2742
4538
|
}
|
|
2743
|
-
|
|
2744
|
-
if (allowance < amountWei) {
|
|
4539
|
+
if (failures.length)
|
|
2745
4540
|
throw new OwneyError(
|
|
2746
|
-
"
|
|
2747
|
-
"
|
|
2748
|
-
{
|
|
4541
|
+
"DEPOSIT_PARTIAL_FAILURE",
|
|
4542
|
+
"The deposit was sent to all agents, but some agent updates could not be confirmed. Check activity before depositing again.",
|
|
4543
|
+
{
|
|
4544
|
+
txHash,
|
|
4545
|
+
fundsSubmitted: true,
|
|
4546
|
+
agentResults,
|
|
4547
|
+
failedAgentIds: failures
|
|
4548
|
+
}
|
|
2749
4549
|
);
|
|
2750
|
-
}
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
const nonce = randomPermit2Nonce();
|
|
2757
|
-
const deadline = BigInt(
|
|
2758
|
-
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
2759
|
-
);
|
|
2760
|
-
const typedData = buildPermitTransferFromTypedData({
|
|
2761
|
-
chainId: cid,
|
|
2762
|
-
message: {
|
|
2763
|
-
permitted: { token, amount: amountWei },
|
|
2764
|
-
spender: relayer,
|
|
2765
|
-
nonce,
|
|
2766
|
-
deadline
|
|
2767
|
-
}
|
|
2768
|
-
});
|
|
2769
|
-
const signature = await wallet.signTypedData({
|
|
2770
|
-
account: deps.ownerAddress,
|
|
2771
|
-
...typedData
|
|
2772
|
-
});
|
|
2773
|
-
deps.onApproved?.();
|
|
2774
|
-
const result = await post({
|
|
2775
|
-
baseUrl: deps.baseUrl,
|
|
2776
|
-
apiKey: deps.apiKey,
|
|
2777
|
-
body: {
|
|
2778
|
-
chainId: cid,
|
|
2779
|
-
token,
|
|
2780
|
-
from: deps.ownerAddress,
|
|
2781
|
-
to: smartWallet,
|
|
2782
|
-
amount,
|
|
2783
|
-
nonce: nonce.toString(),
|
|
2784
|
-
deadline: deadline.toString(),
|
|
2785
|
-
signature
|
|
2786
|
-
}
|
|
2787
|
-
});
|
|
2788
|
-
return result.txHash;
|
|
2789
|
-
};
|
|
4550
|
+
return { agentResults };
|
|
4551
|
+
} catch (error) {
|
|
4552
|
+
funding.reject(error);
|
|
4553
|
+
await Promise.allSettled(tasks);
|
|
4554
|
+
throw error;
|
|
4555
|
+
}
|
|
2790
4556
|
}
|
|
2791
4557
|
|
|
2792
4558
|
// src/lib/sponsored-calls-deposit.ts
|
|
2793
|
-
import { encodeFunctionData, erc20Abi, toHex } from "viem";
|
|
4559
|
+
import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi2, toHex } from "viem";
|
|
2794
4560
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
2795
4561
|
var DEFAULT_MAX_POLLS = 30;
|
|
2796
4562
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -2816,7 +4582,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2816
4582
|
}
|
|
2817
4583
|
return new URL(configured, origin).toString();
|
|
2818
4584
|
};
|
|
2819
|
-
|
|
4585
|
+
const batch = async (chainId, transfers) => {
|
|
2820
4586
|
const cid = chainId;
|
|
2821
4587
|
const token = deps.tokenAddressByChain[cid];
|
|
2822
4588
|
if (!token) {
|
|
@@ -2832,11 +4598,42 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2832
4598
|
{ chainId }
|
|
2833
4599
|
);
|
|
2834
4600
|
}
|
|
2835
|
-
const
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
4601
|
+
const calls = transfers.map((transfer) => ({
|
|
4602
|
+
to: token,
|
|
4603
|
+
value: "0x0",
|
|
4604
|
+
data: encodeFunctionData2({
|
|
4605
|
+
abi: erc20Abi2,
|
|
4606
|
+
functionName: "transfer",
|
|
4607
|
+
args: [transfer.to, BigInt(transfer.amount)]
|
|
4608
|
+
})
|
|
4609
|
+
}));
|
|
4610
|
+
let paymasterUrl = absolutePaymasterUrl();
|
|
4611
|
+
for (const transfer of transfers) {
|
|
4612
|
+
const verification = transfer.yieldseeker;
|
|
4613
|
+
if (!verification) continue;
|
|
4614
|
+
if (chainId !== 8453)
|
|
4615
|
+
throw new OwneyError(
|
|
4616
|
+
"CHAIN_UNSUPPORTED",
|
|
4617
|
+
`Yieldseeker sponsorship is not available on chain ${chainId}.`
|
|
4618
|
+
);
|
|
4619
|
+
const { intent } = await postPaymasterIntent({
|
|
4620
|
+
baseUrl: deps.routingApiBaseUrl,
|
|
4621
|
+
apiKey: deps.apiKey,
|
|
4622
|
+
yieldseekerSignature: verification.signature,
|
|
4623
|
+
body: {
|
|
4624
|
+
chainId,
|
|
4625
|
+
token,
|
|
4626
|
+
from: deps.ownerAddress,
|
|
4627
|
+
to: transfer.to,
|
|
4628
|
+
amount: transfer.amount,
|
|
4629
|
+
yieldseekerUserId: verification.userId,
|
|
4630
|
+
yieldseekerAgentId: verification.agentId
|
|
4631
|
+
}
|
|
4632
|
+
});
|
|
4633
|
+
const url = new URL(paymasterUrl);
|
|
4634
|
+
url.searchParams.append("owneyIntent", intent);
|
|
4635
|
+
paymasterUrl = url.toString();
|
|
4636
|
+
}
|
|
2840
4637
|
const sendResult = await deps.provider.request({
|
|
2841
4638
|
method: "wallet_sendCalls",
|
|
2842
4639
|
params: [
|
|
@@ -2844,10 +4641,10 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2844
4641
|
version: "2.0.0",
|
|
2845
4642
|
from: deps.ownerAddress,
|
|
2846
4643
|
chainId: toHex(chainId),
|
|
2847
|
-
atomicRequired:
|
|
2848
|
-
calls
|
|
4644
|
+
atomicRequired: transfers.length > 1,
|
|
4645
|
+
calls,
|
|
2849
4646
|
capabilities: {
|
|
2850
|
-
paymasterService: { url:
|
|
4647
|
+
paymasterService: { url: paymasterUrl }
|
|
2851
4648
|
}
|
|
2852
4649
|
}
|
|
2853
4650
|
]
|
|
@@ -2867,7 +4664,24 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2867
4664
|
params: [callsId]
|
|
2868
4665
|
});
|
|
2869
4666
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
2870
|
-
if (
|
|
4667
|
+
if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
|
|
4668
|
+
throw new OwneyError(
|
|
4669
|
+
"SPONSOR_REQUEST_FAILED",
|
|
4670
|
+
"The sponsored deposit did not complete successfully.",
|
|
4671
|
+
{ chainId, callsId, safeToFallback: false }
|
|
4672
|
+
);
|
|
4673
|
+
}
|
|
4674
|
+
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
4675
|
+
if (status?.receipts?.some(
|
|
4676
|
+
(receipt) => receipt.transactionHash !== txHash
|
|
4677
|
+
))
|
|
4678
|
+
throw new OwneyError(
|
|
4679
|
+
"SPONSORED_CALLS_NO_RECEIPT",
|
|
4680
|
+
"The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
|
|
4681
|
+
{ chainId, callsId }
|
|
4682
|
+
);
|
|
4683
|
+
return txHash;
|
|
4684
|
+
}
|
|
2871
4685
|
if (pollIntervalMs > 0) {
|
|
2872
4686
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2873
4687
|
}
|
|
@@ -2878,6 +4692,11 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2878
4692
|
{ chainId, callsId }
|
|
2879
4693
|
);
|
|
2880
4694
|
};
|
|
4695
|
+
const callback = makeVerificationAwareDepositCallback(
|
|
4696
|
+
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
4697
|
+
);
|
|
4698
|
+
registerDepositBatch(callback, batch);
|
|
4699
|
+
return callback;
|
|
2881
4700
|
}
|
|
2882
4701
|
|
|
2883
4702
|
// src/client.ts
|
|
@@ -2907,7 +4726,7 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
2907
4726
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
2908
4727
|
};
|
|
2909
4728
|
var VIEM_CHAIN2 = {
|
|
2910
|
-
8453:
|
|
4729
|
+
8453: base4,
|
|
2911
4730
|
42161: arbitrum2,
|
|
2912
4731
|
1: mainnet2
|
|
2913
4732
|
};
|
|
@@ -2916,6 +4735,11 @@ var SPONSORED_WETH_BY_CHAIN = {
|
|
|
2916
4735
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
2917
4736
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
2918
4737
|
};
|
|
4738
|
+
var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
|
|
4739
|
+
function sponsoredTokensFor(asset) {
|
|
4740
|
+
if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
|
|
4741
|
+
return SPONSORED_TOKENS_BY_ASSET[asset];
|
|
4742
|
+
}
|
|
2919
4743
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
2920
4744
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
2921
4745
|
}
|
|
@@ -2937,6 +4761,8 @@ var OwneySDK = class {
|
|
|
2937
4761
|
orgAgentConfig;
|
|
2938
4762
|
orgAgentConfigPromise = null;
|
|
2939
4763
|
zyfaiRpcUrls;
|
|
4764
|
+
yieldseekerApiBaseUrl;
|
|
4765
|
+
yieldseekerSiweOrigin;
|
|
2940
4766
|
routingApiBaseUrl;
|
|
2941
4767
|
referralSource;
|
|
2942
4768
|
cachedSponsoredCallback = null;
|
|
@@ -2959,6 +4785,8 @@ var OwneySDK = class {
|
|
|
2959
4785
|
this.apiKey = config.apiKey;
|
|
2960
4786
|
if (config.debug) setOwneyDebug(true);
|
|
2961
4787
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
4788
|
+
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4789
|
+
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
2962
4790
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
2963
4791
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
2964
4792
|
this.referralSource = config.referralSource;
|
|
@@ -2992,6 +4820,7 @@ var OwneySDK = class {
|
|
|
2992
4820
|
* After calling this, `connect()` must be called again before using agent methods.
|
|
2993
4821
|
*/
|
|
2994
4822
|
async disconnect() {
|
|
4823
|
+
this.state = null;
|
|
2995
4824
|
for (const agent of this.agents.values()) {
|
|
2996
4825
|
await agent.disconnect();
|
|
2997
4826
|
}
|
|
@@ -3045,18 +4874,13 @@ var OwneySDK = class {
|
|
|
3045
4874
|
}
|
|
3046
4875
|
return this.state.provider;
|
|
3047
4876
|
}
|
|
3048
|
-
/**
|
|
3049
|
-
* Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
|
|
3050
|
-
* used when the caller omits `depositCallback`. Wraps the connected EIP-1193
|
|
3051
|
-
* provider with viem `custom(provider)` to read token meta and sign the
|
|
3052
|
-
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
3053
|
-
*/
|
|
4877
|
+
/** Builds the default USDC batch callback for the connected wallet. */
|
|
3054
4878
|
getDefaultSponsoredCallback(onApproved) {
|
|
3055
4879
|
if (!onApproved && this.cachedSponsoredCallback)
|
|
3056
4880
|
return this.cachedSponsoredCallback;
|
|
3057
4881
|
const provider = this.requireConnectedProvider();
|
|
3058
4882
|
const owner = this.state.walletAddress;
|
|
3059
|
-
const callback =
|
|
4883
|
+
const callback = makeSponsoredTokenCallback({
|
|
3060
4884
|
apiKey: this.apiKey,
|
|
3061
4885
|
baseUrl: this.routingApiBaseUrl,
|
|
3062
4886
|
ownerAddress: owner,
|
|
@@ -3065,35 +4889,32 @@ var OwneySDK = class {
|
|
|
3065
4889
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3066
4890
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3067
4891
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3068
|
-
getPublicClient: (cid) =>
|
|
4892
|
+
getPublicClient: (cid) => createPublicClient4({
|
|
3069
4893
|
chain: VIEM_CHAIN2[cid],
|
|
3070
|
-
transport:
|
|
4894
|
+
transport: custom3(provider)
|
|
3071
4895
|
}),
|
|
3072
|
-
getWalletClient: (cid) =>
|
|
4896
|
+
getWalletClient: (cid) => createWalletClient3({
|
|
3073
4897
|
account: owner,
|
|
3074
4898
|
chain: VIEM_CHAIN2[cid],
|
|
3075
|
-
transport:
|
|
4899
|
+
transport: custom3(provider)
|
|
3076
4900
|
})
|
|
3077
4901
|
});
|
|
3078
4902
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
3079
4903
|
return callback;
|
|
3080
4904
|
}
|
|
3081
|
-
/**
|
|
3082
|
-
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
3083
|
-
* callback used when the caller omits `depositCallback` for a WETH
|
|
3084
|
-
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
3085
|
-
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
3086
|
-
*/
|
|
4905
|
+
/** Builds the wallet-native sponsored calls callback for compatible paymasters. */
|
|
3087
4906
|
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
3088
4907
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
3089
4908
|
if (!onApproved && cached) return cached;
|
|
3090
4909
|
const provider = this.requireConnectedProvider();
|
|
3091
4910
|
const callback = makeSponsoredCallsCallback({
|
|
4911
|
+
apiKey: this.apiKey,
|
|
4912
|
+
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
3092
4913
|
provider,
|
|
3093
4914
|
ownerAddress: this.state.walletAddress,
|
|
3094
4915
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
3095
4916
|
onApproved,
|
|
3096
|
-
tokenAddressByChain: asset
|
|
4917
|
+
tokenAddressByChain: sponsoredTokensFor(asset)
|
|
3097
4918
|
});
|
|
3098
4919
|
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
3099
4920
|
return callback;
|
|
@@ -3102,14 +4923,14 @@ var OwneySDK = class {
|
|
|
3102
4923
|
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
3103
4924
|
* callback used when the caller omits `depositCallback` for a WETH deposit.
|
|
3104
4925
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
3105
|
-
*
|
|
4926
|
+
* single-use batch authorization instead of an EIP-3009 authorization.
|
|
3106
4927
|
*/
|
|
3107
4928
|
getDefaultWethSponsoredCallback(onApproved) {
|
|
3108
4929
|
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
3109
4930
|
return this.cachedWethSponsoredCallback;
|
|
3110
4931
|
const provider = this.requireConnectedProvider();
|
|
3111
4932
|
const owner = this.state.walletAddress;
|
|
3112
|
-
const callback =
|
|
4933
|
+
const callback = makeSponsoredTokenCallback({
|
|
3113
4934
|
apiKey: this.apiKey,
|
|
3114
4935
|
baseUrl: this.routingApiBaseUrl,
|
|
3115
4936
|
ownerAddress: owner,
|
|
@@ -3118,14 +4939,14 @@ var OwneySDK = class {
|
|
|
3118
4939
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3119
4940
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3120
4941
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3121
|
-
getPublicClient: (cid) =>
|
|
4942
|
+
getPublicClient: (cid) => createPublicClient4({
|
|
3122
4943
|
chain: VIEM_CHAIN2[cid],
|
|
3123
|
-
transport:
|
|
4944
|
+
transport: custom3(provider)
|
|
3124
4945
|
}),
|
|
3125
|
-
getWalletClient: (cid) =>
|
|
4946
|
+
getWalletClient: (cid) => createWalletClient3({
|
|
3126
4947
|
account: owner,
|
|
3127
4948
|
chain: VIEM_CHAIN2[cid],
|
|
3128
|
-
transport:
|
|
4949
|
+
transport: custom3(provider)
|
|
3129
4950
|
})
|
|
3130
4951
|
});
|
|
3131
4952
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -3156,12 +4977,10 @@ var OwneySDK = class {
|
|
|
3156
4977
|
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
3157
4978
|
this.apiKey,
|
|
3158
4979
|
this.routingApiBaseUrl
|
|
3159
|
-
).then(
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
}
|
|
3164
|
-
);
|
|
4980
|
+
).then((config) => {
|
|
4981
|
+
this.orgAgentConfig = config;
|
|
4982
|
+
return config;
|
|
4983
|
+
});
|
|
3165
4984
|
}
|
|
3166
4985
|
return this.orgAgentConfigPromise;
|
|
3167
4986
|
}
|
|
@@ -3201,7 +5020,14 @@ var OwneySDK = class {
|
|
|
3201
5020
|
this.routingApiBaseUrl
|
|
3202
5021
|
);
|
|
3203
5022
|
this.disabledAgents.clear();
|
|
3204
|
-
for (const {
|
|
5023
|
+
for (const {
|
|
5024
|
+
key: key2,
|
|
5025
|
+
agent_type,
|
|
5026
|
+
is_enabled,
|
|
5027
|
+
is_configured
|
|
5028
|
+
} of agentKeys) {
|
|
5029
|
+
const configured = is_configured ?? Boolean(key2);
|
|
5030
|
+
if (!configured) continue;
|
|
3205
5031
|
const agent = this.createAgent(agent_type, key2);
|
|
3206
5032
|
if (!agent) continue;
|
|
3207
5033
|
this.agents.set(agent_type, agent);
|
|
@@ -3225,8 +5051,15 @@ var OwneySDK = class {
|
|
|
3225
5051
|
}
|
|
3226
5052
|
createAgent(agentId, key2) {
|
|
3227
5053
|
if (agentId === "zyfai") {
|
|
5054
|
+
if (!key2) return null;
|
|
3228
5055
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
3229
5056
|
}
|
|
5057
|
+
if (agentId === "yieldseeker") {
|
|
5058
|
+
return new YieldseekerAgent(this.apiKey, {
|
|
5059
|
+
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5060
|
+
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
3230
5063
|
return null;
|
|
3231
5064
|
}
|
|
3232
5065
|
/**
|
|
@@ -3271,9 +5104,10 @@ var OwneySDK = class {
|
|
|
3271
5104
|
* If provided, ALL specified agents must support the chainId or the call
|
|
3272
5105
|
* throws before activating any agent.
|
|
3273
5106
|
*/
|
|
3274
|
-
async activateAgent(chainId, agentId) {
|
|
5107
|
+
async activateAgent(chainId, agentId, asset) {
|
|
3275
5108
|
const state = this.requireState();
|
|
3276
5109
|
await this.ensureAgentsInitialized();
|
|
5110
|
+
this.assertActivationSession(state);
|
|
3277
5111
|
if (agentId !== void 0) {
|
|
3278
5112
|
if (agentId.length === 0) {
|
|
3279
5113
|
throw new OwneyError(
|
|
@@ -3307,7 +5141,7 @@ var OwneySDK = class {
|
|
|
3307
5141
|
this.activeAgents.add(id);
|
|
3308
5142
|
}
|
|
3309
5143
|
state.chainId = chainId;
|
|
3310
|
-
await this.activateAgentsInTurn(agents, state, chainId);
|
|
5144
|
+
await this.activateAgentsInTurn(agents, state, chainId, asset);
|
|
3311
5145
|
return;
|
|
3312
5146
|
}
|
|
3313
5147
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -3328,7 +5162,12 @@ var OwneySDK = class {
|
|
|
3328
5162
|
const enabledCompatible = compatible.filter(
|
|
3329
5163
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
3330
5164
|
);
|
|
3331
|
-
await this.activateAgentsInTurn(enabledCompatible, state, chainId);
|
|
5165
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId, asset);
|
|
5166
|
+
}
|
|
5167
|
+
assertActivationSession(state) {
|
|
5168
|
+
if (this.state !== state) {
|
|
5169
|
+
throw new OwneyError("NOT_CONNECTED", "Wallet connection changed. Please try again.");
|
|
5170
|
+
}
|
|
3332
5171
|
}
|
|
3333
5172
|
/**
|
|
3334
5173
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -3343,26 +5182,51 @@ var OwneySDK = class {
|
|
|
3343
5182
|
* Serializing costs no real wall-clock: the user can only approve one prompt
|
|
3344
5183
|
* at a time anyway.
|
|
3345
5184
|
*
|
|
3346
|
-
*
|
|
3347
|
-
*
|
|
3348
|
-
*
|
|
3349
|
-
* have had a chance to activate.
|
|
5185
|
+
* Stop at the first failure so a canceled sign-in does not open another
|
|
5186
|
+
* agent's wallet prompt. Report any earlier successes for diagnostics; the
|
|
5187
|
+
* app discards the session when the complete sign-in does not succeed.
|
|
3350
5188
|
*/
|
|
3351
|
-
async activateAgentsInTurn(agents, state, chainId) {
|
|
5189
|
+
async activateAgentsInTurn(agents, state, chainId, asset) {
|
|
3352
5190
|
let firstError = null;
|
|
5191
|
+
const activatedAgentIds = [];
|
|
5192
|
+
const failedAgents = [];
|
|
3353
5193
|
for (const agent of agents) {
|
|
5194
|
+
this.assertActivationSession(state);
|
|
3354
5195
|
try {
|
|
3355
|
-
await agent.activateAgent(state, chainId);
|
|
5196
|
+
await agent.activateAgent(state, chainId, asset);
|
|
5197
|
+
this.assertActivationSession(state);
|
|
3356
5198
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
5199
|
+
this.assertActivationSession(state);
|
|
5200
|
+
activatedAgentIds.push(agent.id);
|
|
3357
5201
|
} catch (error) {
|
|
5202
|
+
this.assertActivationSession(state);
|
|
5203
|
+
const message = error !== null && typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : typeof error === "string" ? error : "No activation error message was returned.";
|
|
5204
|
+
failedAgents.push({
|
|
5205
|
+
agentId: agent.id,
|
|
5206
|
+
code: error instanceof OwneyError ? error.code : void 0,
|
|
5207
|
+
message,
|
|
5208
|
+
...error instanceof OwneyError && error.details ? { details: error.details } : {}
|
|
5209
|
+
});
|
|
3358
5210
|
if (firstError === null) {
|
|
3359
5211
|
firstError = error;
|
|
3360
5212
|
} else {
|
|
3361
5213
|
console.error(`activateAgent(${agent.id}) failed:`, error);
|
|
3362
5214
|
}
|
|
5215
|
+
break;
|
|
3363
5216
|
}
|
|
3364
5217
|
}
|
|
3365
|
-
if (firstError
|
|
5218
|
+
if (firstError === null) return;
|
|
5219
|
+
if (activatedAgentIds.length === 0) throw firstError;
|
|
5220
|
+
const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
|
|
5221
|
+
const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
|
|
5222
|
+
const failureMessages = failedAgents.map(
|
|
5223
|
+
({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
|
|
5224
|
+
).join(" ");
|
|
5225
|
+
throw new OwneyError(
|
|
5226
|
+
"AGENT_ACTIVATION_PARTIAL_FAILURE",
|
|
5227
|
+
`${activeNames} activated. ${failureMessages}`,
|
|
5228
|
+
{ activatedAgentIds, failedAgentIds, failures: failedAgents }
|
|
5229
|
+
);
|
|
3366
5230
|
}
|
|
3367
5231
|
/**
|
|
3368
5232
|
* Deposit into a specific agent, or distribute across all eligible agents.
|
|
@@ -3372,7 +5236,8 @@ var OwneySDK = class {
|
|
|
3372
5236
|
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
3373
5237
|
* @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
|
|
3374
5238
|
* When agentId is omitted, this callback is invoked once per eligible agent with that agent's
|
|
3375
|
-
* split amount and smart wallet address
|
|
5239
|
+
* split amount and smart wallet address. Default sponsored deposits batch
|
|
5240
|
+
* all shares into one signature; custom callbacks still run once per agent.
|
|
3376
5241
|
* @param options.agentId - Optional explicit target. Otherwise split equally,
|
|
3377
5242
|
* or fund remaining agents when a recovery deposit cannot meet every minimum.
|
|
3378
5243
|
* @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
|
|
@@ -3457,6 +5322,39 @@ var OwneySDK = class {
|
|
|
3457
5322
|
}
|
|
3458
5323
|
);
|
|
3459
5324
|
}
|
|
5325
|
+
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
5326
|
+
if (!depositCallback && batchTransfer) {
|
|
5327
|
+
return runAgentDepositBatch(
|
|
5328
|
+
chainId,
|
|
5329
|
+
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
5330
|
+
id: agent.id,
|
|
5331
|
+
amount: amount2,
|
|
5332
|
+
run: (callback) => withFailureReporting(
|
|
5333
|
+
this.apiKey,
|
|
5334
|
+
agent.id,
|
|
5335
|
+
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
5336
|
+
this.routingApiBaseUrl
|
|
5337
|
+
)
|
|
5338
|
+
})),
|
|
5339
|
+
async (cid, transfers) => {
|
|
5340
|
+
try {
|
|
5341
|
+
return await batchTransfer(cid, transfers);
|
|
5342
|
+
} catch (error) {
|
|
5343
|
+
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
5344
|
+
throw error;
|
|
5345
|
+
const requiredAmount = transfers.reduce(
|
|
5346
|
+
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
5347
|
+
0n
|
|
5348
|
+
);
|
|
5349
|
+
await this.approvePermit2(
|
|
5350
|
+
asset,
|
|
5351
|
+
requiredAmount
|
|
5352
|
+
);
|
|
5353
|
+
return batchTransfer(cid, transfers);
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
);
|
|
5357
|
+
}
|
|
3460
5358
|
const agentResults = {};
|
|
3461
5359
|
for (const [
|
|
3462
5360
|
index,
|
|
@@ -3496,7 +5394,7 @@ var OwneySDK = class {
|
|
|
3496
5394
|
*
|
|
3497
5395
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
3498
5396
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
3499
|
-
*
|
|
5397
|
+
* token deposit, this is the wallet's first Permit2 deposit for that token. We send
|
|
3500
5398
|
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
3501
5399
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
3502
5400
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
@@ -3533,12 +5431,15 @@ var OwneySDK = class {
|
|
|
3533
5431
|
try {
|
|
3534
5432
|
return await attempt(effectiveCallback);
|
|
3535
5433
|
} catch (error) {
|
|
3536
|
-
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
5434
|
+
if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
3537
5435
|
approvalAttempted = true;
|
|
3538
5436
|
console.warn(
|
|
3539
|
-
"[owney-sdk] First
|
|
5437
|
+
"[owney-sdk] First token deposit: sending one-time Permit2 approval..."
|
|
5438
|
+
);
|
|
5439
|
+
await this.approvePermit2(
|
|
5440
|
+
asset,
|
|
5441
|
+
BigInt(amount)
|
|
3540
5442
|
);
|
|
3541
|
-
await this.approvePermit2();
|
|
3542
5443
|
continue;
|
|
3543
5444
|
}
|
|
3544
5445
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -3576,10 +5477,10 @@ var OwneySDK = class {
|
|
|
3576
5477
|
agent,
|
|
3577
5478
|
amount: i === agents.length - 1 ? perAgent + remainder : perAgent
|
|
3578
5479
|
}));
|
|
3579
|
-
const
|
|
5480
|
+
const valid2 = splits.filter(
|
|
3580
5481
|
(s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
|
|
3581
5482
|
);
|
|
3582
|
-
if (
|
|
5483
|
+
if (valid2.length === agents.length) {
|
|
3583
5484
|
return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
|
|
3584
5485
|
}
|
|
3585
5486
|
const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
|
|
@@ -3598,6 +5499,11 @@ var OwneySDK = class {
|
|
|
3598
5499
|
)
|
|
3599
5500
|
}));
|
|
3600
5501
|
}
|
|
5502
|
+
formatAgentName(agentId) {
|
|
5503
|
+
if (agentId === "zyfai") return "Zyfai";
|
|
5504
|
+
if (agentId === "yieldseeker") return "Yieldseeker";
|
|
5505
|
+
return agentId;
|
|
5506
|
+
}
|
|
3601
5507
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
3602
5508
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
3603
5509
|
const parsedAmount = BigInt(amount);
|
|
@@ -3629,12 +5535,12 @@ var OwneySDK = class {
|
|
|
3629
5535
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
3630
5536
|
);
|
|
3631
5537
|
const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
|
|
3632
|
-
const
|
|
5538
|
+
const position2 = (balance.positions ?? []).find((p) => {
|
|
3633
5539
|
const positionChain = p.chain.trim().toUpperCase();
|
|
3634
5540
|
const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
|
|
3635
5541
|
return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
|
|
3636
5542
|
});
|
|
3637
|
-
return !!token && Number(token.amount) > 0 || !!
|
|
5543
|
+
return !!token && Number(token.amount) > 0 || !!position2;
|
|
3638
5544
|
} catch (error) {
|
|
3639
5545
|
if (requireReliableRead) {
|
|
3640
5546
|
throw new OwneyError(
|
|
@@ -3749,6 +5655,10 @@ var OwneySDK = class {
|
|
|
3749
5655
|
}
|
|
3750
5656
|
const requested = BigInt(amount);
|
|
3751
5657
|
const aggregated = await this.getBalances();
|
|
5658
|
+
const unavailableAgents = eligibleAgents.filter(
|
|
5659
|
+
(agent) => !(agent.id in aggregated.agentBalances)
|
|
5660
|
+
);
|
|
5661
|
+
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
3752
5662
|
const balances = projectAgentBalancesForAsset(
|
|
3753
5663
|
eligibleAgents,
|
|
3754
5664
|
aggregated.agentBalances,
|
|
@@ -3757,7 +5667,18 @@ var OwneySDK = class {
|
|
|
3757
5667
|
assetInfo.decimals
|
|
3758
5668
|
);
|
|
3759
5669
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
3760
|
-
if (totalAvailable
|
|
5670
|
+
if (totalAvailable === 0n && unavailableAgents.length > 0) {
|
|
5671
|
+
throw new OwneyError(
|
|
5672
|
+
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5673
|
+
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
5674
|
+
{
|
|
5675
|
+
asset,
|
|
5676
|
+
unavailableAgents: unavailableAgentIds,
|
|
5677
|
+
agentErrors: aggregated.agentErrors
|
|
5678
|
+
}
|
|
5679
|
+
);
|
|
5680
|
+
}
|
|
5681
|
+
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
3761
5682
|
throw new OwneyError(
|
|
3762
5683
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3763
5684
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -3768,6 +5689,7 @@ var OwneySDK = class {
|
|
|
3768
5689
|
}
|
|
3769
5690
|
);
|
|
3770
5691
|
}
|
|
5692
|
+
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
3771
5693
|
const disabledBalances = balances.filter(
|
|
3772
5694
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
3773
5695
|
);
|
|
@@ -3776,7 +5698,7 @@ var OwneySDK = class {
|
|
|
3776
5698
|
);
|
|
3777
5699
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
3778
5700
|
disabledBalances,
|
|
3779
|
-
|
|
5701
|
+
plannedTarget
|
|
3780
5702
|
);
|
|
3781
5703
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
3782
5704
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -3786,7 +5708,9 @@ var OwneySDK = class {
|
|
|
3786
5708
|
}));
|
|
3787
5709
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
3788
5710
|
const results = {};
|
|
3789
|
-
const agentErrors = {
|
|
5711
|
+
const agentErrors = {
|
|
5712
|
+
...aggregated.agentErrors ?? {}
|
|
5713
|
+
};
|
|
3790
5714
|
for (let i = 0; i < plans.length; i++) {
|
|
3791
5715
|
const p = plans[i];
|
|
3792
5716
|
if (p.planned === 0n) continue;
|
|
@@ -3833,7 +5757,8 @@ var OwneySDK = class {
|
|
|
3833
5757
|
requested: amount,
|
|
3834
5758
|
withdrawn: withdrawn.toString(),
|
|
3835
5759
|
partialResults: results,
|
|
3836
|
-
agentErrors
|
|
5760
|
+
agentErrors,
|
|
5761
|
+
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
3837
5762
|
}
|
|
3838
5763
|
);
|
|
3839
5764
|
}
|
|
@@ -3851,7 +5776,10 @@ var OwneySDK = class {
|
|
|
3851
5776
|
if (agentId) {
|
|
3852
5777
|
const agent = this.getAgent(agentId);
|
|
3853
5778
|
const result = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3854
|
-
return
|
|
5779
|
+
return {
|
|
5780
|
+
...result,
|
|
5781
|
+
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
5782
|
+
};
|
|
3855
5783
|
}
|
|
3856
5784
|
let totalBalance = 0;
|
|
3857
5785
|
const results = {};
|
|
@@ -3859,7 +5787,13 @@ var OwneySDK = class {
|
|
|
3859
5787
|
const balanceResults = await Promise.allSettled(
|
|
3860
5788
|
entries.map(async ([id, agent]) => {
|
|
3861
5789
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
3862
|
-
return [
|
|
5790
|
+
return [
|
|
5791
|
+
id,
|
|
5792
|
+
{
|
|
5793
|
+
...b,
|
|
5794
|
+
balanceComposition: agent.balanceComposition ?? "tokens-include-positions"
|
|
5795
|
+
}
|
|
5796
|
+
];
|
|
3863
5797
|
})
|
|
3864
5798
|
);
|
|
3865
5799
|
let successCount = 0;
|
|
@@ -3881,6 +5815,7 @@ var OwneySDK = class {
|
|
|
3881
5815
|
const retryDelay = rateLimitDelay(reason);
|
|
3882
5816
|
if (retryDelay !== void 0) agentRetryAt[agentId2] = Date.now() + retryDelay;
|
|
3883
5817
|
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
5818
|
+
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
|
|
3884
5819
|
}
|
|
3885
5820
|
if (successCount === 0) {
|
|
3886
5821
|
throw new OwneyError(
|
|
@@ -4059,7 +5994,10 @@ var OwneySDK = class {
|
|
|
4059
5994
|
Promise.all(
|
|
4060
5995
|
entries.map(async ([id, agent]) => {
|
|
4061
5996
|
const b = await this.readAgent(agent, "balances", () => agent.getBalances(state, chainId));
|
|
4062
|
-
return [
|
|
5997
|
+
return [
|
|
5998
|
+
id,
|
|
5999
|
+
balanceForApyScope(b, chainId, tokenSymbol)
|
|
6000
|
+
];
|
|
4063
6001
|
})
|
|
4064
6002
|
)
|
|
4065
6003
|
]);
|
|
@@ -4220,43 +6158,44 @@ var OwneySDK = class {
|
|
|
4220
6158
|
return pending;
|
|
4221
6159
|
}
|
|
4222
6160
|
/**
|
|
4223
|
-
*
|
|
4224
|
-
*
|
|
4225
|
-
*
|
|
4226
|
-
*
|
|
4227
|
-
*
|
|
6161
|
+
* User-paid approval of Permit2 on the selected token for the active chain.
|
|
6162
|
+
* Approves exactly the pending deposit amount. Another approval is required
|
|
6163
|
+
* for a later deposit once this allowance has been consumed. Resolves after
|
|
6164
|
+
* one confirmation so the subsequent deposit attempt sees the new allowance.
|
|
6165
|
+
*
|
|
6166
|
+
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
4228
6167
|
* @returns the approval transaction hash.
|
|
4229
6168
|
*/
|
|
4230
|
-
async approvePermit2(asset = "WETH") {
|
|
4231
|
-
void asset;
|
|
6169
|
+
async approvePermit2(asset = "WETH", requiredAmount = 0n) {
|
|
4232
6170
|
const state = this.requireState();
|
|
4233
6171
|
const chainId = this.requireChainId();
|
|
4234
|
-
this.
|
|
4235
|
-
const token =
|
|
6172
|
+
this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
|
|
6173
|
+
const token = sponsoredTokensFor(asset)[chainId];
|
|
4236
6174
|
if (!token) {
|
|
4237
6175
|
throw new OwneyError(
|
|
4238
6176
|
"CHAIN_UNSUPPORTED",
|
|
4239
|
-
`No sponsored
|
|
6177
|
+
`No sponsored token on chain ${chainId}`
|
|
4240
6178
|
);
|
|
4241
6179
|
}
|
|
4242
6180
|
const provider = this.requireConnectedProvider();
|
|
4243
|
-
const
|
|
6181
|
+
const publicClient = createPublicClient4({
|
|
6182
|
+
chain: VIEM_CHAIN2[chainId],
|
|
6183
|
+
transport: custom3(provider)
|
|
6184
|
+
});
|
|
6185
|
+
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6186
|
+
const wallet = createWalletClient3({
|
|
4244
6187
|
account: state.walletAddress,
|
|
4245
6188
|
chain: VIEM_CHAIN2[chainId],
|
|
4246
|
-
transport:
|
|
6189
|
+
transport: custom3(provider)
|
|
4247
6190
|
});
|
|
4248
6191
|
const hash = await wallet.writeContract({
|
|
4249
6192
|
address: token,
|
|
4250
6193
|
abi: ERC20_ALLOWANCE_ABI,
|
|
4251
6194
|
functionName: "approve",
|
|
4252
|
-
args: [PERMIT2_ADDRESS,
|
|
6195
|
+
args: [PERMIT2_ADDRESS, approvalAmount],
|
|
4253
6196
|
account: state.walletAddress,
|
|
4254
6197
|
chain: VIEM_CHAIN2[chainId]
|
|
4255
6198
|
});
|
|
4256
|
-
const publicClient = createPublicClient2({
|
|
4257
|
-
chain: VIEM_CHAIN2[chainId],
|
|
4258
|
-
transport: custom(provider)
|
|
4259
|
-
});
|
|
4260
6199
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4261
6200
|
hash,
|
|
4262
6201
|
confirmations: 1
|
|
@@ -4292,7 +6231,9 @@ var OwneySDK = class {
|
|
|
4292
6231
|
return this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
4293
6232
|
}
|
|
4294
6233
|
const results = {};
|
|
4295
|
-
const agentEntries = [...this.agents.entries()]
|
|
6234
|
+
const agentEntries = [...this.agents.entries()].filter(
|
|
6235
|
+
([id]) => !this.isAgentDisabled(id)
|
|
6236
|
+
);
|
|
4296
6237
|
const apyResults = await Promise.all(
|
|
4297
6238
|
agentEntries.map(async ([id, agent]) => {
|
|
4298
6239
|
const apy = await this.readAgent(agent, "agentApy", () => agent.getAgentApy(days, agentOptions), { days, ...agentOptions });
|
|
@@ -4367,13 +6308,13 @@ var OwneySDK = class {
|
|
|
4367
6308
|
};
|
|
4368
6309
|
|
|
4369
6310
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
4370
|
-
import { getAddress } from "viem";
|
|
4371
|
-
import { SiweMessage } from "siwe";
|
|
6311
|
+
import { getAddress as getAddress3 } from "viem";
|
|
6312
|
+
import { SiweMessage as SiweMessage2 } from "siwe";
|
|
4372
6313
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
4373
6314
|
|
|
4374
6315
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
4375
|
-
var
|
|
4376
|
-
var
|
|
6316
|
+
var KEY_PREFIX4 = "owney.siwx.session";
|
|
6317
|
+
var storage4 = () => {
|
|
4377
6318
|
if (typeof window === "undefined") return null;
|
|
4378
6319
|
try {
|
|
4379
6320
|
return window.localStorage;
|
|
@@ -4381,8 +6322,8 @@ var storage2 = () => {
|
|
|
4381
6322
|
return null;
|
|
4382
6323
|
}
|
|
4383
6324
|
};
|
|
4384
|
-
var
|
|
4385
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
6325
|
+
var buildKey3 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
|
|
6326
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
|
|
4386
6327
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
4387
6328
|
var readLegacySiwxSession = (store, address) => {
|
|
4388
6329
|
if (!store) return null;
|
|
@@ -4413,17 +6354,17 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
4413
6354
|
};
|
|
4414
6355
|
var readSiwxSession = (address, chainId) => {
|
|
4415
6356
|
if (typeof window === "undefined") return null;
|
|
4416
|
-
const key2 =
|
|
4417
|
-
const store =
|
|
4418
|
-
let
|
|
6357
|
+
const key2 = buildKey3(address);
|
|
6358
|
+
const store = storage4();
|
|
6359
|
+
let raw2 = null;
|
|
4419
6360
|
try {
|
|
4420
|
-
|
|
6361
|
+
raw2 = store?.getItem(key2) ?? null;
|
|
4421
6362
|
} catch {
|
|
4422
|
-
|
|
6363
|
+
raw2 = null;
|
|
4423
6364
|
}
|
|
4424
|
-
if (
|
|
6365
|
+
if (raw2) {
|
|
4425
6366
|
try {
|
|
4426
|
-
return JSON.parse(
|
|
6367
|
+
return JSON.parse(raw2);
|
|
4427
6368
|
} catch {
|
|
4428
6369
|
memorySiwxSessions.delete(key2);
|
|
4429
6370
|
try {
|
|
@@ -4442,18 +6383,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
4442
6383
|
};
|
|
4443
6384
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
4444
6385
|
if (typeof window === "undefined") return;
|
|
4445
|
-
const key2 =
|
|
6386
|
+
const key2 = buildKey3(address);
|
|
4446
6387
|
memorySiwxSessions.set(key2, session);
|
|
4447
|
-
const store =
|
|
6388
|
+
const store = storage4();
|
|
4448
6389
|
try {
|
|
4449
6390
|
store?.setItem(key2, JSON.stringify(session));
|
|
4450
6391
|
} catch {
|
|
4451
6392
|
}
|
|
4452
6393
|
};
|
|
4453
6394
|
var clearSiwxSession = (address, _chainId) => {
|
|
4454
|
-
const key2 =
|
|
6395
|
+
const key2 = buildKey3(address);
|
|
4455
6396
|
memorySiwxSessions.delete(key2);
|
|
4456
|
-
const store =
|
|
6397
|
+
const store = storage4();
|
|
4457
6398
|
try {
|
|
4458
6399
|
store?.removeItem(key2);
|
|
4459
6400
|
} catch {
|
|
@@ -4493,8 +6434,8 @@ function buildSIWXConfig(deps) {
|
|
|
4493
6434
|
statement: STATEMENT,
|
|
4494
6435
|
issuedAt,
|
|
4495
6436
|
toString() {
|
|
4496
|
-
return new
|
|
4497
|
-
address:
|
|
6437
|
+
return new SiweMessage2({
|
|
6438
|
+
address: getAddress3(accountAddress),
|
|
4498
6439
|
chainId: numericChainId(chainId),
|
|
4499
6440
|
domain,
|
|
4500
6441
|
uri,
|
|
@@ -4536,7 +6477,7 @@ function buildSIWXConfig(deps) {
|
|
|
4536
6477
|
const persistSession = async (session) => {
|
|
4537
6478
|
const address = session.data.accountAddress;
|
|
4538
6479
|
const id = numericChainId(session.data.chainId);
|
|
4539
|
-
const message = new
|
|
6480
|
+
const message = new SiweMessage2(session.message);
|
|
4540
6481
|
const login = await post("/auth/login", {
|
|
4541
6482
|
message,
|
|
4542
6483
|
signature: session.signature,
|
|
@@ -4585,6 +6526,7 @@ export {
|
|
|
4585
6526
|
NotConnectedError,
|
|
4586
6527
|
OwneyError,
|
|
4587
6528
|
OwneySDK,
|
|
6529
|
+
YieldseekerAgent,
|
|
4588
6530
|
createOwneySIWX,
|
|
4589
6531
|
setOwneyDebug
|
|
4590
6532
|
};
|