@owney/sdk 0.7.25-beta.4 → 0.7.26-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 +7 -9
- package/dist/index.cjs +1500 -2513
- package/dist/index.d.cts +415 -146
- package/dist/index.d.ts +415 -146
- package/dist/index.js +1492 -2520
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,32 +26,12 @@ __export(index_exports, {
|
|
|
26
26
|
NotConnectedError: () => NotConnectedError,
|
|
27
27
|
OwneyError: () => OwneyError,
|
|
28
28
|
OwneySDK: () => OwneySDK,
|
|
29
|
-
YieldseekerAgent: () => YieldseekerAgent,
|
|
30
29
|
createOwneySIWX: () => createOwneySIWX,
|
|
30
|
+
listPendingSwaps: () => listOrders,
|
|
31
31
|
setOwneyDebug: () => setOwneyDebug
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(index_exports);
|
|
34
34
|
|
|
35
|
-
// src/lib/deposit-batch-callback.ts
|
|
36
|
-
var batchCallbacks = /* @__PURE__ */ new WeakMap();
|
|
37
|
-
var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
|
|
38
|
-
function toBatchTransfer(to, amount, verification) {
|
|
39
|
-
return {
|
|
40
|
-
to,
|
|
41
|
-
amount,
|
|
42
|
-
...verification ? {
|
|
43
|
-
yieldseeker: {
|
|
44
|
-
signature: verification.signature,
|
|
45
|
-
userId: verification.userId,
|
|
46
|
-
agentId: verification.yieldseekerAgentId
|
|
47
|
-
}
|
|
48
|
-
} : {}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function registerDepositBatch(callback, transfer) {
|
|
52
|
-
batchCallbacks.set(callback, transfer);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
35
|
// src/errors.ts
|
|
56
36
|
var OwneyError = class extends Error {
|
|
57
37
|
code;
|
|
@@ -331,18 +311,18 @@ function tokenDecimals(symbol, explicit) {
|
|
|
331
311
|
return explicit;
|
|
332
312
|
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
333
313
|
}
|
|
334
|
-
function mapDeposit(
|
|
314
|
+
function mapDeposit(raw) {
|
|
335
315
|
return {
|
|
336
|
-
txHash:
|
|
337
|
-
smartWallet:
|
|
338
|
-
amount:
|
|
316
|
+
txHash: raw.txHash,
|
|
317
|
+
smartWallet: raw.smartWallet,
|
|
318
|
+
amount: raw.amount
|
|
339
319
|
};
|
|
340
320
|
}
|
|
341
|
-
function mapWithdraw(
|
|
321
|
+
function mapWithdraw(raw) {
|
|
342
322
|
return {
|
|
343
|
-
txHash:
|
|
344
|
-
type:
|
|
345
|
-
amount:
|
|
323
|
+
txHash: raw.txHash,
|
|
324
|
+
type: raw.type,
|
|
325
|
+
amount: raw.amount
|
|
346
326
|
};
|
|
347
327
|
}
|
|
348
328
|
var CHAIN_ID_TO_NAME = {
|
|
@@ -361,10 +341,10 @@ function resolveChainId(chain) {
|
|
|
361
341
|
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
362
342
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
363
343
|
}
|
|
364
|
-
function mapPendingAllocations(
|
|
365
|
-
if (!Array.isArray(
|
|
344
|
+
function mapPendingAllocations(raw) {
|
|
345
|
+
if (!Array.isArray(raw)) return void 0;
|
|
366
346
|
const pending = [];
|
|
367
|
-
for (const entry of
|
|
347
|
+
for (const entry of raw) {
|
|
368
348
|
if (typeof entry !== "object" || entry === null) continue;
|
|
369
349
|
const e = entry;
|
|
370
350
|
if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
|
|
@@ -387,8 +367,8 @@ function mapPendingAllocations(raw2) {
|
|
|
387
367
|
}
|
|
388
368
|
return pending.length > 0 ? pending : void 0;
|
|
389
369
|
}
|
|
390
|
-
function mapBalances(
|
|
391
|
-
const portfolio =
|
|
370
|
+
function mapBalances(raw, _chainId, smartWallet) {
|
|
371
|
+
const portfolio = raw.portfolio;
|
|
392
372
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
393
373
|
let totalBalance = 0;
|
|
394
374
|
const tokens = [];
|
|
@@ -457,8 +437,8 @@ function sumTokenValues(tokens) {
|
|
|
457
437
|
function sumTokenEarnings(tokens) {
|
|
458
438
|
return Object.values(tokens).reduce((sum, val) => sum + Number(val), 0);
|
|
459
439
|
}
|
|
460
|
-
function mapEarnings(
|
|
461
|
-
const totalEarningsByChain =
|
|
440
|
+
function mapEarnings(raw, smartWallet) {
|
|
441
|
+
const totalEarningsByChain = raw.data.totalEarningsByChainWithFee ?? raw.data.totalEarningsByChain ?? {};
|
|
462
442
|
const tokens = [];
|
|
463
443
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(
|
|
464
444
|
totalEarningsByChain
|
|
@@ -477,15 +457,15 @@ function mapEarnings(raw2, smartWallet) {
|
|
|
477
457
|
return {
|
|
478
458
|
smartWallet,
|
|
479
459
|
lifetimeEarnings: sumTokenEarnings(
|
|
480
|
-
|
|
460
|
+
raw.data.totalEarningsByTokenWithFee ?? raw.data.totalEarningsByToken
|
|
481
461
|
),
|
|
482
462
|
tokens
|
|
483
463
|
};
|
|
484
464
|
}
|
|
485
|
-
function mapWeightedApyByChain(
|
|
486
|
-
if (!
|
|
465
|
+
function mapWeightedApyByChain(raw) {
|
|
466
|
+
if (!raw) return void 0;
|
|
487
467
|
const out = {};
|
|
488
|
-
for (const [chainKey, tokenApy] of Object.entries(
|
|
468
|
+
for (const [chainKey, tokenApy] of Object.entries(raw)) {
|
|
489
469
|
const chainId = Number(chainKey);
|
|
490
470
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
491
471
|
const perAsset = {};
|
|
@@ -525,8 +505,8 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
|
525
505
|
}
|
|
526
506
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
527
507
|
}
|
|
528
|
-
function mapApyHistory(
|
|
529
|
-
const history = Object.entries(
|
|
508
|
+
function mapApyHistory(raw, chainId, tokenSymbol) {
|
|
509
|
+
const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
|
|
530
510
|
date,
|
|
531
511
|
apy: rawPoolApyForChain(entry, chainId, tokenSymbol),
|
|
532
512
|
// Provider position balances are treated as decimal amounts of the
|
|
@@ -542,9 +522,9 @@ function mapApyHistory(raw2, chainId, tokenSymbol) {
|
|
|
542
522
|
} : {}
|
|
543
523
|
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
544
524
|
return {
|
|
545
|
-
walletAddress:
|
|
546
|
-
weightedApyAfterFee:
|
|
547
|
-
apyByChainAndAsset: mapWeightedApyByChain(
|
|
525
|
+
walletAddress: raw.walletAddress,
|
|
526
|
+
weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
|
|
527
|
+
apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
|
|
548
528
|
history
|
|
549
529
|
};
|
|
550
530
|
}
|
|
@@ -640,23 +620,23 @@ function mapEntries(rawEntries, chainId) {
|
|
|
640
620
|
};
|
|
641
621
|
});
|
|
642
622
|
}
|
|
643
|
-
function mapUserProfile(
|
|
623
|
+
function mapUserProfile(raw, userAddress) {
|
|
644
624
|
return {
|
|
645
625
|
address: userAddress,
|
|
646
|
-
smartWallet:
|
|
647
|
-
chains:
|
|
648
|
-
strategy:
|
|
649
|
-
hasActiveSessionKey:
|
|
650
|
-
protocols:
|
|
651
|
-
splitting:
|
|
652
|
-
minSplits:
|
|
626
|
+
smartWallet: raw.smartWallet || "",
|
|
627
|
+
chains: raw.chains || [],
|
|
628
|
+
strategy: raw.strategy,
|
|
629
|
+
hasActiveSessionKey: raw.hasActiveSessionKey || false,
|
|
630
|
+
protocols: raw.protocols || [],
|
|
631
|
+
splitting: raw.splitting,
|
|
632
|
+
minSplits: raw.minSplits
|
|
653
633
|
};
|
|
654
634
|
}
|
|
655
|
-
function mapApyByStrategy(
|
|
635
|
+
function mapApyByStrategy(raw) {
|
|
656
636
|
const apyPerAsset = {};
|
|
657
637
|
let apySum = 0;
|
|
658
638
|
let apyCount = 0;
|
|
659
|
-
for (const entry of
|
|
639
|
+
for (const entry of raw.data) {
|
|
660
640
|
const supported = SupportedAssets.find(
|
|
661
641
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
662
642
|
);
|
|
@@ -768,9 +748,9 @@ function netDeltaForSnapshot(entry, chainId, asset) {
|
|
|
768
748
|
debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
|
|
769
749
|
return gross;
|
|
770
750
|
}
|
|
771
|
-
function mapDailyEarnings(
|
|
751
|
+
function mapDailyEarnings(raw, chainId, tokenSymbol) {
|
|
772
752
|
const wanted = tokenSymbol?.toUpperCase();
|
|
773
|
-
const snapshots = [...
|
|
753
|
+
const snapshots = [...raw.data ?? []].sort(
|
|
774
754
|
(a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
|
|
775
755
|
);
|
|
776
756
|
const byAsset = /* @__PURE__ */ new Map();
|
|
@@ -785,7 +765,7 @@ function mapDailyEarnings(raw2, chainId, tokenSymbol) {
|
|
|
785
765
|
}
|
|
786
766
|
}
|
|
787
767
|
const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
|
|
788
|
-
return { walletAddress:
|
|
768
|
+
return { walletAddress: raw.walletAddress, chainId, assets };
|
|
789
769
|
}
|
|
790
770
|
|
|
791
771
|
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
@@ -896,15 +876,15 @@ var readSession = (address, _chainId) => {
|
|
|
896
876
|
if (typeof window === "undefined") return null;
|
|
897
877
|
const key2 = buildKey(address);
|
|
898
878
|
const store = storage();
|
|
899
|
-
let
|
|
879
|
+
let raw = null;
|
|
900
880
|
try {
|
|
901
|
-
|
|
881
|
+
raw = store?.getItem(key2) ?? null;
|
|
902
882
|
} catch {
|
|
903
|
-
|
|
883
|
+
raw = null;
|
|
904
884
|
}
|
|
905
|
-
if (
|
|
885
|
+
if (raw) {
|
|
906
886
|
try {
|
|
907
|
-
const parsed = JSON.parse(
|
|
887
|
+
const parsed = JSON.parse(raw);
|
|
908
888
|
if (isFreshSession(parsed)) return parsed;
|
|
909
889
|
} catch {
|
|
910
890
|
}
|
|
@@ -1072,8 +1052,8 @@ function buildChainsByProtocol(protocols, dashboardChainIds) {
|
|
|
1072
1052
|
}
|
|
1073
1053
|
return result;
|
|
1074
1054
|
}
|
|
1075
|
-
function flattenAvailablePools(
|
|
1076
|
-
const byChain =
|
|
1055
|
+
function flattenAvailablePools(raw) {
|
|
1056
|
+
const byChain = raw ?? {};
|
|
1077
1057
|
const names = [];
|
|
1078
1058
|
for (const byToken of Object.values(byChain ?? {})) {
|
|
1079
1059
|
for (const entry of Object.values(byToken ?? {})) {
|
|
@@ -1576,8 +1556,8 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1576
1556
|
const poolResults = await Promise.all(
|
|
1577
1557
|
universe.map(async (protocol) => {
|
|
1578
1558
|
try {
|
|
1579
|
-
const
|
|
1580
|
-
return [protocol.id, flattenAvailablePools(
|
|
1559
|
+
const raw = await this.sdk.getAvailablePools(protocol.id, strategy);
|
|
1560
|
+
return [protocol.id, flattenAvailablePools(raw)];
|
|
1581
1561
|
} catch (error) {
|
|
1582
1562
|
console.warn(
|
|
1583
1563
|
`[zyfai] applyPoolPolicy: getAvailablePools(${protocol.id}) failed, skipping this protocol (non-fatal):`,
|
|
@@ -1643,14 +1623,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1643
1623
|
async readWalletState(ownerAddress) {
|
|
1644
1624
|
try {
|
|
1645
1625
|
const { portfolio } = await this.sdk.getPositions(ownerAddress);
|
|
1646
|
-
const
|
|
1626
|
+
const raw = portfolio;
|
|
1647
1627
|
debugLog("zyfai:onboard", "wallet state from getPositions", {
|
|
1648
|
-
predeployed:
|
|
1649
|
-
hasActiveSessionKey:
|
|
1628
|
+
predeployed: raw?.predeployed,
|
|
1629
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1650
1630
|
});
|
|
1651
1631
|
return {
|
|
1652
|
-
predeployed:
|
|
1653
|
-
hasActiveSessionKey:
|
|
1632
|
+
predeployed: raw?.predeployed,
|
|
1633
|
+
hasActiveSessionKey: raw?.hasActiveSessionKey
|
|
1654
1634
|
};
|
|
1655
1635
|
} catch (error) {
|
|
1656
1636
|
console.warn(
|
|
@@ -1936,14 +1916,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1936
1916
|
return { txHash, smartWallet, amount };
|
|
1937
1917
|
}
|
|
1938
1918
|
await this.ensureWalletDeployed(this.getAddress(), validChainId);
|
|
1939
|
-
const
|
|
1919
|
+
const raw = await this.sdk.depositFunds(
|
|
1940
1920
|
this.getAddress(),
|
|
1941
1921
|
validChainId,
|
|
1942
1922
|
amount,
|
|
1943
1923
|
asset,
|
|
1944
1924
|
"aggressive"
|
|
1945
1925
|
);
|
|
1946
|
-
return mapDeposit(
|
|
1926
|
+
return mapDeposit(raw);
|
|
1947
1927
|
} catch (error) {
|
|
1948
1928
|
throw error;
|
|
1949
1929
|
}
|
|
@@ -1952,27 +1932,27 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1952
1932
|
async withdraw(state, chainId, token, amount) {
|
|
1953
1933
|
const validChainId = isValidChainId(chainId);
|
|
1954
1934
|
await this.ensureConnected(state, validChainId);
|
|
1955
|
-
const
|
|
1935
|
+
const raw = await this.sdk.withdrawFunds(
|
|
1956
1936
|
this.getAddress(),
|
|
1957
1937
|
validChainId,
|
|
1958
1938
|
amount,
|
|
1959
1939
|
token
|
|
1960
1940
|
);
|
|
1961
|
-
if (!
|
|
1941
|
+
if (!raw.success) {
|
|
1962
1942
|
throw new OwneyError(
|
|
1963
1943
|
"WITHDRAW_FAILED",
|
|
1964
|
-
|
|
1965
|
-
{ chainId: validChainId, token, amount, response:
|
|
1944
|
+
raw.message || "Zyfai withdraw failed.",
|
|
1945
|
+
{ chainId: validChainId, token, amount, response: raw },
|
|
1966
1946
|
this.id
|
|
1967
1947
|
);
|
|
1968
1948
|
}
|
|
1969
|
-
return mapWithdraw(
|
|
1949
|
+
return mapWithdraw(raw);
|
|
1970
1950
|
}
|
|
1971
1951
|
// --- IAgent: Portfolio reads ---
|
|
1972
1952
|
async getBalances(state, chainId) {
|
|
1973
1953
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
1974
|
-
const
|
|
1975
|
-
return mapBalances(
|
|
1954
|
+
const raw = await this.sdk.getPortfolio(this.getAddress());
|
|
1955
|
+
return mapBalances(raw, validChainId, smartWallet);
|
|
1976
1956
|
}
|
|
1977
1957
|
earningsKey(state, chainId, smartWallet) {
|
|
1978
1958
|
return JSON.stringify([
|
|
@@ -1985,11 +1965,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1985
1965
|
const existing = this.earningsReads.get(key2);
|
|
1986
1966
|
if (existing) return existing;
|
|
1987
1967
|
const generation = this.earningsGeneration;
|
|
1988
|
-
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((
|
|
1968
|
+
const pending = Promise.resolve().then(() => this.sdk.getOnchainEarnings(smartWallet)).then((raw) => {
|
|
1989
1969
|
if (generation === this.earningsGeneration) {
|
|
1990
|
-
this.earningsSnapshot = { key: key2, raw
|
|
1970
|
+
this.earningsSnapshot = { key: key2, raw, at: Date.now() };
|
|
1991
1971
|
}
|
|
1992
|
-
return
|
|
1972
|
+
return raw;
|
|
1993
1973
|
}).finally(() => {
|
|
1994
1974
|
if (this.earningsReads.get(key2) === pending)
|
|
1995
1975
|
this.earningsReads.delete(key2);
|
|
@@ -1999,11 +1979,11 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
1999
1979
|
}
|
|
2000
1980
|
async getEarnings(state, chainId) {
|
|
2001
1981
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
2002
|
-
const
|
|
1982
|
+
const raw = await this.readEarnings(
|
|
2003
1983
|
this.earningsKey(state, chainId, smartWallet),
|
|
2004
1984
|
smartWallet
|
|
2005
1985
|
);
|
|
2006
|
-
return mapEarnings(
|
|
1986
|
+
return mapEarnings(raw, smartWallet);
|
|
2007
1987
|
}
|
|
2008
1988
|
async refreshEarnings(state, chainId) {
|
|
2009
1989
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -2030,17 +2010,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2030
2010
|
}
|
|
2031
2011
|
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
2032
2012
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
2033
|
-
const
|
|
2034
|
-
return mapApyHistory(
|
|
2013
|
+
const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
2014
|
+
return mapApyHistory(raw, chainId, tokenSymbol);
|
|
2035
2015
|
}
|
|
2036
2016
|
async getDailyEarnings(state, chainId, days, tokenSymbol) {
|
|
2037
2017
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
2038
2018
|
const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
|
|
2039
|
-
const
|
|
2019
|
+
const raw = await this.sdk.getDailyEarnings(
|
|
2040
2020
|
smartWallet,
|
|
2041
2021
|
start.toISOString().slice(0, 10)
|
|
2042
2022
|
);
|
|
2043
|
-
return mapDailyEarnings(
|
|
2023
|
+
return mapDailyEarnings(raw, chainId, tokenSymbol);
|
|
2044
2024
|
}
|
|
2045
2025
|
/**
|
|
2046
2026
|
* Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
|
|
@@ -2075,7 +2055,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2075
2055
|
const matched = [];
|
|
2076
2056
|
let backendExhausted = false;
|
|
2077
2057
|
for (let i = 0; i < MAX_BACKEND_CALLS && matched.length < limit; i++) {
|
|
2078
|
-
const
|
|
2058
|
+
const raw = await this.sdk.getHistory(smartWallet, validChainId, {
|
|
2079
2059
|
limit: backendPageSize,
|
|
2080
2060
|
offset,
|
|
2081
2061
|
fromDate: options?.fromDate,
|
|
@@ -2086,13 +2066,13 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2086
2066
|
// asset's rows and handing back a page that filters to nothing.
|
|
2087
2067
|
assetType
|
|
2088
2068
|
});
|
|
2089
|
-
|
|
2069
|
+
raw.data.forEach((entry, idx) => {
|
|
2090
2070
|
if (entry.chainId === validChainId) {
|
|
2091
2071
|
matched.push({ entry, rawIdx: offset + idx });
|
|
2092
2072
|
}
|
|
2093
2073
|
});
|
|
2094
|
-
offset +=
|
|
2095
|
-
if (
|
|
2074
|
+
offset += raw.data.length;
|
|
2075
|
+
if (raw.data.length < backendPageSize) {
|
|
2096
2076
|
backendExhausted = true;
|
|
2097
2077
|
break;
|
|
2098
2078
|
}
|
|
@@ -2114,18 +2094,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2114
2094
|
}
|
|
2115
2095
|
async getUserProfile(state, chainId) {
|
|
2116
2096
|
await this.connectAuth(state, chainId);
|
|
2117
|
-
const
|
|
2097
|
+
const raw = await this.sdk.getUserDetails();
|
|
2118
2098
|
debugLog("zyfai:profile", "getUserProfile \u2014 Zyfai reports", {
|
|
2119
2099
|
asset: "USDC (default \u2014 no asset passed)",
|
|
2120
|
-
splitting:
|
|
2121
|
-
minSplits:
|
|
2122
|
-
strategy:
|
|
2123
|
-
chains:
|
|
2124
|
-
protocolCount:
|
|
2125
|
-
hasActiveSessionKey:
|
|
2126
|
-
smartWallet:
|
|
2100
|
+
splitting: raw.splitting,
|
|
2101
|
+
minSplits: raw.minSplits,
|
|
2102
|
+
strategy: raw.strategy,
|
|
2103
|
+
chains: raw.chains,
|
|
2104
|
+
protocolCount: raw.protocols?.length,
|
|
2105
|
+
hasActiveSessionKey: raw.hasActiveSessionKey,
|
|
2106
|
+
smartWallet: raw.smartWallet
|
|
2127
2107
|
});
|
|
2128
|
-
return mapUserProfile(
|
|
2108
|
+
return mapUserProfile(raw, this.connectedAddress);
|
|
2129
2109
|
}
|
|
2130
2110
|
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
2131
2111
|
await this.connectAuth(state, chainId);
|
|
@@ -2144,20 +2124,80 @@ var ZyfaiAgent = class _ZyfaiAgent {
|
|
|
2144
2124
|
}
|
|
2145
2125
|
// --- IAgent: Discovery (no wallet required) ---
|
|
2146
2126
|
async getAgentApy(days, options) {
|
|
2147
|
-
const
|
|
2127
|
+
const raw = await this.sdk.getAPYPerStrategy(
|
|
2148
2128
|
false,
|
|
2149
2129
|
DayFilterMapping[days],
|
|
2150
2130
|
"aggressive",
|
|
2151
2131
|
options?.chainId,
|
|
2152
2132
|
options?.tokenSymbol
|
|
2153
2133
|
);
|
|
2154
|
-
return mapApyByStrategy(
|
|
2134
|
+
return mapApyByStrategy(raw);
|
|
2155
2135
|
}
|
|
2156
2136
|
};
|
|
2157
2137
|
|
|
2158
|
-
// src/
|
|
2159
|
-
var
|
|
2160
|
-
|
|
2138
|
+
// src/lib/routing-api.ts
|
|
2139
|
+
var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2140
|
+
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2141
|
+
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
2142
|
+
try {
|
|
2143
|
+
const res = await fetch(url, {
|
|
2144
|
+
method: "GET",
|
|
2145
|
+
headers: {
|
|
2146
|
+
"Content-Type": "application/json",
|
|
2147
|
+
"x-owney-api-key": `${apiKey}`
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
if (!res.ok) {
|
|
2151
|
+
if (res.status !== 404) {
|
|
2152
|
+
console.warn(
|
|
2153
|
+
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
const json = await res.json();
|
|
2159
|
+
const policy = json.success ? json.data ?? null : null;
|
|
2160
|
+
debugLog(
|
|
2161
|
+
"owney-sdk",
|
|
2162
|
+
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
2163
|
+
policy ?? void 0
|
|
2164
|
+
);
|
|
2165
|
+
return policy;
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
console.warn(
|
|
2168
|
+
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
2169
|
+
error instanceof Error ? error.message : String(error)
|
|
2170
|
+
);
|
|
2171
|
+
return null;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
2175
|
+
const url = `${baseUrl}/api/v1/agent/keys`;
|
|
2176
|
+
const res = await fetch(url, {
|
|
2177
|
+
method: "GET",
|
|
2178
|
+
headers: {
|
|
2179
|
+
"Content-Type": "application/json",
|
|
2180
|
+
"x-owney-api-key": `${apiKey}`
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
if (!res.ok) {
|
|
2184
|
+
const text = await res.text().catch(() => "");
|
|
2185
|
+
throw new OwneyError(
|
|
2186
|
+
"API_ROUTING_ERROR",
|
|
2187
|
+
`Routing API error ${res.status}: ${text}`,
|
|
2188
|
+
{ statusCode: res.status, responseBody: text }
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
const json = await res.json();
|
|
2192
|
+
if (!json.success) {
|
|
2193
|
+
throw new OwneyError(
|
|
2194
|
+
"API_ROUTING_FAILED",
|
|
2195
|
+
`Routing API request failed: ${json.message}`,
|
|
2196
|
+
{ message: json.message }
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
return json.data;
|
|
2200
|
+
}
|
|
2161
2201
|
|
|
2162
2202
|
// src/lib/chain-guard.ts
|
|
2163
2203
|
var CHAIN_NAMES = {
|
|
@@ -2194,132 +2234,169 @@ async function ensureWalletOnChain(pub, wallet, expected) {
|
|
|
2194
2234
|
}
|
|
2195
2235
|
}
|
|
2196
2236
|
|
|
2197
|
-
// src/lib/
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2237
|
+
// src/lib/swap/swap-api.ts
|
|
2238
|
+
async function request(baseUrl, apiKey, path, init) {
|
|
2239
|
+
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
2240
|
+
const res = await fetch(url, {
|
|
2241
|
+
method: init?.method ?? "GET",
|
|
2242
|
+
headers: {
|
|
2243
|
+
"Content-Type": "application/json",
|
|
2244
|
+
"x-owney-api-key": apiKey
|
|
2245
|
+
},
|
|
2246
|
+
...init ? { body: JSON.stringify(init.body) } : {}
|
|
2247
|
+
});
|
|
2248
|
+
if (!res.ok) {
|
|
2249
|
+
const text = await res.text().catch(() => "");
|
|
2250
|
+
if (res.status === 429) {
|
|
2251
|
+
throw new OwneyError(
|
|
2252
|
+
"SWAP_RATE_LIMITED",
|
|
2253
|
+
"Swap provider is rate limiting, retry shortly",
|
|
2254
|
+
{ statusCode: res.status }
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
if (res.status === 403) {
|
|
2258
|
+
throw new OwneyError(
|
|
2259
|
+
"SWAP_DISABLED",
|
|
2260
|
+
"Swap is not enabled for this organization",
|
|
2261
|
+
{ statusCode: res.status }
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2216
2264
|
throw new OwneyError(
|
|
2217
|
-
"
|
|
2218
|
-
`
|
|
2219
|
-
{
|
|
2265
|
+
"SWAP_REQUEST_FAILED",
|
|
2266
|
+
`Swap API error ${res.status}: ${text}`,
|
|
2267
|
+
{ statusCode: res.status, responseBody: text }
|
|
2220
2268
|
);
|
|
2221
2269
|
}
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
try {
|
|
2225
|
-
parsed = JSON.parse(text);
|
|
2226
|
-
} catch {
|
|
2227
|
-
}
|
|
2228
|
-
if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
|
|
2270
|
+
const json = await res.json();
|
|
2271
|
+
if (!json.success) {
|
|
2229
2272
|
throw new OwneyError(
|
|
2230
|
-
"
|
|
2231
|
-
`
|
|
2232
|
-
{
|
|
2233
|
-
statusCode: res.status,
|
|
2234
|
-
responseBody: text.slice(0, 500),
|
|
2235
|
-
safeToFallback: true
|
|
2236
|
-
}
|
|
2273
|
+
"SWAP_REQUEST_FAILED",
|
|
2274
|
+
`Swap API request failed: ${json.message ?? "unknown error"}`,
|
|
2275
|
+
{ message: json.message }
|
|
2237
2276
|
);
|
|
2238
2277
|
}
|
|
2239
|
-
return
|
|
2278
|
+
return json.data;
|
|
2240
2279
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2280
|
+
function createSwapApi(baseUrl, apiKey) {
|
|
2281
|
+
return {
|
|
2282
|
+
/** Source assets the user may pay with, and each chain's deposit targets. */
|
|
2283
|
+
listTokens: () => request(baseUrl, apiKey, "/tokens"),
|
|
2284
|
+
/**
|
|
2285
|
+
* Search the payable-with assets by name, symbol or address.
|
|
2286
|
+
*
|
|
2287
|
+
* Separate from `listTokens` on purpose: the full set is thousands of
|
|
2288
|
+
* tokens per chain, far too much to ship to a picker, so the short list
|
|
2289
|
+
* renders immediately and this reaches everything else on demand.
|
|
2290
|
+
*
|
|
2291
|
+
* Already filtered server-side to what a quote will accept, so anything
|
|
2292
|
+
* returned here can be paid with.
|
|
2293
|
+
*/
|
|
2294
|
+
searchTokens: (params) => request(
|
|
2295
|
+
baseUrl,
|
|
2296
|
+
apiKey,
|
|
2297
|
+
`/tokens/search?${new URLSearchParams({
|
|
2298
|
+
query: params.query,
|
|
2299
|
+
// Omitted, the routing API searches every supported chain — which is
|
|
2300
|
+
// what a picker wants, since its rows already span all three.
|
|
2301
|
+
...params.chainId === void 0 ? {} : { chainId: String(params.chainId) },
|
|
2302
|
+
...params.limit === void 0 ? {} : { limit: String(params.limit) }
|
|
2303
|
+
}).toString()}`
|
|
2304
|
+
),
|
|
2305
|
+
/**
|
|
2306
|
+
* `walletAddress` is required even though the routing API could not infer
|
|
2307
|
+
* it: the Fusion+ quoter binds a quote to whoever will sign the order and
|
|
2308
|
+
* rejects the request without it.
|
|
2309
|
+
*/
|
|
2310
|
+
quote: (params) => request(baseUrl, apiKey, "/quote", {
|
|
2311
|
+
method: "POST",
|
|
2312
|
+
body: {
|
|
2313
|
+
srcChainId: params.from.chainId,
|
|
2314
|
+
srcSymbol: params.from.symbol,
|
|
2315
|
+
dstChainId: params.to.chainId,
|
|
2316
|
+
dstSymbol: params.to.symbol,
|
|
2317
|
+
amount: params.from.amount,
|
|
2318
|
+
walletAddress: params.walletAddress,
|
|
2319
|
+
...params.direction ? { direction: params.direction } : {}
|
|
2272
2320
|
}
|
|
2273
|
-
)
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
"x-owney-api-key": input.apiKey
|
|
2287
|
-
},
|
|
2288
|
-
body: JSON.stringify(input.body)
|
|
2321
|
+
}),
|
|
2322
|
+
/** Ready-to-send calldata for a same-chain swap. */
|
|
2323
|
+
swapTx: (params) => request(baseUrl, apiKey, "/tx", {
|
|
2324
|
+
method: "POST",
|
|
2325
|
+
body: {
|
|
2326
|
+
srcChainId: params.from.chainId,
|
|
2327
|
+
srcSymbol: params.from.symbol,
|
|
2328
|
+
dstChainId: params.to.chainId,
|
|
2329
|
+
dstSymbol: params.to.symbol,
|
|
2330
|
+
amount: params.from.amount,
|
|
2331
|
+
walletAddress: params.walletAddress,
|
|
2332
|
+
slippage: params.slippage,
|
|
2333
|
+
...params.direction ? { direction: params.direction } : {}
|
|
2289
2334
|
}
|
|
2290
|
-
)
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2335
|
+
}),
|
|
2336
|
+
/**
|
|
2337
|
+
* Builds a Fusion+ order server-side and returns EIP-712 typed data.
|
|
2338
|
+
*
|
|
2339
|
+
* Only HASHES go over the wire. The preimages never leave the browser —
|
|
2340
|
+
* see swap.secrets.
|
|
2341
|
+
*/
|
|
2342
|
+
buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
|
|
2343
|
+
method: "POST",
|
|
2344
|
+
body: {
|
|
2345
|
+
srcChainId: params.from.chainId,
|
|
2346
|
+
srcSymbol: params.from.symbol,
|
|
2347
|
+
dstChainId: params.to.chainId,
|
|
2348
|
+
dstSymbol: params.to.symbol,
|
|
2349
|
+
amount: params.from.amount,
|
|
2350
|
+
walletAddress: params.walletAddress,
|
|
2351
|
+
secretHashes: params.secretHashes,
|
|
2352
|
+
...params.direction ? { direction: params.direction } : {},
|
|
2353
|
+
...params.receiver ? { receiver: params.receiver } : {}
|
|
2307
2354
|
}
|
|
2308
|
-
)
|
|
2309
|
-
|
|
2310
|
-
|
|
2355
|
+
}),
|
|
2356
|
+
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
2357
|
+
/**
|
|
2358
|
+
* Only call once `readyForSecrets` reports the escrow deployed. Publishing
|
|
2359
|
+
* earlier hands a resolver the preimage while the user's funds are locked
|
|
2360
|
+
* and nothing has been posted on the destination chain.
|
|
2361
|
+
*/
|
|
2362
|
+
submitSecret: (orderHash, secret) => request(baseUrl, apiKey, "/order/secret", {
|
|
2363
|
+
method: "POST",
|
|
2364
|
+
body: { orderHash, secret }
|
|
2365
|
+
}),
|
|
2366
|
+
orderStatus: (orderHash) => request(baseUrl, apiKey, `/order/${orderHash}`),
|
|
2367
|
+
readyForSecrets: (orderHash) => request(
|
|
2368
|
+
baseUrl,
|
|
2369
|
+
apiKey,
|
|
2370
|
+
`/order/${orderHash}/ready-for-secrets`
|
|
2371
|
+
)
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// src/lib/swap/swap.rpc.ts
|
|
2376
|
+
var import_viem2 = require("viem");
|
|
2377
|
+
var DEFAULT_RPC_URLS = {
|
|
2378
|
+
1: ["https://cloudflare-eth.com", "https://ethereum-rpc.publicnode.com"],
|
|
2379
|
+
8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
|
|
2380
|
+
42161: [
|
|
2381
|
+
"https://arb1.arbitrum.io/rpc",
|
|
2382
|
+
"https://arbitrum-one-rpc.publicnode.com"
|
|
2383
|
+
]
|
|
2384
|
+
};
|
|
2385
|
+
function swapReadTransport(chainId, overrides) {
|
|
2386
|
+
const override = overrides?.[chainId];
|
|
2387
|
+
if (override) return (0, import_viem2.http)(override);
|
|
2388
|
+
const urls = DEFAULT_RPC_URLS[chainId];
|
|
2389
|
+
if (!urls || urls.length === 0) return (0, import_viem2.http)();
|
|
2390
|
+
return (0, import_viem2.fallback)(urls.map((url) => (0, import_viem2.http)(url)));
|
|
2391
|
+
}
|
|
2392
|
+
function receiptTimeoutMs(chainId) {
|
|
2393
|
+
return chainId === 1 ? 6e5 : 18e4;
|
|
2311
2394
|
}
|
|
2312
2395
|
|
|
2313
2396
|
// src/lib/permit2.ts
|
|
2314
2397
|
var import_viem3 = require("viem");
|
|
2315
2398
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2316
2399
|
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2317
|
-
function permit2ApprovalAmount(requiredAmount) {
|
|
2318
|
-
if (requiredAmount <= 0n) {
|
|
2319
|
-
throw new Error("Permit2 approval requires a positive deposit amount");
|
|
2320
|
-
}
|
|
2321
|
-
return MAX_UINT256;
|
|
2322
|
-
}
|
|
2323
2400
|
var ERC20_ALLOWANCE_ABI = [
|
|
2324
2401
|
{
|
|
2325
2402
|
type: "function",
|
|
@@ -2349,18 +2426,40 @@ var ERC20_ALLOWANCE_ABI = [
|
|
|
2349
2426
|
outputs: [{ name: "", type: "uint256" }]
|
|
2350
2427
|
}
|
|
2351
2428
|
];
|
|
2429
|
+
function buildPermitTransferFromTypedData(input) {
|
|
2430
|
+
return {
|
|
2431
|
+
domain: {
|
|
2432
|
+
name: "Permit2",
|
|
2433
|
+
chainId: input.chainId,
|
|
2434
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
2435
|
+
},
|
|
2436
|
+
types: {
|
|
2437
|
+
PermitTransferFrom: [
|
|
2438
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
2439
|
+
{ name: "spender", type: "address" },
|
|
2440
|
+
{ name: "nonce", type: "uint256" },
|
|
2441
|
+
{ name: "deadline", type: "uint256" }
|
|
2442
|
+
],
|
|
2443
|
+
TokenPermissions: [
|
|
2444
|
+
{ name: "token", type: "address" },
|
|
2445
|
+
{ name: "amount", type: "uint256" }
|
|
2446
|
+
]
|
|
2447
|
+
},
|
|
2448
|
+
primaryType: "PermitTransferFrom",
|
|
2449
|
+
message: input.message
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2352
2452
|
function randomPermit2Nonce() {
|
|
2353
2453
|
const bytes = new Uint8Array(32);
|
|
2354
2454
|
globalThis.crypto.getRandomValues(bytes);
|
|
2355
2455
|
return BigInt((0, import_viem3.bytesToHex)(bytes));
|
|
2356
2456
|
}
|
|
2357
|
-
async function readPermit2Allowance(publicClient, token, owner
|
|
2457
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2358
2458
|
return publicClient.readContract({
|
|
2359
2459
|
address: token,
|
|
2360
2460
|
abi: ERC20_ALLOWANCE_ABI,
|
|
2361
2461
|
functionName: "allowance",
|
|
2362
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2363
|
-
...blockNumber === void 0 ? {} : { blockNumber }
|
|
2462
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
2364
2463
|
});
|
|
2365
2464
|
}
|
|
2366
2465
|
async function readErc20Balance(publicClient, token, owner) {
|
|
@@ -2372,39 +2471,122 @@ async function readErc20Balance(publicClient, token, owner) {
|
|
|
2372
2471
|
});
|
|
2373
2472
|
}
|
|
2374
2473
|
|
|
2375
|
-
// src/lib/
|
|
2376
|
-
var
|
|
2377
|
-
var
|
|
2378
|
-
function
|
|
2379
|
-
|
|
2474
|
+
// src/lib/swap/swap.secrets.ts
|
|
2475
|
+
var import_viem4 = require("viem");
|
|
2476
|
+
var SECRET_BYTES = 32;
|
|
2477
|
+
function randomBytes(length) {
|
|
2478
|
+
const bytes = new Uint8Array(length);
|
|
2479
|
+
const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
2480
|
+
if (!cryptoObj?.getRandomValues) {
|
|
2481
|
+
throw new Error(
|
|
2482
|
+
"[owney-sdk] Secure randomness is unavailable, so a swap secret cannot be generated safely."
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
cryptoObj.getRandomValues(bytes);
|
|
2486
|
+
return bytes;
|
|
2380
2487
|
}
|
|
2381
|
-
function
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2488
|
+
function mintSecrets(count) {
|
|
2489
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
2490
|
+
throw new Error(
|
|
2491
|
+
`[owney-sdk] A swap needs at least one secret, got ${String(count)}.`
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
const secrets = [];
|
|
2495
|
+
const secretHashes = [];
|
|
2496
|
+
for (let i = 0; i < count; i++) {
|
|
2497
|
+
const secret = (0, import_viem4.toHex)(randomBytes(SECRET_BYTES));
|
|
2498
|
+
secrets.push(secret);
|
|
2499
|
+
secretHashes.push((0, import_viem4.keccak256)(secret));
|
|
2500
|
+
}
|
|
2501
|
+
return { secrets, secretHashes };
|
|
2394
2502
|
}
|
|
2395
2503
|
|
|
2396
|
-
// src/
|
|
2397
|
-
var
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
var KEY_PREFIX2 = "owney.yieldseeker.session.v5";
|
|
2403
|
-
var INVALIDATED_KEY_PREFIXES = [
|
|
2404
|
-
"owney.yieldseeker.session",
|
|
2405
|
-
"owney.yieldseeker.session.v3",
|
|
2406
|
-
"owney.yieldseeker.session.v4"
|
|
2504
|
+
// src/lib/swap/swap.types.ts
|
|
2505
|
+
var SWAP_TERMINAL_STATUSES = [
|
|
2506
|
+
"executed",
|
|
2507
|
+
"expired",
|
|
2508
|
+
"cancelled",
|
|
2509
|
+
"refunded"
|
|
2407
2510
|
];
|
|
2511
|
+
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2512
|
+
|
|
2513
|
+
// src/lib/swap/swap.order-runner.ts
|
|
2514
|
+
var DEFAULT_POLL_MS = 5e3;
|
|
2515
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
2516
|
+
var backoffFor = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
2517
|
+
var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
2518
|
+
async function runFusionOrder(deps, options) {
|
|
2519
|
+
const {
|
|
2520
|
+
orderHash,
|
|
2521
|
+
secrets,
|
|
2522
|
+
onStage,
|
|
2523
|
+
pollIntervalMs = DEFAULT_POLL_MS,
|
|
2524
|
+
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
2525
|
+
} = options;
|
|
2526
|
+
const deadline = deps.now() + timeoutMs;
|
|
2527
|
+
let failures = 0;
|
|
2528
|
+
const published = /* @__PURE__ */ new Set();
|
|
2529
|
+
onStage?.("swapping");
|
|
2530
|
+
for (; ; ) {
|
|
2531
|
+
if (deps.now() >= deadline) {
|
|
2532
|
+
throw new OwneyError(
|
|
2533
|
+
"SWAP_REQUEST_FAILED",
|
|
2534
|
+
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
2535
|
+
{ orderHash }
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
let ready;
|
|
2539
|
+
try {
|
|
2540
|
+
ready = await deps.readyForSecrets(orderHash);
|
|
2541
|
+
} catch {
|
|
2542
|
+
ready = {};
|
|
2543
|
+
}
|
|
2544
|
+
for (const fill of ready.fills ?? []) {
|
|
2545
|
+
if (published.has(fill.idx)) continue;
|
|
2546
|
+
const secret = secrets[fill.idx];
|
|
2547
|
+
if (secret === void 0) {
|
|
2548
|
+
throw new OwneyError(
|
|
2549
|
+
"SWAP_REQUEST_FAILED",
|
|
2550
|
+
`Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
|
|
2551
|
+
{ orderHash, fillIndex: fill.idx }
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
try {
|
|
2555
|
+
await deps.submitSecret(orderHash, secret);
|
|
2556
|
+
published.add(fill.idx);
|
|
2557
|
+
} catch {
|
|
2558
|
+
failures += 1;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
let status;
|
|
2562
|
+
try {
|
|
2563
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2564
|
+
failures = 0;
|
|
2565
|
+
} catch {
|
|
2566
|
+
failures += 1;
|
|
2567
|
+
await deps.sleep(backoffFor(failures, pollIntervalMs));
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
if (status === "refunding") onStage?.("refunding");
|
|
2571
|
+
if (isSwapTerminal(status)) {
|
|
2572
|
+
if (status === "executed") {
|
|
2573
|
+
onStage?.("swapped");
|
|
2574
|
+
return { status, filled: true };
|
|
2575
|
+
}
|
|
2576
|
+
if (status === "refunded") onStage?.("refunded");
|
|
2577
|
+
throw new OwneyError(
|
|
2578
|
+
status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2579
|
+
status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
|
|
2580
|
+
{ orderHash, status }
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
await deps.sleep(pollIntervalMs);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
// src/lib/swap/swap.secret-store.ts
|
|
2588
|
+
var KEY_PREFIX2 = "owney.swap.order";
|
|
2589
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
2408
2590
|
var storage2 = () => {
|
|
2409
2591
|
if (typeof window === "undefined") return null;
|
|
2410
2592
|
try {
|
|
@@ -2413,1649 +2595,287 @@ var storage2 = () => {
|
|
|
2413
2595
|
return null;
|
|
2414
2596
|
}
|
|
2415
2597
|
};
|
|
2416
|
-
var
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
);
|
|
2420
|
-
var clearInvalidatedSessions = (store, address, chainId) => {
|
|
2421
|
-
for (const key2 of invalidatedKeys(address, chainId)) {
|
|
2422
|
-
memorySessions2.delete(key2);
|
|
2423
|
-
try {
|
|
2424
|
-
store?.removeItem(key2);
|
|
2425
|
-
} catch {
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
};
|
|
2429
|
-
var memorySessions2 = /* @__PURE__ */ new Map();
|
|
2430
|
-
var isValidSession = (session) => {
|
|
2431
|
-
if (!session?.token) return false;
|
|
2598
|
+
var keyFor = (orderHash) => `${KEY_PREFIX2}.${orderHash}`;
|
|
2599
|
+
function saveOrder(order) {
|
|
2600
|
+
const store = storage2();
|
|
2601
|
+
if (!store) return;
|
|
2432
2602
|
try {
|
|
2433
|
-
|
|
2434
|
-
return typeof parsed.message === "string" && typeof parsed.signature === "string" && parsed.signature.startsWith("0x");
|
|
2603
|
+
store.setItem(keyFor(order.orderHash), JSON.stringify(order));
|
|
2435
2604
|
} catch {
|
|
2436
|
-
return false;
|
|
2437
2605
|
}
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
if (typeof window === "undefined") return null;
|
|
2441
|
-
const key2 = buildKey2(address, chainId);
|
|
2606
|
+
}
|
|
2607
|
+
function clearOrder(orderHash) {
|
|
2442
2608
|
const store = storage2();
|
|
2443
|
-
|
|
2444
|
-
let raw2 = null;
|
|
2609
|
+
if (!store) return;
|
|
2445
2610
|
try {
|
|
2446
|
-
|
|
2611
|
+
store.removeItem(keyFor(orderHash));
|
|
2447
2612
|
} catch {
|
|
2448
|
-
raw2 = null;
|
|
2449
2613
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
store
|
|
2459
|
-
|
|
2614
|
+
}
|
|
2615
|
+
function listOrders(now = Date.now()) {
|
|
2616
|
+
const store = storage2();
|
|
2617
|
+
if (!store) return [];
|
|
2618
|
+
const out = [];
|
|
2619
|
+
try {
|
|
2620
|
+
const keys = [];
|
|
2621
|
+
for (let i = 0; i < store.length; i++) {
|
|
2622
|
+
const key2 = store.key(i);
|
|
2623
|
+
if (key2?.startsWith(`${KEY_PREFIX2}.`)) keys.push(key2);
|
|
2460
2624
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
if (isValidSession(cached)) return cached.token;
|
|
2465
|
-
if (cached) memorySessions2.delete(key2);
|
|
2466
|
-
return null;
|
|
2467
|
-
};
|
|
2468
|
-
var writeYieldseekerSession = (address, chainId, token) => {
|
|
2469
|
-
if (typeof window === "undefined") return;
|
|
2470
|
-
const session = { token };
|
|
2471
|
-
if (!isValidSession(session)) return;
|
|
2472
|
-
const key2 = buildKey2(address, chainId);
|
|
2473
|
-
memorySessions2.set(key2, session);
|
|
2474
|
-
const store = storage2();
|
|
2475
|
-
try {
|
|
2476
|
-
store?.setItem(key2, JSON.stringify(session));
|
|
2477
|
-
} catch {
|
|
2478
|
-
}
|
|
2479
|
-
};
|
|
2480
|
-
var clearYieldseekerSession = (address, chainId) => {
|
|
2481
|
-
const key2 = buildKey2(address, chainId);
|
|
2482
|
-
memorySessions2.delete(key2);
|
|
2483
|
-
const store = storage2();
|
|
2484
|
-
clearInvalidatedSessions(store, address, chainId);
|
|
2485
|
-
try {
|
|
2486
|
-
store?.removeItem(key2);
|
|
2487
|
-
} catch {
|
|
2488
|
-
}
|
|
2489
|
-
};
|
|
2490
|
-
|
|
2491
|
-
// src/agents/yieldseeker/yieldseeker.auth.ts
|
|
2492
|
-
function resolveSiweOrigin(override) {
|
|
2493
|
-
const origin = override?.trim() || (typeof window !== "undefined" ? window.location?.origin : void 0);
|
|
2494
|
-
if (!origin || origin === "null") {
|
|
2495
|
-
throw new Error("Yieldseeker sign-in requires the requesting app's browser origin.");
|
|
2496
|
-
}
|
|
2497
|
-
const url = new URL(origin);
|
|
2498
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
2499
|
-
throw new Error("Yieldseeker sign-in requires an HTTP or HTTPS app origin.");
|
|
2500
|
-
}
|
|
2501
|
-
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
2502
|
-
throw new Error("Yieldseeker SIWE override must be an origin without credentials, path, query, or fragment.");
|
|
2503
|
-
}
|
|
2504
|
-
return url;
|
|
2505
|
-
}
|
|
2506
|
-
function createYieldseekerSiweMessage(address, chainId, dependencies = {}) {
|
|
2507
|
-
const url = resolveSiweOrigin(dependencies.origin);
|
|
2508
|
-
return new import_siwe.SiweMessage({
|
|
2509
|
-
scheme: url.protocol.slice(0, -1),
|
|
2510
|
-
domain: url.host,
|
|
2511
|
-
address: (0, import_viem4.getAddress)(address),
|
|
2512
|
-
uri: url.origin,
|
|
2513
|
-
version: "1",
|
|
2514
|
-
chainId,
|
|
2515
|
-
nonce: (dependencies.nonce ?? import_siwe.generateNonce)(),
|
|
2516
|
-
issuedAt: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
2517
|
-
}).prepareMessage();
|
|
2518
|
-
}
|
|
2519
|
-
function encodeYieldseekerAuthToken(token) {
|
|
2520
|
-
const bytes = new TextEncoder().encode(JSON.stringify(token));
|
|
2521
|
-
let binary = "";
|
|
2522
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
2523
|
-
return btoa(binary);
|
|
2524
|
-
}
|
|
2525
|
-
var YieldseekerAuth = class {
|
|
2526
|
-
constructor(dependencies = {}) {
|
|
2527
|
-
this.dependencies = dependencies;
|
|
2528
|
-
}
|
|
2529
|
-
dependencies;
|
|
2530
|
-
tokens = /* @__PURE__ */ new Map();
|
|
2531
|
-
pending = /* @__PURE__ */ new Map();
|
|
2532
|
-
scopes = /* @__PURE__ */ new Map();
|
|
2533
|
-
key(state, chainId) {
|
|
2534
|
-
return `${state.walletAddress.toLowerCase()}:${chainId}:${resolveSiweOrigin(this.dependencies.origin).origin}`;
|
|
2535
|
-
}
|
|
2536
|
-
async getToken(state, chainId) {
|
|
2537
|
-
const key2 = this.key(state, chainId);
|
|
2538
|
-
const scope = { address: state.walletAddress, chainId };
|
|
2539
|
-
this.scopes.set(key2, scope);
|
|
2540
|
-
const cached = this.tokens.get(key2);
|
|
2541
|
-
if (cached) return cached;
|
|
2542
|
-
const persisted = readYieldseekerSession(scope.address, scope.chainId);
|
|
2543
|
-
if (persisted && this.matchesOrigin(persisted)) {
|
|
2544
|
-
this.tokens.set(key2, persisted);
|
|
2545
|
-
return persisted;
|
|
2546
|
-
}
|
|
2547
|
-
if (persisted) clearYieldseekerSession(scope.address, scope.chainId);
|
|
2548
|
-
const inFlight = this.pending.get(key2);
|
|
2549
|
-
if (inFlight) return inFlight;
|
|
2550
|
-
const request = this.sign(state, chainId).then((token) => {
|
|
2551
|
-
if (this.pending.get(key2) !== request) throw new Error("Wallet sign-in session changed. Please try again.");
|
|
2552
|
-
this.tokens.set(key2, token);
|
|
2553
|
-
writeYieldseekerSession(scope.address, scope.chainId, token);
|
|
2554
|
-
return token;
|
|
2555
|
-
});
|
|
2556
|
-
this.pending.set(key2, request);
|
|
2557
|
-
try {
|
|
2558
|
-
return await request;
|
|
2559
|
-
} finally {
|
|
2560
|
-
if (this.pending.get(key2) === request) this.pending.delete(key2);
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
async refreshToken(state, chainId, rejectedToken) {
|
|
2564
|
-
const key2 = this.key(state, chainId);
|
|
2565
|
-
if (this.tokens.get(key2) === rejectedToken) {
|
|
2566
|
-
this.tokens.delete(key2);
|
|
2567
|
-
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2568
|
-
}
|
|
2569
|
-
return this.getToken(state, chainId);
|
|
2570
|
-
}
|
|
2571
|
-
matchesOrigin(token) {
|
|
2572
|
-
try {
|
|
2573
|
-
const message = new import_siwe.SiweMessage(JSON.parse(atob(token)).message);
|
|
2574
|
-
const url = resolveSiweOrigin(this.dependencies.origin);
|
|
2575
|
-
return message.domain === url.host && message.uri === url.origin && message.scheme === url.protocol.slice(0, -1);
|
|
2576
|
-
} catch {
|
|
2577
|
-
return false;
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
clear(state, chainId) {
|
|
2581
|
-
if (!state || chainId === void 0) {
|
|
2582
|
-
for (const scope of this.scopes.values()) {
|
|
2583
|
-
clearYieldseekerSession(scope.address, scope.chainId);
|
|
2584
|
-
}
|
|
2585
|
-
this.tokens.clear();
|
|
2586
|
-
this.pending.clear();
|
|
2587
|
-
this.scopes.clear();
|
|
2588
|
-
return;
|
|
2589
|
-
}
|
|
2590
|
-
const key2 = this.key(state, chainId);
|
|
2591
|
-
this.tokens.delete(key2);
|
|
2592
|
-
this.pending.delete(key2);
|
|
2593
|
-
this.scopes.delete(key2);
|
|
2594
|
-
clearYieldseekerSession(state.walletAddress, chainId);
|
|
2595
|
-
}
|
|
2596
|
-
async sign(state, chainId) {
|
|
2597
|
-
const account = (0, import_viem4.getAddress)(state.walletAddress);
|
|
2598
|
-
const publicClient = (0, import_viem4.createPublicClient)({
|
|
2599
|
-
chain: import_chains2.base,
|
|
2600
|
-
transport: (0, import_viem4.custom)(state.provider)
|
|
2601
|
-
});
|
|
2602
|
-
const walletClient = (0, import_viem4.createWalletClient)({
|
|
2603
|
-
account,
|
|
2604
|
-
chain: import_chains2.base,
|
|
2605
|
-
transport: (0, import_viem4.custom)(state.provider)
|
|
2606
|
-
});
|
|
2607
|
-
await ensureWalletOnChain(
|
|
2608
|
-
publicClient,
|
|
2609
|
-
walletClient,
|
|
2610
|
-
8453
|
|
2611
|
-
);
|
|
2612
|
-
const message = createYieldseekerSiweMessage(
|
|
2613
|
-
account,
|
|
2614
|
-
chainId,
|
|
2615
|
-
this.dependencies
|
|
2616
|
-
);
|
|
2617
|
-
const signature = await walletClient.signMessage({ account, message });
|
|
2618
|
-
return encodeYieldseekerAuthToken({ message, signature });
|
|
2619
|
-
}
|
|
2620
|
-
};
|
|
2621
|
-
|
|
2622
|
-
// src/agents/yieldseeker/yieldseeker.identity-cache.ts
|
|
2623
|
-
var KEY_PREFIX3 = "owney.yieldseeker.identity.v1";
|
|
2624
|
-
var YIELDSEEKER_IDENTITY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2625
|
-
var memoryIdentities = /* @__PURE__ */ new Map();
|
|
2626
|
-
var storage3 = () => {
|
|
2627
|
-
if (typeof window === "undefined") return null;
|
|
2628
|
-
try {
|
|
2629
|
-
return window.localStorage;
|
|
2630
|
-
} catch {
|
|
2631
|
-
return null;
|
|
2632
|
-
}
|
|
2633
|
-
};
|
|
2634
|
-
var keyFor = (walletAddress, chainId) => `${KEY_PREFIX3}:${walletAddress.toLowerCase()}:${chainId}`;
|
|
2635
|
-
function valid(value, walletAddress, chainId, now) {
|
|
2636
|
-
return Boolean(
|
|
2637
|
-
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
|
|
2638
|
-
);
|
|
2639
|
-
}
|
|
2640
|
-
function readYieldseekerIdentity(walletAddress, chainId, now = Date.now()) {
|
|
2641
|
-
if (typeof window === "undefined") return null;
|
|
2642
|
-
const key2 = keyFor(walletAddress, chainId);
|
|
2643
|
-
const store = storage3();
|
|
2644
|
-
let parsed = null;
|
|
2645
|
-
try {
|
|
2646
|
-
const raw2 = store?.getItem(key2);
|
|
2647
|
-
parsed = raw2 ? JSON.parse(raw2) : null;
|
|
2648
|
-
} catch {
|
|
2649
|
-
parsed = null;
|
|
2650
|
-
}
|
|
2651
|
-
const candidate = parsed ?? memoryIdentities.get(key2);
|
|
2652
|
-
if (valid(candidate, walletAddress, chainId, now)) {
|
|
2653
|
-
memoryIdentities.set(key2, candidate);
|
|
2654
|
-
return { userId: candidate.userId };
|
|
2655
|
-
}
|
|
2656
|
-
memoryIdentities.delete(key2);
|
|
2657
|
-
try {
|
|
2658
|
-
store?.removeItem(key2);
|
|
2659
|
-
} catch {
|
|
2660
|
-
}
|
|
2661
|
-
return null;
|
|
2662
|
-
}
|
|
2663
|
-
function writeYieldseekerIdentity(walletAddress, chainId, userId, now = Date.now()) {
|
|
2664
|
-
if (typeof window === "undefined") return;
|
|
2665
|
-
const identity = {
|
|
2666
|
-
userId,
|
|
2667
|
-
walletAddress,
|
|
2668
|
-
chainId,
|
|
2669
|
-
expiresAt: now + YIELDSEEKER_IDENTITY_TTL_MS
|
|
2670
|
-
};
|
|
2671
|
-
if (!valid(identity, walletAddress, chainId, now)) return;
|
|
2672
|
-
const key2 = keyFor(walletAddress, chainId);
|
|
2673
|
-
memoryIdentities.set(key2, identity);
|
|
2674
|
-
try {
|
|
2675
|
-
storage3()?.setItem(key2, JSON.stringify(identity));
|
|
2676
|
-
} catch {
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
function clearYieldseekerIdentity(walletAddress, chainId) {
|
|
2680
|
-
const key2 = keyFor(walletAddress, chainId);
|
|
2681
|
-
memoryIdentities.delete(key2);
|
|
2682
|
-
try {
|
|
2683
|
-
storage3()?.removeItem(key2);
|
|
2684
|
-
} catch {
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
// src/agents/yieldseeker/yieldseeker.client.ts
|
|
2689
|
-
var DEFAULT_ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2690
|
-
function getYieldseekerProxyBaseUrl(routingApiBaseUrl = DEFAULT_ROUTING_API_BASE_URL) {
|
|
2691
|
-
return `${routingApiBaseUrl.replace(/\/$/, "")}/api/v1/agent/yieldseeker`;
|
|
2692
|
-
}
|
|
2693
|
-
var YIELDSEEKER_API_BASE_URL = getYieldseekerProxyBaseUrl();
|
|
2694
|
-
var YieldseekerApiError = class extends Error {
|
|
2695
|
-
constructor(status, providerCode, responseFields) {
|
|
2696
|
-
super(`Yieldseeker request failed (${status}): ${providerCode}`);
|
|
2697
|
-
this.status = status;
|
|
2698
|
-
this.providerCode = providerCode;
|
|
2699
|
-
this.responseFields = responseFields;
|
|
2700
|
-
this.name = "YieldseekerApiError";
|
|
2701
|
-
}
|
|
2702
|
-
status;
|
|
2703
|
-
providerCode;
|
|
2704
|
-
responseFields;
|
|
2705
|
-
get isAuthenticationError() {
|
|
2706
|
-
return this.status === 401 || this.status === 403;
|
|
2707
|
-
}
|
|
2708
|
-
};
|
|
2709
|
-
function providerError(body, fallback) {
|
|
2710
|
-
if (!body || typeof body !== "object") return { code: fallback };
|
|
2711
|
-
const record = body;
|
|
2712
|
-
return {
|
|
2713
|
-
code: typeof record.message === "string" ? record.message : fallback,
|
|
2714
|
-
fields: record.fields && typeof record.fields === "object" ? record.fields : void 0
|
|
2715
|
-
};
|
|
2716
|
-
}
|
|
2717
|
-
var YieldseekerApiClient = class {
|
|
2718
|
-
constructor(owneyApiKey, baseUrl = YIELDSEEKER_API_BASE_URL, fetchFn = fetch.bind(globalThis)) {
|
|
2719
|
-
this.owneyApiKey = owneyApiKey;
|
|
2720
|
-
this.baseUrl = baseUrl;
|
|
2721
|
-
this.fetchFn = fetchFn;
|
|
2722
|
-
}
|
|
2723
|
-
owneyApiKey;
|
|
2724
|
-
baseUrl;
|
|
2725
|
-
fetchFn;
|
|
2726
|
-
async request(path, options = {}) {
|
|
2727
|
-
const controller = new AbortController();
|
|
2728
|
-
const timer = setTimeout(
|
|
2729
|
-
() => controller.abort(),
|
|
2730
|
-
options.timeoutMs ?? 15e3
|
|
2731
|
-
);
|
|
2732
|
-
try {
|
|
2733
|
-
const response = await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
2734
|
-
method: options.method ?? "GET",
|
|
2735
|
-
headers: {
|
|
2736
|
-
"Content-Type": "application/json",
|
|
2737
|
-
"x-owney-api-key": this.owneyApiKey,
|
|
2738
|
-
...options.signature ? { Authorization: `Signature ${options.signature}` } : {}
|
|
2739
|
-
},
|
|
2740
|
-
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2741
|
-
signal: controller.signal
|
|
2742
|
-
});
|
|
2743
|
-
const payload = await response.json().catch(() => null);
|
|
2744
|
-
if (!response.ok) {
|
|
2745
|
-
const error = providerError(payload, `HTTP_${response.status}`);
|
|
2746
|
-
throw new YieldseekerApiError(
|
|
2747
|
-
response.status,
|
|
2748
|
-
error.code,
|
|
2749
|
-
error.fields
|
|
2750
|
-
);
|
|
2751
|
-
}
|
|
2752
|
-
if (payload && typeof payload === "object" && payload.success === true && "data" in payload) {
|
|
2753
|
-
return payload.data;
|
|
2754
|
-
}
|
|
2755
|
-
return payload;
|
|
2756
|
-
} catch (error) {
|
|
2757
|
-
if (error instanceof YieldseekerApiError) throw error;
|
|
2758
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2759
|
-
throw new YieldseekerApiError(408, "REQUEST_TIMEOUT");
|
|
2760
|
-
}
|
|
2761
|
-
throw new YieldseekerApiError(0, "NETWORK_ERROR", {
|
|
2762
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2763
|
-
});
|
|
2764
|
-
} finally {
|
|
2765
|
-
clearTimeout(timer);
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
};
|
|
2769
|
-
|
|
2770
|
-
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2771
|
-
var import_viem5 = require("viem");
|
|
2772
|
-
|
|
2773
|
-
// src/lib/helpers/snapshot-apy.ts
|
|
2774
|
-
var DAY_MS = 864e5;
|
|
2775
|
-
function snapshotTime(date) {
|
|
2776
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
|
|
2777
|
-
const time = Date.parse(date);
|
|
2778
|
-
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === date ? time : void 0;
|
|
2779
|
-
}
|
|
2780
|
-
function returnFactor(value) {
|
|
2781
|
-
if (typeof value !== "number" && typeof value !== "string") return void 0;
|
|
2782
|
-
if (typeof value === "string" && value.trim() === "") return void 0;
|
|
2783
|
-
const factor = Number(value);
|
|
2784
|
-
return Number.isFinite(factor) && factor >= 0 ? factor : void 0;
|
|
2785
|
-
}
|
|
2786
|
-
function calculateSnapshotApy(snapshots, lookbackDays, now = Date.now()) {
|
|
2787
|
-
if (!Number.isFinite(lookbackDays) || lookbackDays <= 0 || !Number.isFinite(now)) {
|
|
2788
|
-
return void 0;
|
|
2789
|
-
}
|
|
2790
|
-
const points = snapshots.flatMap((snapshot) => {
|
|
2791
|
-
const time = snapshotTime(snapshot.date);
|
|
2792
|
-
return time !== void 0 && time <= now ? [{ snapshot, time }] : [];
|
|
2793
|
-
}).sort((a, b) => a.time - b.time);
|
|
2794
|
-
const end = points.at(-1);
|
|
2795
|
-
if (!end) return void 0;
|
|
2796
|
-
const cutoff = end.time - lookbackDays * DAY_MS;
|
|
2797
|
-
const start = points.find((point) => point.time >= cutoff);
|
|
2798
|
-
const actualDays = (end.time - start.time) / DAY_MS;
|
|
2799
|
-
if (actualDays <= 0) return void 0;
|
|
2800
|
-
const startFactor = returnFactor(start.snapshot.cumulativeReturnFactor);
|
|
2801
|
-
const endFactor = returnFactor(end.snapshot.cumulativeReturnFactor);
|
|
2802
|
-
if (startFactor === void 0 || startFactor === 0 || endFactor === void 0) {
|
|
2803
|
-
return void 0;
|
|
2804
|
-
}
|
|
2805
|
-
const periodReturn = endFactor / startFactor - 1;
|
|
2806
|
-
const apy = Math.pow(1 + periodReturn, 365 / actualDays) - 1;
|
|
2807
|
-
return Number.isFinite(apy) ? apy : void 0;
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
|
-
// src/agents/yieldseeker/yieldseeker.types.ts
|
|
2811
|
-
var YIELDSEEKER_ASSET_METADATA = {
|
|
2812
|
-
USDC: {
|
|
2813
|
-
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
2814
|
-
decimals: 6
|
|
2815
|
-
},
|
|
2816
|
-
WETH: {
|
|
2817
|
-
address: "0x4200000000000000000000000000000000000006",
|
|
2818
|
-
decimals: 18
|
|
2819
|
-
}
|
|
2820
|
-
};
|
|
2821
|
-
|
|
2822
|
-
// src/agents/yieldseeker/yieldseeker.mapper.ts
|
|
2823
|
-
function invalid(endpoint, detail) {
|
|
2824
|
-
throw new OwneyError(
|
|
2825
|
-
"AGENT_INVALID_RESPONSE",
|
|
2826
|
-
`Yieldseeker returned an invalid ${endpoint} response: ${detail}.`,
|
|
2827
|
-
{ endpoint, detail },
|
|
2828
|
-
"yieldseeker"
|
|
2829
|
-
);
|
|
2830
|
-
}
|
|
2831
|
-
function raw(value, endpoint) {
|
|
2832
|
-
if (typeof value !== "string" || !/^-?[0-9]+$/.test(value)) {
|
|
2833
|
-
return invalid(endpoint, "expected a base-10 integer string");
|
|
2834
|
-
}
|
|
2835
|
-
return BigInt(value);
|
|
2836
|
-
}
|
|
2837
|
-
function decimal(value, decimals, endpoint) {
|
|
2838
|
-
return (0, import_viem5.formatUnits)(raw(value, endpoint), decimals);
|
|
2839
|
-
}
|
|
2840
|
-
function usd(rawAmount, decimals, price) {
|
|
2841
|
-
return Number((0, import_viem5.formatUnits)(rawAmount, decimals)) * price;
|
|
2842
|
-
}
|
|
2843
|
-
function percent(value) {
|
|
2844
|
-
const result = Number(value);
|
|
2845
|
-
return Number.isFinite(result) ? result * 100 : 0;
|
|
2846
|
-
}
|
|
2847
|
-
var YIELDSEEKER_POSITIVE_YIELD_FEE_RATE = 0.1;
|
|
2848
|
-
function publicApyAfterYieldseekerFee(value) {
|
|
2849
|
-
const grossPercent = percent(value);
|
|
2850
|
-
if (grossPercent <= 0) return grossPercent;
|
|
2851
|
-
const netPercent = grossPercent * (1 - YIELDSEEKER_POSITIVE_YIELD_FEE_RATE);
|
|
2852
|
-
return Math.round(netPercent * 1e12) / 1e12;
|
|
2853
|
-
}
|
|
2854
|
-
function riskAdjustedApyForDays(option, days) {
|
|
2855
|
-
if (days === "7D") return option.riskAdjustedApy7dAverage;
|
|
2856
|
-
if (days === "30D") return option.riskAdjustedApy30dAverage;
|
|
2857
|
-
return (option.riskAdjustedApy7dAverage + option.riskAdjustedApy30dAverage) / 2;
|
|
2858
|
-
}
|
|
2859
|
-
function assetAddressValue(record, address) {
|
|
2860
|
-
const entry = Object.entries(record).find(
|
|
2861
|
-
([key2]) => key2.toLowerCase() === address.toLowerCase()
|
|
2862
|
-
);
|
|
2863
|
-
return entry?.[1] ?? "0";
|
|
2864
|
-
}
|
|
2865
|
-
function position(value, asset, baseAssetDecimals) {
|
|
2866
|
-
const option = value?.yieldOption;
|
|
2867
|
-
if (!option || typeof option.provider !== "string" || typeof option.name !== "string" || typeof option.address !== "string" || !(0, import_viem5.isAddress)(option.address)) {
|
|
2868
|
-
return invalid("yield positions", "missing vault metadata");
|
|
2869
|
-
}
|
|
2870
|
-
return {
|
|
2871
|
-
chain: "BASE",
|
|
2872
|
-
protocol: option.provider,
|
|
2873
|
-
protocolId: option.address,
|
|
2874
|
-
pool: option.name,
|
|
2875
|
-
asset,
|
|
2876
|
-
// `assetsRaw` is the ERC-4626 vault-share quantity, whose decimals can
|
|
2877
|
-
// differ from the underlying asset. Yieldseeker already converts it to
|
|
2878
|
-
// underlying base-asset units in `assetsBase`; pair that value with the
|
|
2879
|
-
// snapshot's corresponding `baseAssetDecimals` for display. Keep the raw
|
|
2880
|
-
// share quantity separately because withdraw-from-position expects it.
|
|
2881
|
-
amount: decimal(
|
|
2882
|
-
value.assetsBase,
|
|
2883
|
-
baseAssetDecimals,
|
|
2884
|
-
"yield positions"
|
|
2885
|
-
),
|
|
2886
|
-
amountRaw: String(value.assetsRaw),
|
|
2887
|
-
apy: percent(option.riskAdjustedApy),
|
|
2888
|
-
tvl: Number(option.totalDepositsUsd),
|
|
2889
|
-
liquidity: Number(option.withdrawableDepositsUsd)
|
|
2890
|
-
};
|
|
2891
|
-
}
|
|
2892
|
-
function mapYieldseekerBalances(contexts) {
|
|
2893
|
-
const tokens = [];
|
|
2894
|
-
const assetBalances = [];
|
|
2895
|
-
const positions = [];
|
|
2896
|
-
let totalUsd = 0;
|
|
2897
|
-
for (const context of contexts) {
|
|
2898
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[context.asset];
|
|
2899
|
-
assetBalances.push({
|
|
2900
|
-
chain: "BASE",
|
|
2901
|
-
chainId: 8453,
|
|
2902
|
-
asset: context.asset,
|
|
2903
|
-
amount: decimal(context.snapshot.totalValueBase, context.snapshot.baseAssetDecimals, "snapshot")
|
|
2904
|
-
});
|
|
2905
|
-
const idle = assetAddressValue(
|
|
2906
|
-
context.snapshot.tokenBalances,
|
|
2907
|
-
metadata.address
|
|
2908
|
-
);
|
|
2909
|
-
tokens.push({
|
|
2910
|
-
chain: "BASE",
|
|
2911
|
-
chainId: 8453,
|
|
2912
|
-
asset: context.asset,
|
|
2913
|
-
amount: decimal(idle, metadata.decimals, "snapshot")
|
|
2914
|
-
});
|
|
2915
|
-
positions.push(
|
|
2916
|
-
...context.positions.map(
|
|
2917
|
-
(entry) => position(
|
|
2918
|
-
entry,
|
|
2919
|
-
context.asset,
|
|
2920
|
-
context.snapshot.baseAssetDecimals
|
|
2921
|
-
)
|
|
2922
|
-
)
|
|
2923
|
-
);
|
|
2924
|
-
totalUsd += usd(
|
|
2925
|
-
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
2926
|
-
context.snapshot.baseAssetDecimals,
|
|
2927
|
-
context.snapshot.baseAssetPriceUsd
|
|
2928
|
-
);
|
|
2929
|
-
}
|
|
2930
|
-
return {
|
|
2931
|
-
...contexts.length === 1 ? { smartWallet: contexts[0].wallet.walletAddress } : {},
|
|
2932
|
-
totalBalance: String(totalUsd),
|
|
2933
|
-
totalBalanceAsset: "usdc",
|
|
2934
|
-
assetBalances,
|
|
2935
|
-
tokens,
|
|
2936
|
-
positions
|
|
2937
|
-
};
|
|
2938
|
-
}
|
|
2939
|
-
function mapYieldseekerEarnings(contexts) {
|
|
2940
|
-
const tokens = [];
|
|
2941
|
-
let lifetimeEarnings = 0;
|
|
2942
|
-
for (const context of contexts) {
|
|
2943
|
-
const amount = raw(context.snapshot.cumulativeYieldBase, "snapshot");
|
|
2944
|
-
tokens.push({
|
|
2945
|
-
chain: "BASE",
|
|
2946
|
-
chainId: 8453,
|
|
2947
|
-
asset: context.asset,
|
|
2948
|
-
amount: (0, import_viem5.formatUnits)(amount, context.snapshot.baseAssetDecimals)
|
|
2949
|
-
});
|
|
2950
|
-
lifetimeEarnings += usd(
|
|
2951
|
-
amount,
|
|
2952
|
-
context.snapshot.baseAssetDecimals,
|
|
2953
|
-
context.snapshot.baseAssetPriceUsd
|
|
2954
|
-
);
|
|
2955
|
-
}
|
|
2956
|
-
return {
|
|
2957
|
-
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
2958
|
-
lifetimeEarnings,
|
|
2959
|
-
tokens
|
|
2960
|
-
};
|
|
2961
|
-
}
|
|
2962
|
-
function apyForDays(context, days, now) {
|
|
2963
|
-
if (days === "7D") return percent(context.snapshot.apy7d);
|
|
2964
|
-
if (days === "30D") return percent(context.snapshot.apy30d);
|
|
2965
|
-
const apy = calculateSnapshotApy(context.historic?.dailyYieldSnapshots ?? [], 14, now);
|
|
2966
|
-
const apyPercent = apy === void 0 ? void 0 : apy * 100;
|
|
2967
|
-
return apyPercent !== void 0 && Number.isFinite(apyPercent) ? apyPercent : void 0;
|
|
2968
|
-
}
|
|
2969
|
-
function dailyApy(point) {
|
|
2970
|
-
const total = raw(point.totalValueBase, "historic position");
|
|
2971
|
-
const earned = raw(point.dailyYieldBase, "historic position");
|
|
2972
|
-
const principal = total - earned;
|
|
2973
|
-
if (principal <= 0n || earned === 0n) return 0;
|
|
2974
|
-
return Number(earned) / Number(principal) * 365 * 100;
|
|
2975
|
-
}
|
|
2976
|
-
function aggregateHistory(contexts, dayCount, now) {
|
|
2977
|
-
const today = new Date(now).toISOString().slice(0, 10);
|
|
2978
|
-
const cutoff = new Date(Date.parse(today) - (dayCount - 1) * 864e5).toISOString().slice(0, 10);
|
|
2979
|
-
const assets = new Set(contexts.map((context) => context.asset.toUpperCase()));
|
|
2980
|
-
const unit = assets.size === 1 ? [...assets][0] : "USD";
|
|
2981
|
-
const byDate = /* @__PURE__ */ new Map();
|
|
2982
|
-
for (const context of contexts) {
|
|
2983
|
-
const points = context.historic?.dailyYieldSnapshots ?? [];
|
|
2984
|
-
for (const point of points) {
|
|
2985
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(point.date) || !Number.isFinite(Date.parse(point.date)) || point.date < cutoff || point.date > today) continue;
|
|
2986
|
-
const tokens = Number(decimal(point.totalValueBase, point.baseAssetDecimals, "historic position"));
|
|
2987
|
-
const amount = assets.size === 1 ? tokens : tokens * point.baseAssetPriceUsd;
|
|
2988
|
-
if (!Number.isFinite(amount) || amount < 0) {
|
|
2989
|
-
invalid("historic position", "expected a finite non-negative balance");
|
|
2990
|
-
}
|
|
2991
|
-
const current = byDate.get(point.date) ?? { weighted: 0, amount: 0 };
|
|
2992
|
-
current.weighted += dailyApy(point) * amount;
|
|
2993
|
-
current.amount += amount;
|
|
2994
|
-
byDate.set(point.date, current);
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
return [...byDate.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([date, value]) => ({
|
|
2998
|
-
date,
|
|
2999
|
-
apy: value.amount > 0 ? value.weighted / value.amount : 0,
|
|
3000
|
-
historicalBalance: { amount: value.amount, unit }
|
|
3001
|
-
}));
|
|
3002
|
-
}
|
|
3003
|
-
function mapYieldseekerApy(walletAddress, contexts, days, now = Date.now()) {
|
|
3004
|
-
let weighted = 0;
|
|
3005
|
-
let totalUsd = 0;
|
|
3006
|
-
const byAsset = {};
|
|
3007
|
-
for (const context of contexts) {
|
|
3008
|
-
const valueUsd = usd(
|
|
3009
|
-
raw(context.snapshot.totalValueBase, "snapshot"),
|
|
3010
|
-
context.snapshot.baseAssetDecimals,
|
|
3011
|
-
context.snapshot.baseAssetPriceUsd
|
|
3012
|
-
);
|
|
3013
|
-
const apy = apyForDays(context, days, now);
|
|
3014
|
-
if (apy === void 0) continue;
|
|
3015
|
-
weighted += apy * valueUsd;
|
|
3016
|
-
totalUsd += valueUsd;
|
|
3017
|
-
byAsset[context.asset] = apy;
|
|
3018
|
-
}
|
|
3019
|
-
const dayCount = Number(days.slice(0, -1));
|
|
3020
|
-
return {
|
|
3021
|
-
walletAddress,
|
|
3022
|
-
...days !== "14D" || Object.keys(byAsset).length > 0 ? { weightedApyAfterFee: totalUsd > 0 ? weighted / totalUsd : 0 } : {},
|
|
3023
|
-
apyByChainAndAsset: { 8453: byAsset },
|
|
3024
|
-
history: aggregateHistory(contexts, dayCount, now)
|
|
3025
|
-
};
|
|
3026
|
-
}
|
|
3027
|
-
function actionType(value) {
|
|
3028
|
-
const normalized = value.toLowerCase();
|
|
3029
|
-
if (normalized.includes("deposit")) return "Deposit";
|
|
3030
|
-
if (normalized.includes("withdraw")) return "Withdraw";
|
|
3031
|
-
if (normalized.includes("yield") || normalized.includes("earn"))
|
|
3032
|
-
return "Earned";
|
|
3033
|
-
return "Rebalance";
|
|
3034
|
-
}
|
|
3035
|
-
function transactionHashes(details) {
|
|
3036
|
-
if (!details) return [];
|
|
3037
|
-
const values = [
|
|
3038
|
-
details.transactionHash,
|
|
3039
|
-
details.txHash,
|
|
3040
|
-
...Array.isArray(details.transactionHashes) ? details.transactionHashes : [],
|
|
3041
|
-
...Array.isArray(details.txHashes) ? details.txHashes : []
|
|
3042
|
-
];
|
|
3043
|
-
return values.filter(
|
|
3044
|
-
(value) => typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value)
|
|
3045
|
-
).filter((value, index, all) => all.indexOf(value) === index);
|
|
3046
|
-
}
|
|
3047
|
-
function actionEntry(action) {
|
|
3048
|
-
if (action.actionType.toLowerCase().includes("withdraw")) return void 0;
|
|
3049
|
-
return {
|
|
3050
|
-
agent: "yieldseeker",
|
|
3051
|
-
action: actionType(action.actionType),
|
|
3052
|
-
date: action.createdDate,
|
|
3053
|
-
oldApy: null,
|
|
3054
|
-
newApy: null,
|
|
3055
|
-
transactions: [
|
|
3056
|
-
{
|
|
3057
|
-
txHashes: transactionHashes(action.details),
|
|
3058
|
-
chainId: 8453
|
|
3059
|
-
}
|
|
3060
|
-
],
|
|
3061
|
-
rebalanceLog: []
|
|
3062
|
-
};
|
|
3063
|
-
}
|
|
3064
|
-
function depositDestination(context, movement) {
|
|
3065
|
-
const to = movement.toAddress.toLowerCase();
|
|
3066
|
-
const option = context.positions.find(({ yieldOption }) => yieldOption.chainId === movement.chainId && yieldOption.assetAddress.toLowerCase() === context.agent.assetAddress.toLowerCase() && yieldOption.address.toLowerCase() === to)?.yieldOption;
|
|
3067
|
-
if (option) return { protocol: option.provider || "Vault", pool: option.name, tokenSymbol: context.asset };
|
|
3068
|
-
const receipt = context.historic?.assets.find((asset) => asset.chainId === movement.chainId && !asset.isSpam && asset.address.toLowerCase() === to && asset.address.toLowerCase() !== context.agent.assetAddress.toLowerCase());
|
|
3069
|
-
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);
|
|
3070
|
-
if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
|
|
3071
|
-
return void 0;
|
|
3072
|
-
}
|
|
3073
|
-
function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
|
|
3074
|
-
const from = movement.fromAddress.toLowerCase();
|
|
3075
|
-
const to = movement.toAddress.toLowerCase();
|
|
3076
|
-
const owner = ownerAddress.toLowerCase();
|
|
3077
|
-
const agentWallet = wallet.walletAddress.toLowerCase();
|
|
3078
|
-
const baseAsset = agent.assetAddress.toLowerCase();
|
|
3079
|
-
if (movement.chainId !== agent.chainId || movement.assetAddress.toLowerCase() !== baseAsset) {
|
|
3080
|
-
return void 0;
|
|
3081
|
-
}
|
|
3082
|
-
let action;
|
|
3083
|
-
if (to === agentWallet && !vaultAddresses.has(from)) {
|
|
3084
|
-
action = "Top up";
|
|
3085
|
-
} else if (from === agentWallet && to === owner) {
|
|
3086
|
-
action = "Withdraw";
|
|
3087
|
-
} else if (from === agentWallet && destination) {
|
|
3088
|
-
action = "Deposit";
|
|
3089
|
-
}
|
|
3090
|
-
if (!action) return void 0;
|
|
3091
|
-
return {
|
|
3092
|
-
agent: "yieldseeker",
|
|
3093
|
-
action,
|
|
3094
|
-
...action === "Deposit" && destination ? { positions: [{
|
|
3095
|
-
...destination,
|
|
3096
|
-
amount: decimal(movement.assetAmount, YIELDSEEKER_ASSET_METADATA[asset].decimals, "historic position")
|
|
3097
|
-
}] } : {},
|
|
3098
|
-
date: movement.blockDate,
|
|
3099
|
-
oldApy: null,
|
|
3100
|
-
newApy: null,
|
|
3101
|
-
transactions: [
|
|
3102
|
-
{
|
|
3103
|
-
txHashes: [movement.transactionHash],
|
|
3104
|
-
chainId: agent.chainId,
|
|
3105
|
-
tokenSymbol: asset,
|
|
3106
|
-
amount: decimal(
|
|
3107
|
-
movement.assetAmount,
|
|
3108
|
-
YIELDSEEKER_ASSET_METADATA[asset].decimals,
|
|
3109
|
-
"historic position"
|
|
3110
|
-
)
|
|
3111
|
-
}
|
|
3112
|
-
],
|
|
3113
|
-
rebalanceLog: []
|
|
3114
|
-
};
|
|
3115
|
-
}
|
|
3116
|
-
function mapYieldseekerHistory(contexts, options) {
|
|
3117
|
-
const entries = contexts.flatMap((context) => {
|
|
3118
|
-
const seenMovements = /* @__PURE__ */ new Set();
|
|
3119
|
-
const movements = (context.historic?.movements ?? []).filter((movement) => {
|
|
3120
|
-
const key2 = `${movement.chainId}:${movement.transactionHash.toLowerCase()}:${movement.logIndex}`;
|
|
3121
|
-
if (seenMovements.has(key2)) return false;
|
|
3122
|
-
seenMovements.add(key2);
|
|
3123
|
-
return true;
|
|
3124
|
-
});
|
|
3125
|
-
return [
|
|
3126
|
-
...movements.map(
|
|
3127
|
-
(movement) => movementEntry(
|
|
3128
|
-
movement,
|
|
3129
|
-
context.wallet,
|
|
3130
|
-
context.agent,
|
|
3131
|
-
context.asset,
|
|
3132
|
-
options.ownerAddress,
|
|
3133
|
-
options.vaultAddresses,
|
|
3134
|
-
depositDestination(context, movement)
|
|
3135
|
-
)
|
|
3136
|
-
),
|
|
3137
|
-
...(context.actions ?? []).map(actionEntry)
|
|
3138
|
-
].filter((entry) => entry !== void 0);
|
|
3139
|
-
});
|
|
3140
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
3141
|
-
const ungrouped = [];
|
|
3142
|
-
for (const entry of entries) {
|
|
3143
|
-
const tx = entry.transactions[0];
|
|
3144
|
-
const hash = tx?.txHashes[0];
|
|
3145
|
-
if (!hash) {
|
|
3146
|
-
ungrouped.push(entry);
|
|
3147
|
-
continue;
|
|
3148
|
-
}
|
|
3149
|
-
const key2 = `${entry.agent}:${entry.action}:${tx.chainId}:${hash.toLowerCase()}`;
|
|
3150
|
-
const previous = grouped.get(key2);
|
|
3151
|
-
if (!previous) {
|
|
3152
|
-
grouped.set(key2, entry);
|
|
3153
|
-
continue;
|
|
3154
|
-
}
|
|
3155
|
-
if (entry.action === "Deposit" && entry.positions?.length) {
|
|
3156
|
-
if (!previous.positions?.length) {
|
|
3157
|
-
grouped.set(key2, entry);
|
|
3158
|
-
continue;
|
|
3159
|
-
}
|
|
3160
|
-
previous.positions.push(...entry.positions);
|
|
3161
|
-
previous.transactions.push(...entry.transactions);
|
|
3162
|
-
}
|
|
3163
|
-
}
|
|
3164
|
-
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));
|
|
3165
|
-
return {
|
|
3166
|
-
data: filtered.slice(0, options.limit),
|
|
3167
|
-
// v1 returns the whole action/movement collection and defines no cursor.
|
|
3168
|
-
// Report a terminal page so callers never loop over the same prefix.
|
|
3169
|
-
hasMore: false
|
|
3170
|
-
};
|
|
3171
|
-
}
|
|
3172
|
-
function mapYieldseekerProfile(address, contexts) {
|
|
3173
|
-
const protocols = /* @__PURE__ */ new Set();
|
|
3174
|
-
for (const context of contexts) {
|
|
3175
|
-
for (const current of context.positions) {
|
|
3176
|
-
if (current.yieldOption?.provider) {
|
|
3177
|
-
protocols.add(String(current.yieldOption.provider));
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
}
|
|
3181
|
-
return {
|
|
3182
|
-
address,
|
|
3183
|
-
smartWallet: contexts[0]?.wallet.walletAddress ?? "0x0000000000000000000000000000000000000000",
|
|
3184
|
-
chains: contexts.length > 0 ? [8453] : [],
|
|
3185
|
-
hasActiveSessionKey: contexts.some(
|
|
3186
|
-
(context) => context.wallet.initializedDate != null
|
|
3187
|
-
),
|
|
3188
|
-
protocols: [...protocols]
|
|
3189
|
-
};
|
|
3190
|
-
}
|
|
3191
|
-
function mapYieldseekerAgentApy(options, days) {
|
|
3192
|
-
const perAsset = {};
|
|
3193
|
-
const all = [];
|
|
3194
|
-
for (const entry of options) {
|
|
3195
|
-
const apys = entry.yieldOptions.map(
|
|
3196
|
-
(option) => publicApyAfterYieldseekerFee(riskAdjustedApyForDays(option, days))
|
|
3197
|
-
).filter(Number.isFinite);
|
|
3198
|
-
if (apys.length === 0) continue;
|
|
3199
|
-
const average = apys.reduce((sum, value) => sum + value, 0) / apys.length;
|
|
3200
|
-
perAsset[entry.asset] = average;
|
|
3201
|
-
all.push(average);
|
|
3202
|
-
}
|
|
3203
|
-
return {
|
|
3204
|
-
averageApy: all.length > 0 ? all.reduce((sum, value) => sum + value, 0) / all.length : 0,
|
|
3205
|
-
detailedApys: { apyPerAsset: { 8453: perAsset } }
|
|
3206
|
-
};
|
|
3207
|
-
}
|
|
3208
|
-
|
|
3209
|
-
// src/agents/yieldseeker/yieldseeker.agent.ts
|
|
3210
|
-
var OWNEY_AGENT_NAME = "owney";
|
|
3211
|
-
var YIELDSEEKER_USERNAME_PREFIX = "owney_";
|
|
3212
|
-
var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
|
|
3213
|
-
var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
|
|
3214
|
-
var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
|
|
3215
|
-
function generateYieldseekerUsername() {
|
|
3216
|
-
const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
|
|
3217
|
-
return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
|
|
3218
|
-
}
|
|
3219
|
-
function isUsernameConflict(error) {
|
|
3220
|
-
if (!(error instanceof YieldseekerApiError)) return false;
|
|
3221
|
-
const code = error.providerCode.toUpperCase();
|
|
3222
|
-
return code.includes("USERNAME") && (code.includes("TAKEN") || code.includes("EXIST") || code.includes("UNAVAILABLE") || code.includes("IN_USE"));
|
|
3223
|
-
}
|
|
3224
|
-
var YIELDSEEKER_AGENT_WALLET_ABI = [
|
|
3225
|
-
{
|
|
3226
|
-
type: "function",
|
|
3227
|
-
name: "withdrawAssetToUser",
|
|
3228
|
-
stateMutability: "nonpayable",
|
|
3229
|
-
inputs: [
|
|
3230
|
-
{ name: "recipient", type: "address" },
|
|
3231
|
-
{ name: "asset", type: "address" },
|
|
3232
|
-
{ name: "amount", type: "uint256" }
|
|
3233
|
-
],
|
|
3234
|
-
outputs: []
|
|
3235
|
-
},
|
|
3236
|
-
{
|
|
3237
|
-
type: "function",
|
|
3238
|
-
name: "withdrawAllAssetToUser",
|
|
3239
|
-
stateMutability: "nonpayable",
|
|
3240
|
-
inputs: [
|
|
3241
|
-
{ name: "recipient", type: "address" },
|
|
3242
|
-
{ name: "asset", type: "address" }
|
|
3243
|
-
],
|
|
3244
|
-
outputs: []
|
|
3245
|
-
}
|
|
3246
|
-
];
|
|
3247
|
-
function query(params) {
|
|
3248
|
-
const search = new URLSearchParams();
|
|
3249
|
-
for (const [key2, value] of Object.entries(params)) {
|
|
3250
|
-
if (value !== void 0) search.set(key2, String(value));
|
|
3251
|
-
}
|
|
3252
|
-
const encoded = search.toString();
|
|
3253
|
-
return encoded ? `?${encoded}` : "";
|
|
3254
|
-
}
|
|
3255
|
-
var YieldseekerAgent = class {
|
|
3256
|
-
id = "yieldseeker";
|
|
3257
|
-
balanceComposition = "tokens-plus-positions";
|
|
3258
|
-
supportedChainIds = [8453];
|
|
3259
|
-
supportedAssets = [
|
|
3260
|
-
{
|
|
3261
|
-
chainId: 8453,
|
|
3262
|
-
chain: "BASE",
|
|
3263
|
-
assets: [
|
|
3264
|
-
{ symbol: "USDC", minDepositAmount: "10000000" },
|
|
3265
|
-
{ symbol: "WETH", minDepositAmount: "1" }
|
|
3266
|
-
]
|
|
3267
|
-
}
|
|
3268
|
-
];
|
|
3269
|
-
api;
|
|
3270
|
-
auth;
|
|
3271
|
-
transactionExecutor;
|
|
3272
|
-
unwindReceiptWaiter;
|
|
3273
|
-
agentContexts = /* @__PURE__ */ new Map();
|
|
3274
|
-
users = /* @__PURE__ */ new Map();
|
|
3275
|
-
pendingAgents = /* @__PURE__ */ new Map();
|
|
3276
|
-
yieldOptions = /* @__PURE__ */ new Map();
|
|
3277
|
-
pendingYieldOptions = /* @__PURE__ */ new Map();
|
|
3278
|
-
constructor(owneyApiKey, options = {}) {
|
|
3279
|
-
this.api = new YieldseekerApiClient(
|
|
3280
|
-
owneyApiKey,
|
|
3281
|
-
options.baseUrl ?? getYieldseekerProxyBaseUrl(),
|
|
3282
|
-
options.fetchFn
|
|
3283
|
-
);
|
|
3284
|
-
this.auth = new YieldseekerAuth(options.auth);
|
|
3285
|
-
this.transactionExecutor = options.transactionExecutor;
|
|
3286
|
-
this.unwindReceiptWaiter = options.unwindReceiptWaiter;
|
|
3287
|
-
}
|
|
3288
|
-
async disconnect() {
|
|
3289
|
-
this.auth.clear();
|
|
3290
|
-
for (const key2 of this.users.keys()) {
|
|
3291
|
-
const [walletAddress, chainId] = key2.split(":");
|
|
3292
|
-
clearYieldseekerIdentity(walletAddress, Number(chainId));
|
|
3293
|
-
}
|
|
3294
|
-
this.users.clear();
|
|
3295
|
-
this.agentContexts.clear();
|
|
3296
|
-
this.pendingAgents.clear();
|
|
3297
|
-
}
|
|
3298
|
-
async activateAgent(state, chainId, asset) {
|
|
3299
|
-
this.assertChain(chainId);
|
|
3300
|
-
const targetAsset = asset ?? "USDC";
|
|
3301
|
-
this.assertAsset(targetAsset);
|
|
3302
|
-
await this.ensureAgent(state, chainId, targetAsset);
|
|
3303
|
-
}
|
|
3304
|
-
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
3305
|
-
this.assertChain(chainId);
|
|
3306
|
-
this.assertAsset(asset);
|
|
3307
|
-
if (BigInt(amount) <= 0n) {
|
|
3308
|
-
throw new OwneyError(
|
|
3309
|
-
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
3310
|
-
"Yieldseeker deposits must be greater than zero.",
|
|
3311
|
-
{ amount, minDepositAmount: "1" },
|
|
3312
|
-
this.id
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
|
-
const context = await this.ensureAgent(state, chainId, asset);
|
|
3316
|
-
let txHash;
|
|
3317
|
-
try {
|
|
3318
|
-
if (depositCallback) {
|
|
3319
|
-
provideDepositVerificationContext(depositCallback, {
|
|
3320
|
-
agentId: "yieldseeker",
|
|
3321
|
-
signature: await this.auth.getToken(state, chainId),
|
|
3322
|
-
userId: context.user.userId,
|
|
3323
|
-
yieldseekerAgentId: context.agent.agentId
|
|
3324
|
-
});
|
|
3325
|
-
txHash = await depositCallback(
|
|
3326
|
-
context.wallet.walletAddress,
|
|
3327
|
-
chainId,
|
|
3328
|
-
amount
|
|
3329
|
-
);
|
|
3330
|
-
await this.waitForReceipt(state, chainId, txHash);
|
|
3331
|
-
} else {
|
|
3332
|
-
txHash = await this.submitTransaction(state, chainId, {
|
|
3333
|
-
from: (0, import_viem6.getAddress)(state.walletAddress),
|
|
3334
|
-
to: YIELDSEEKER_ASSET_METADATA[asset].address,
|
|
3335
|
-
data: (0, import_viem6.encodeFunctionData)({
|
|
3336
|
-
abi: import_viem6.erc20Abi,
|
|
3337
|
-
functionName: "transfer",
|
|
3338
|
-
args: [(0, import_viem6.getAddress)(context.wallet.walletAddress), BigInt(amount)]
|
|
3339
|
-
}),
|
|
3340
|
-
value: "0",
|
|
3341
|
-
chainId
|
|
3342
|
-
});
|
|
3343
|
-
}
|
|
3344
|
-
} finally {
|
|
3345
|
-
await this.refreshSnapshotAfterMovement(
|
|
3346
|
-
state,
|
|
3347
|
-
chainId,
|
|
3348
|
-
context,
|
|
3349
|
-
"deposit"
|
|
3350
|
-
);
|
|
3351
|
-
}
|
|
3352
|
-
return {
|
|
3353
|
-
txHash,
|
|
3354
|
-
smartWallet: context.wallet.walletAddress,
|
|
3355
|
-
amount
|
|
3356
|
-
};
|
|
3357
|
-
}
|
|
3358
|
-
async withdraw(state, chainId, asset, amount) {
|
|
3359
|
-
this.assertChain(chainId);
|
|
3360
|
-
this.assertAsset(asset);
|
|
3361
|
-
if (amount !== void 0 && BigInt(amount) <= 0n) {
|
|
3362
|
-
throw new OwneyError(
|
|
3363
|
-
"WITHDRAW_FAILED",
|
|
3364
|
-
"Yieldseeker withdrawals must be greater than zero.",
|
|
3365
|
-
{ amount },
|
|
3366
|
-
this.id
|
|
3367
|
-
);
|
|
3368
|
-
}
|
|
3369
|
-
const context = await this.findAgent(state, chainId, asset);
|
|
3370
|
-
if (!context) {
|
|
3371
|
-
throw new OwneyError(
|
|
3372
|
-
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3373
|
-
`No Yieldseeker ${asset} agent exists for this wallet.`,
|
|
3374
|
-
{ asset, available: "0" },
|
|
3375
|
-
this.id
|
|
3376
|
-
);
|
|
3377
|
-
}
|
|
3378
|
-
try {
|
|
3379
|
-
const portfolio = await this.loadPortfolioContext(
|
|
3380
|
-
state,
|
|
3381
|
-
chainId,
|
|
3382
|
-
context
|
|
3383
|
-
);
|
|
3384
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3385
|
-
const idleEntry = Object.entries(portfolio.snapshot.tokenBalances).find(
|
|
3386
|
-
([address]) => address.toLowerCase() === metadata.address.toLowerCase()
|
|
3387
|
-
);
|
|
3388
|
-
const idle = BigInt(idleEntry?.[1] ?? "0");
|
|
3389
|
-
const deployed = portfolio.positions.reduce(
|
|
3390
|
-
(total, position2) => total + BigInt(position2.withdrawableAssetsRaw),
|
|
3391
|
-
0n
|
|
3392
|
-
);
|
|
3393
|
-
const totalAvailable = idle + deployed;
|
|
3394
|
-
const requested = amount === void 0 ? totalAvailable : BigInt(amount);
|
|
3395
|
-
if (requested > totalAvailable) {
|
|
3396
|
-
throw new OwneyError(
|
|
3397
|
-
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
3398
|
-
`Requested withdrawal "${requested}" exceeds available Yieldseeker balance "${totalAvailable}" for asset "${asset}".`,
|
|
3399
|
-
{
|
|
3400
|
-
asset,
|
|
3401
|
-
requested: requested.toString(),
|
|
3402
|
-
available: totalAvailable.toString()
|
|
3403
|
-
},
|
|
3404
|
-
this.id
|
|
3405
|
-
);
|
|
3406
|
-
}
|
|
3407
|
-
let remaining = requested > idle ? requested - idle : 0n;
|
|
3408
|
-
for (const position2 of portfolio.positions) {
|
|
3409
|
-
if (remaining === 0n) break;
|
|
3410
|
-
const available = BigInt(position2.withdrawableAssetsRaw);
|
|
3411
|
-
if (available <= 0n) continue;
|
|
3412
|
-
const assetsRaw = available < remaining ? available : remaining;
|
|
3413
|
-
const response = await this.walletRequest(
|
|
3414
|
-
state,
|
|
3415
|
-
chainId,
|
|
3416
|
-
this.agentPath(context, "withdraw-from-position"),
|
|
3417
|
-
{
|
|
3418
|
-
method: "POST",
|
|
3419
|
-
body: {
|
|
3420
|
-
chainId,
|
|
3421
|
-
vaultAddress: position2.yieldOption.address,
|
|
3422
|
-
assetsRaw: assetsRaw.toString()
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
);
|
|
3426
|
-
if (!this.isTransactionHash(response?.transactionHash)) {
|
|
3427
|
-
throw this.invalidResponse("position withdrawal");
|
|
3428
|
-
}
|
|
3429
|
-
await this.waitForReceipt(state, chainId, response.transactionHash);
|
|
3430
|
-
remaining -= assetsRaw;
|
|
3431
|
-
}
|
|
3432
|
-
if (remaining > 0n) {
|
|
3433
|
-
throw this.invalidResponse("yield positions", {
|
|
3434
|
-
reason: "Withdrawable positions could not cover the request.",
|
|
3435
|
-
remaining: remaining.toString()
|
|
3436
|
-
});
|
|
3437
|
-
}
|
|
3438
|
-
const account = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3439
|
-
const txHash = await this.submitTransaction(state, chainId, {
|
|
3440
|
-
from: account,
|
|
3441
|
-
to: (0, import_viem6.getAddress)(context.wallet.walletAddress),
|
|
3442
|
-
data: amount === void 0 ? (0, import_viem6.encodeFunctionData)({
|
|
3443
|
-
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3444
|
-
functionName: "withdrawAllAssetToUser",
|
|
3445
|
-
args: [account, metadata.address]
|
|
3446
|
-
}) : (0, import_viem6.encodeFunctionData)({
|
|
3447
|
-
abi: YIELDSEEKER_AGENT_WALLET_ABI,
|
|
3448
|
-
functionName: "withdrawAssetToUser",
|
|
3449
|
-
args: [account, metadata.address, requested]
|
|
3450
|
-
}),
|
|
3451
|
-
value: "0",
|
|
3452
|
-
chainId
|
|
3453
|
-
});
|
|
3454
|
-
return {
|
|
3455
|
-
txHash,
|
|
3456
|
-
type: amount === void 0 ? "full" : "partial",
|
|
3457
|
-
amount: requested.toString()
|
|
3458
|
-
};
|
|
3459
|
-
} finally {
|
|
3460
|
-
await this.refreshSnapshotAfterMovement(
|
|
3461
|
-
state,
|
|
3462
|
-
chainId,
|
|
3463
|
-
context,
|
|
3464
|
-
"withdrawal"
|
|
3465
|
-
);
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
async getBalances(state, chainId) {
|
|
3469
|
-
this.assertChain(chainId);
|
|
3470
|
-
return mapYieldseekerBalances(await this.loadPortfolio(state, chainId, {}));
|
|
3471
|
-
}
|
|
3472
|
-
async getEarnings(state, chainId) {
|
|
3473
|
-
this.assertChain(chainId);
|
|
3474
|
-
return mapYieldseekerEarnings(await this.loadPortfolio(state, chainId, {}));
|
|
3475
|
-
}
|
|
3476
|
-
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
3477
|
-
this.assertChain(chainId);
|
|
3478
|
-
const asset = tokenSymbol?.toUpperCase();
|
|
3479
|
-
if (asset !== void 0) this.assertAsset(asset);
|
|
3480
|
-
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3481
|
-
...asset ? { asset } : {},
|
|
3482
|
-
historic: true
|
|
3483
|
-
});
|
|
3484
|
-
return mapYieldseekerApy(state.walletAddress, contexts, days);
|
|
3485
|
-
}
|
|
3486
|
-
async getHistory(state, chainId, options) {
|
|
3487
|
-
this.assertChain(chainId);
|
|
3488
|
-
const asset = options?.tokenSymbol?.toUpperCase();
|
|
3489
|
-
if (asset !== void 0) this.assertAsset(asset);
|
|
3490
|
-
const contexts = await this.loadPortfolio(state, chainId, {
|
|
3491
|
-
...asset ? { asset } : {},
|
|
3492
|
-
historic: true,
|
|
3493
|
-
actions: true
|
|
3494
|
-
});
|
|
3495
|
-
const catalog = await Promise.all(
|
|
3496
|
-
[...new Set(contexts.map((context) => context.asset))].map(
|
|
3497
|
-
(contextAsset) => this.loadYieldOptions(contextAsset)
|
|
3498
|
-
)
|
|
3499
|
-
);
|
|
3500
|
-
const vaultAddresses = new Set(
|
|
3501
|
-
catalog.flat().filter(
|
|
3502
|
-
(yieldOption) => yieldOption.chainId === chainId && (0, import_viem6.isAddress)(yieldOption.address)
|
|
3503
|
-
).map((yieldOption) => yieldOption.address.toLowerCase())
|
|
3504
|
-
);
|
|
3505
|
-
return mapYieldseekerHistory(contexts, {
|
|
3506
|
-
limit: options?.limit ?? 10,
|
|
3507
|
-
ownerAddress: state.walletAddress,
|
|
3508
|
-
vaultAddresses,
|
|
3509
|
-
...options?.fromDate ? { fromDate: options.fromDate } : {},
|
|
3510
|
-
...options?.toDate ? { toDate: options.toDate } : {}
|
|
3511
|
-
});
|
|
3512
|
-
}
|
|
3513
|
-
async getUserProfile(state, chainId) {
|
|
3514
|
-
this.assertChain(chainId);
|
|
3515
|
-
return mapYieldseekerProfile(
|
|
3516
|
-
state.walletAddress,
|
|
3517
|
-
await this.loadPortfolio(state, chainId, {})
|
|
3518
|
-
);
|
|
3519
|
-
}
|
|
3520
|
-
async getAgentApy(days, options) {
|
|
3521
|
-
this.assertOptionalChain(options?.chainId);
|
|
3522
|
-
const requested = options?.tokenSymbol?.toUpperCase();
|
|
3523
|
-
if (requested !== void 0) this.assertAsset(requested);
|
|
3524
|
-
const assets = requested ? [requested] : ["USDC", "WETH"];
|
|
3525
|
-
const values = await Promise.all(
|
|
3526
|
-
assets.map(async (asset) => {
|
|
3527
|
-
return { asset, yieldOptions: await this.loadYieldOptions(asset) };
|
|
3528
|
-
})
|
|
3529
|
-
);
|
|
3530
|
-
return mapYieldseekerAgentApy(values, days);
|
|
3531
|
-
}
|
|
3532
|
-
async loadYieldOptions(asset) {
|
|
3533
|
-
const cached = this.yieldOptions.get(asset);
|
|
3534
|
-
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
3535
|
-
const pending = this.pendingYieldOptions.get(asset);
|
|
3536
|
-
if (pending) return pending;
|
|
3537
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3538
|
-
const request = this.api.request(
|
|
3539
|
-
`/chains/8453/assets/${metadata.address}/yield-options`
|
|
3540
|
-
).then((response) => {
|
|
3541
|
-
if (!Array.isArray(response?.yieldOptions)) {
|
|
3542
|
-
throw this.invalidResponse("yield options");
|
|
3543
|
-
}
|
|
3544
|
-
this.yieldOptions.set(asset, {
|
|
3545
|
-
expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
|
|
3546
|
-
value: response.yieldOptions
|
|
3547
|
-
});
|
|
3548
|
-
return response.yieldOptions;
|
|
3549
|
-
}).finally(() => this.pendingYieldOptions.delete(asset));
|
|
3550
|
-
this.pendingYieldOptions.set(asset, request);
|
|
3551
|
-
return request;
|
|
3552
|
-
}
|
|
3553
|
-
userKey(state, chainId) {
|
|
3554
|
-
return `${state.walletAddress.toLowerCase()}:${chainId}`;
|
|
3555
|
-
}
|
|
3556
|
-
contextKey(state, chainId, asset) {
|
|
3557
|
-
return `${this.userKey(state, chainId)}:${asset}`;
|
|
3558
|
-
}
|
|
3559
|
-
async resolveUser(state, chainId) {
|
|
3560
|
-
const key2 = this.userKey(state, chainId);
|
|
3561
|
-
const inMemory = this.users.get(key2);
|
|
3562
|
-
if (inMemory) return inMemory;
|
|
3563
|
-
const persisted = readYieldseekerIdentity(state.walletAddress, chainId);
|
|
3564
|
-
if (persisted) {
|
|
3565
|
-
this.users.set(key2, persisted);
|
|
3566
|
-
return persisted;
|
|
3567
|
-
}
|
|
3568
|
-
const walletAddress = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3569
|
-
let user = null;
|
|
3570
|
-
try {
|
|
3571
|
-
const login = await this.providerRequest(
|
|
3572
|
-
state,
|
|
3573
|
-
chainId,
|
|
3574
|
-
"/users/login-with-wallet",
|
|
3575
|
-
{ method: "POST", body: { walletAddress } }
|
|
3576
|
-
);
|
|
3577
|
-
user = login?.user ?? null;
|
|
3578
|
-
if (!user) {
|
|
3579
|
-
throw this.invalidResponse("wallet login", {
|
|
3580
|
-
reason: "A successful login returned no user."
|
|
3581
|
-
});
|
|
3582
|
-
}
|
|
3583
|
-
} catch (error) {
|
|
3584
|
-
if (!(error instanceof YieldseekerApiError) || error.providerCode !== "NO_USER") {
|
|
3585
|
-
if (error instanceof OwneyError) throw error;
|
|
3586
|
-
throw this.mapApiError(error);
|
|
3587
|
-
}
|
|
3588
|
-
let created;
|
|
3589
|
-
for (let attempt = 0; attempt < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS; attempt += 1) {
|
|
3590
|
-
try {
|
|
3591
|
-
created = await this.providerRequest(
|
|
3592
|
-
state,
|
|
3593
|
-
chainId,
|
|
3594
|
-
"/users",
|
|
3595
|
-
{
|
|
3596
|
-
method: "POST",
|
|
3597
|
-
body: {
|
|
3598
|
-
walletAddress,
|
|
3599
|
-
username: generateYieldseekerUsername()
|
|
3600
|
-
}
|
|
3601
|
-
}
|
|
3602
|
-
);
|
|
3603
|
-
break;
|
|
3604
|
-
} catch (createError) {
|
|
3605
|
-
const canRetry = isUsernameConflict(createError) && attempt + 1 < YIELDSEEKER_USERNAME_CREATE_ATTEMPTS;
|
|
3606
|
-
if (canRetry) continue;
|
|
3607
|
-
throw this.mapApiError(createError);
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
user = created?.user ?? null;
|
|
3611
|
-
}
|
|
3612
|
-
if (!user || !/^[a-zA-Z0-9_-]{1,128}$/.test(user.userId)) {
|
|
3613
|
-
throw this.invalidResponse("wallet identity");
|
|
3614
|
-
}
|
|
3615
|
-
const resolved = { userId: user.userId };
|
|
3616
|
-
this.users.set(key2, resolved);
|
|
3617
|
-
writeYieldseekerIdentity(state.walletAddress, chainId, resolved.userId);
|
|
3618
|
-
return resolved;
|
|
3619
|
-
}
|
|
3620
|
-
forgetUser(state, chainId) {
|
|
3621
|
-
this.users.delete(this.userKey(state, chainId));
|
|
3622
|
-
clearYieldseekerIdentity(state.walletAddress, chainId);
|
|
3623
|
-
}
|
|
3624
|
-
async ensureAgent(state, chainId, asset) {
|
|
3625
|
-
const key2 = this.contextKey(state, chainId, asset);
|
|
3626
|
-
const cached = this.agentContexts.get(key2);
|
|
3627
|
-
if (cached) return cached;
|
|
3628
|
-
const pending = this.pendingAgents.get(key2);
|
|
3629
|
-
if (pending) return pending;
|
|
3630
|
-
const request = this.resolveAgent(state, chainId, asset, true).then(
|
|
3631
|
-
async (context) => {
|
|
3632
|
-
if (!context) throw this.invalidResponse("agent creation");
|
|
3633
|
-
await this.deployAgent(state, chainId, context);
|
|
3634
|
-
this.agentContexts.set(key2, context);
|
|
3635
|
-
return context;
|
|
3636
|
-
}
|
|
3637
|
-
);
|
|
3638
|
-
this.pendingAgents.set(key2, request);
|
|
3639
|
-
try {
|
|
3640
|
-
return await request;
|
|
3641
|
-
} finally {
|
|
3642
|
-
this.pendingAgents.delete(key2);
|
|
3643
|
-
}
|
|
3644
|
-
}
|
|
3645
|
-
async findAgent(state, chainId, asset) {
|
|
3646
|
-
const key2 = this.contextKey(state, chainId, asset);
|
|
3647
|
-
const cached = this.agentContexts.get(key2);
|
|
3648
|
-
if (cached) return cached;
|
|
3649
|
-
const context = await this.resolveAgent(state, chainId, asset, false);
|
|
3650
|
-
if (context) this.agentContexts.set(key2, context);
|
|
3651
|
-
return context;
|
|
3652
|
-
}
|
|
3653
|
-
async resolveAgent(state, chainId, asset, createIfMissing) {
|
|
3654
|
-
const user = await this.resolveUser(state, chainId);
|
|
3655
|
-
const response = await this.walletRequest(
|
|
3656
|
-
state,
|
|
3657
|
-
chainId,
|
|
3658
|
-
`/users/${user.userId}/agents`
|
|
3659
|
-
);
|
|
3660
|
-
if (!Array.isArray(response?.agents)) {
|
|
3661
|
-
throw this.invalidResponse("agent list");
|
|
3662
|
-
}
|
|
3663
|
-
const metadata = YIELDSEEKER_ASSET_METADATA[asset];
|
|
3664
|
-
let agent = response.agents.find(
|
|
3665
|
-
(candidate) => this.isOwneyAgent(candidate) && candidate.chainId === chainId && candidate.type === "vault" && candidate.assetAddress.toLowerCase() === metadata.address.toLowerCase()
|
|
3666
|
-
);
|
|
3667
|
-
if (!agent && createIfMissing) {
|
|
3668
|
-
const created = await this.walletRequest(
|
|
3669
|
-
state,
|
|
3670
|
-
chainId,
|
|
3671
|
-
`/users/${user.userId}/agents`,
|
|
3672
|
-
{
|
|
3673
|
-
method: "POST",
|
|
3674
|
-
body: {
|
|
3675
|
-
name: OWNEY_AGENT_NAME,
|
|
3676
|
-
emoji: "\u{1F989}",
|
|
3677
|
-
chainId,
|
|
3678
|
-
assetAddress: metadata.address,
|
|
3679
|
-
type: "vault",
|
|
3680
|
-
rulePreset: null
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
);
|
|
3684
|
-
agent = created?.agent;
|
|
3685
|
-
}
|
|
3686
|
-
if (!agent) return null;
|
|
3687
|
-
this.assertAgent(agent);
|
|
3688
|
-
const walletResponse = await this.walletRequest(
|
|
3689
|
-
state,
|
|
3690
|
-
chainId,
|
|
3691
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3692
|
-
);
|
|
3693
|
-
if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
3694
|
-
throw this.invalidResponse("agent wallet");
|
|
3695
|
-
}
|
|
3696
|
-
return { user, agent, wallet: walletResponse.agentWallet, asset };
|
|
3697
|
-
}
|
|
3698
|
-
async loadPortfolio(state, chainId, options) {
|
|
3699
|
-
const user = await this.resolveUser(state, chainId);
|
|
3700
|
-
const response = await this.walletRequest(
|
|
3701
|
-
state,
|
|
3702
|
-
chainId,
|
|
3703
|
-
`/users/${user.userId}/agents`
|
|
3704
|
-
);
|
|
3705
|
-
if (!Array.isArray(response?.agents)) {
|
|
3706
|
-
throw this.invalidResponse("agent list");
|
|
3707
|
-
}
|
|
3708
|
-
const contexts = [];
|
|
3709
|
-
for (const agent of response.agents) {
|
|
3710
|
-
const asset = this.assetForAgent(agent);
|
|
3711
|
-
if (!this.isOwneyAgent(agent) || !asset || agent.chainId !== chainId || agent.type !== "vault" || options.asset && options.asset !== asset) {
|
|
3712
|
-
continue;
|
|
3713
|
-
}
|
|
3714
|
-
this.assertAgent(agent);
|
|
3715
|
-
const walletResponse = await this.walletRequest(
|
|
3716
|
-
state,
|
|
3717
|
-
chainId,
|
|
3718
|
-
`/users/${user.userId}/agents/${agent.agentId}/wallet`
|
|
3719
|
-
);
|
|
3720
|
-
if (!walletResponse?.agentWallet || !(0, import_viem6.isAddress)(walletResponse.agentWallet.walletAddress)) {
|
|
3721
|
-
throw this.invalidResponse("agent wallet");
|
|
3722
|
-
}
|
|
3723
|
-
const context = {
|
|
3724
|
-
user,
|
|
3725
|
-
agent,
|
|
3726
|
-
wallet: walletResponse.agentWallet,
|
|
3727
|
-
asset
|
|
3728
|
-
};
|
|
3729
|
-
this.agentContexts.set(this.contextKey(state, chainId, asset), context);
|
|
3730
|
-
contexts.push(context);
|
|
3731
|
-
}
|
|
3732
|
-
return Promise.all(
|
|
3733
|
-
contexts.map(
|
|
3734
|
-
(context) => this.loadPortfolioContext(state, chainId, context, options)
|
|
3735
|
-
)
|
|
3736
|
-
);
|
|
3737
|
-
}
|
|
3738
|
-
async loadPortfolioContext(state, chainId, context, options = {}) {
|
|
3739
|
-
const [snapshot, positions, historic, actions] = await Promise.all([
|
|
3740
|
-
this.walletRequest(
|
|
3741
|
-
state,
|
|
3742
|
-
chainId,
|
|
3743
|
-
`${this.agentPath(context, "snapshot")}${query({
|
|
3744
|
-
shouldOnlyUseRecentValue: true,
|
|
3745
|
-
shouldAllowStaleOnError: true
|
|
3746
|
-
})}`
|
|
3747
|
-
),
|
|
3748
|
-
this.walletRequest(
|
|
3749
|
-
state,
|
|
3750
|
-
chainId,
|
|
3751
|
-
this.agentPath(context, "yield-positions")
|
|
3752
|
-
),
|
|
3753
|
-
options.historic ? this.walletRequest(
|
|
3754
|
-
state,
|
|
3755
|
-
chainId,
|
|
3756
|
-
this.agentPath(context, "wallet/historic-position")
|
|
3757
|
-
) : Promise.resolve(void 0),
|
|
3758
|
-
options.actions ? this.walletRequest(
|
|
3759
|
-
state,
|
|
3760
|
-
chainId,
|
|
3761
|
-
this.agentPath(context, "actions")
|
|
3762
|
-
) : Promise.resolve(void 0)
|
|
3763
|
-
]);
|
|
3764
|
-
if (!snapshot?.agentSnapshot) {
|
|
3765
|
-
throw this.invalidResponse("agent snapshot");
|
|
3766
|
-
}
|
|
3767
|
-
if (!Array.isArray(positions?.yieldPositions)) {
|
|
3768
|
-
throw this.invalidResponse("yield positions");
|
|
3769
|
-
}
|
|
3770
|
-
return {
|
|
3771
|
-
...context,
|
|
3772
|
-
snapshot: snapshot.agentSnapshot,
|
|
3773
|
-
positions: positions.yieldPositions,
|
|
3774
|
-
...historic?.position ? { historic: historic.position } : {},
|
|
3775
|
-
...actions?.actions ? { actions: actions.actions } : {}
|
|
3776
|
-
};
|
|
3777
|
-
}
|
|
3778
|
-
async deployAgent(state, chainId, context) {
|
|
3779
|
-
if (context.wallet.initializedDate != null) return;
|
|
3780
|
-
const walletAddress = context.wallet.walletAddress.toLowerCase();
|
|
3781
|
-
const deployed = await this.walletRequest(
|
|
3782
|
-
state,
|
|
3783
|
-
chainId,
|
|
3784
|
-
this.agentPath(context, "deploy"),
|
|
3785
|
-
{ method: "POST", body: {} }
|
|
3786
|
-
);
|
|
3787
|
-
if (!deployed?.agentWallet || !(0, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
|
|
3788
|
-
throw this.invalidResponse("agent deployment", {
|
|
3789
|
-
reason: "Deploy did not return the expected Agent Wallet."
|
|
3790
|
-
});
|
|
3791
|
-
}
|
|
3792
|
-
context.wallet = deployed.agentWallet;
|
|
3793
|
-
}
|
|
3794
|
-
async refreshSnapshotAfterMovement(state, chainId, context, movement) {
|
|
3795
|
-
try {
|
|
3796
|
-
const response = await this.walletRequest(
|
|
3797
|
-
state,
|
|
3798
|
-
chainId,
|
|
3799
|
-
`${this.agentPath(context, "snapshot")}${query({
|
|
3800
|
-
shouldForceRefresh: true
|
|
3801
|
-
})}`
|
|
3802
|
-
);
|
|
3803
|
-
if (!response?.agentSnapshot) {
|
|
3804
|
-
throw this.invalidResponse("agent snapshot refresh");
|
|
3805
|
-
}
|
|
3806
|
-
} catch (error) {
|
|
3807
|
-
console.warn(
|
|
3808
|
-
`[owney-sdk] Yieldseeker ${movement} snapshot refresh failed:`,
|
|
3809
|
-
error
|
|
3810
|
-
);
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
agentPath(context, suffix) {
|
|
3814
|
-
return `/users/${context.user.userId}/agents/${context.agent.agentId}/${suffix}`;
|
|
3815
|
-
}
|
|
3816
|
-
async walletRequest(state, chainId, path, options = {}) {
|
|
3817
|
-
try {
|
|
3818
|
-
return await this.providerRequest(state, chainId, path, options);
|
|
3819
|
-
} catch (error) {
|
|
3820
|
-
throw this.mapApiError(error);
|
|
3821
|
-
}
|
|
3822
|
-
}
|
|
3823
|
-
async providerRequest(state, chainId, path, options = {}) {
|
|
3824
|
-
this.assertChain(chainId);
|
|
3825
|
-
const request = (signature2) => this.api.request(path, {
|
|
3826
|
-
...options,
|
|
3827
|
-
signature: signature2
|
|
3828
|
-
});
|
|
3829
|
-
let signature = await this.auth.getToken(state, chainId);
|
|
3830
|
-
try {
|
|
3831
|
-
return await request(signature);
|
|
3832
|
-
} catch (error) {
|
|
3833
|
-
if (!(error instanceof YieldseekerApiError)) throw error;
|
|
3834
|
-
if (error.providerCode === "NO_USER") throw error;
|
|
3835
|
-
if (!error.isAuthenticationError) throw error;
|
|
3836
|
-
signature = await this.auth.refreshToken(state, chainId, signature);
|
|
2625
|
+
for (const key2 of keys) {
|
|
2626
|
+
const raw = store.getItem(key2);
|
|
2627
|
+
if (!raw) continue;
|
|
3837
2628
|
try {
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
2629
|
+
const parsed = JSON.parse(raw);
|
|
2630
|
+
if (now - parsed.createdAt > MAX_AGE_MS) {
|
|
2631
|
+
store.removeItem(key2);
|
|
2632
|
+
continue;
|
|
3842
2633
|
}
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
{ cause: error instanceof Error ? error.message : String(error) },
|
|
3853
|
-
this.id
|
|
3854
|
-
);
|
|
3855
|
-
}
|
|
3856
|
-
const code = error.isAuthenticationError ? "AGENT_AUTH_FAILED" : error.providerCode === "REQUEST_TIMEOUT" ? "AGENT_TIMEOUT" : "AGENT_API_ERROR";
|
|
3857
|
-
return new OwneyError(
|
|
3858
|
-
code,
|
|
3859
|
-
`Yieldseeker request failed: ${error.providerCode}.`,
|
|
3860
|
-
{
|
|
3861
|
-
statusCode: error.status,
|
|
3862
|
-
providerCode: error.providerCode,
|
|
3863
|
-
...error.responseFields ? { fields: error.responseFields } : {}
|
|
3864
|
-
},
|
|
3865
|
-
this.id
|
|
3866
|
-
);
|
|
3867
|
-
}
|
|
3868
|
-
async submitTransaction(state, chainId, transaction) {
|
|
3869
|
-
if (this.transactionExecutor) {
|
|
3870
|
-
return this.transactionExecutor(state, chainId, transaction);
|
|
3871
|
-
}
|
|
3872
|
-
this.assertTransaction(transaction, state, chainId);
|
|
3873
|
-
const account = (0, import_viem6.getAddress)(state.walletAddress);
|
|
3874
|
-
const walletClient = (0, import_viem6.createWalletClient)({
|
|
3875
|
-
account,
|
|
3876
|
-
chain: import_chains3.base,
|
|
3877
|
-
transport: (0, import_viem6.custom)(state.provider)
|
|
3878
|
-
});
|
|
3879
|
-
const publicClient = (0, import_viem6.createPublicClient)({
|
|
3880
|
-
chain: import_chains3.base,
|
|
3881
|
-
transport: (0, import_viem6.custom)(state.provider)
|
|
3882
|
-
});
|
|
3883
|
-
await ensureWalletOnChain(
|
|
3884
|
-
publicClient,
|
|
3885
|
-
walletClient,
|
|
3886
|
-
8453
|
|
3887
|
-
);
|
|
3888
|
-
const hash = await walletClient.sendTransaction({
|
|
3889
|
-
account,
|
|
3890
|
-
chain: import_chains3.base,
|
|
3891
|
-
to: (0, import_viem6.getAddress)(transaction.to),
|
|
3892
|
-
data: transaction.data,
|
|
3893
|
-
value: BigInt(transaction.value)
|
|
3894
|
-
});
|
|
3895
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3896
|
-
hash,
|
|
3897
|
-
confirmations: 1
|
|
3898
|
-
});
|
|
3899
|
-
if (receipt.status !== "success") {
|
|
3900
|
-
throw new OwneyError(
|
|
3901
|
-
"AGENT_TRANSACTION_REVERTED",
|
|
3902
|
-
`Yieldseeker transaction reverted (${hash}).`,
|
|
3903
|
-
{ transactionHash: hash },
|
|
3904
|
-
this.id
|
|
3905
|
-
);
|
|
3906
|
-
}
|
|
3907
|
-
return hash;
|
|
3908
|
-
}
|
|
3909
|
-
async waitForReceipt(state, chainId, transactionHash) {
|
|
3910
|
-
if (this.unwindReceiptWaiter) {
|
|
3911
|
-
await this.unwindReceiptWaiter(state, chainId, transactionHash);
|
|
3912
|
-
return;
|
|
3913
|
-
}
|
|
3914
|
-
const publicClient = (0, import_viem6.createPublicClient)({
|
|
3915
|
-
chain: import_chains3.base,
|
|
3916
|
-
transport: (0, import_viem6.custom)(state.provider)
|
|
3917
|
-
});
|
|
3918
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3919
|
-
hash: transactionHash,
|
|
3920
|
-
confirmations: 1
|
|
3921
|
-
});
|
|
3922
|
-
if (receipt.status !== "success") {
|
|
3923
|
-
throw new OwneyError(
|
|
3924
|
-
"AGENT_TRANSACTION_REVERTED",
|
|
3925
|
-
`Yieldseeker transaction reverted (${transactionHash}).`,
|
|
3926
|
-
{ transactionHash },
|
|
3927
|
-
this.id
|
|
3928
|
-
);
|
|
3929
|
-
}
|
|
3930
|
-
}
|
|
3931
|
-
assertTransaction(transaction, state, chainId) {
|
|
3932
|
-
if (!transaction || typeof transaction.from !== "string" || !(0, import_viem6.isAddress)(transaction.from) || typeof transaction.to !== "string" || !(0, import_viem6.isAddress)(transaction.to) || typeof transaction.data !== "string" || !/^0x[a-fA-F0-9]*$/.test(transaction.data) || typeof transaction.value !== "string" || !/^[0-9]+$/.test(transaction.value) || transaction.chainId !== chainId || (0, import_viem6.getAddress)(transaction.from) !== (0, import_viem6.getAddress)(state.walletAddress)) {
|
|
3933
|
-
throw this.invalidResponse("transaction");
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
assertAgent(agent) {
|
|
3937
|
-
if (typeof agent.agentId !== "string" || typeof agent.assetAddress !== "string" || !(0, import_viem6.isAddress)(agent.assetAddress) || agent.chainId !== 8453) {
|
|
3938
|
-
throw this.invalidResponse("agent");
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
isOwneyAgent(agent) {
|
|
3942
|
-
return typeof agent.name === "string" && agent.name.trim().toLowerCase() === OWNEY_AGENT_NAME;
|
|
3943
|
-
}
|
|
3944
|
-
assetForAgent(agent) {
|
|
3945
|
-
for (const asset of ["USDC", "WETH"]) {
|
|
3946
|
-
if (agent.assetAddress.toLowerCase() === YIELDSEEKER_ASSET_METADATA[asset].address.toLowerCase()) {
|
|
3947
|
-
return asset;
|
|
3948
|
-
}
|
|
3949
|
-
}
|
|
3950
|
-
return null;
|
|
3951
|
-
}
|
|
3952
|
-
isTransactionHash(value) {
|
|
3953
|
-
return typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3954
|
-
}
|
|
3955
|
-
assertChain(chainId) {
|
|
3956
|
-
if (chainId !== 8453) {
|
|
3957
|
-
throw new OwneyError(
|
|
3958
|
-
"CHAIN_UNSUPPORTED",
|
|
3959
|
-
`Yieldseeker does not support chain ${chainId}.`,
|
|
3960
|
-
{ chainId, supportedChainIds: [8453] },
|
|
3961
|
-
this.id
|
|
3962
|
-
);
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
assertOptionalChain(chainId) {
|
|
3966
|
-
if (chainId !== void 0) this.assertChain(chainId);
|
|
3967
|
-
}
|
|
3968
|
-
assertAsset(asset) {
|
|
3969
|
-
if (asset !== "USDC" && asset !== "WETH") {
|
|
3970
|
-
throw new OwneyError(
|
|
3971
|
-
"ASSET_UNSUPPORTED",
|
|
3972
|
-
`Yieldseeker does not support asset ${asset} in the Owney rollout.`,
|
|
3973
|
-
{
|
|
3974
|
-
asset,
|
|
3975
|
-
supportedAssets: ["USDC", "WETH"],
|
|
3976
|
-
providerAlsoAdvertises: ["cbBTC"]
|
|
3977
|
-
},
|
|
3978
|
-
this.id
|
|
3979
|
-
);
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
invalidResponse(operation, details = {}) {
|
|
3983
|
-
return new OwneyError(
|
|
3984
|
-
"AGENT_INVALID_RESPONSE",
|
|
3985
|
-
`Yieldseeker returned an invalid ${operation} response.`,
|
|
3986
|
-
details,
|
|
3987
|
-
this.id
|
|
3988
|
-
);
|
|
3989
|
-
}
|
|
3990
|
-
};
|
|
3991
|
-
|
|
3992
|
-
// src/lib/routing-api.ts
|
|
3993
|
-
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3994
|
-
async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL2) {
|
|
3995
|
-
const url = `${baseUrl}/api/v1/agent/org-config`;
|
|
3996
|
-
try {
|
|
3997
|
-
const res = await fetch(url, {
|
|
3998
|
-
method: "GET",
|
|
3999
|
-
headers: {
|
|
4000
|
-
"Content-Type": "application/json",
|
|
4001
|
-
"x-owney-api-key": `${apiKey}`
|
|
4002
|
-
}
|
|
4003
|
-
});
|
|
4004
|
-
if (!res.ok) {
|
|
4005
|
-
if (res.status !== 404) {
|
|
4006
|
-
console.warn(
|
|
4007
|
-
`[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
|
|
4008
|
-
);
|
|
4009
|
-
}
|
|
4010
|
-
return null;
|
|
4011
|
-
}
|
|
4012
|
-
const json = await res.json();
|
|
4013
|
-
const policy = json.success ? json.data ?? null : null;
|
|
4014
|
-
debugLog(
|
|
4015
|
-
"owney-sdk",
|
|
4016
|
-
policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
|
|
4017
|
-
policy ?? void 0
|
|
4018
|
-
);
|
|
4019
|
-
return policy;
|
|
4020
|
-
} catch (error) {
|
|
4021
|
-
console.warn(
|
|
4022
|
-
"[owney-sdk] Could not read org agent config (non-fatal):",
|
|
4023
|
-
error instanceof Error ? error.message : String(error)
|
|
4024
|
-
);
|
|
4025
|
-
return null;
|
|
2634
|
+
if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
|
|
2635
|
+
out.push(parsed);
|
|
2636
|
+
}
|
|
2637
|
+
} catch {
|
|
2638
|
+
store.removeItem(key2);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
} catch {
|
|
2642
|
+
return out;
|
|
4026
2643
|
}
|
|
2644
|
+
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
4027
2645
|
}
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
2646
|
+
|
|
2647
|
+
// src/lib/swap/swap.executor.ts
|
|
2648
|
+
var DEFAULT_SLIPPAGE = 1;
|
|
2649
|
+
async function affordableAmount(deps, quoted) {
|
|
2650
|
+
const balance = await deps.readSourceBalance();
|
|
2651
|
+
if (balance >= quoted) return quoted;
|
|
2652
|
+
debugLog("owney-sdk", "swap: trimming to the current source balance", {
|
|
2653
|
+
quoted: quoted.toString(),
|
|
2654
|
+
balance: balance.toString(),
|
|
2655
|
+
short: (quoted - balance).toString()
|
|
4036
2656
|
});
|
|
4037
|
-
|
|
4038
|
-
|
|
2657
|
+
return balance;
|
|
2658
|
+
}
|
|
2659
|
+
async function executeSwap(deps, options) {
|
|
2660
|
+
const { quote, walletAddress, onStage } = options;
|
|
2661
|
+
debugLog("owney-sdk", "swap: start", {
|
|
2662
|
+
rail: quote.rail,
|
|
2663
|
+
from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
|
|
2664
|
+
to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
|
|
2665
|
+
expected: quote.dst.amount,
|
|
2666
|
+
floor: quote.dstAmountMin
|
|
2667
|
+
});
|
|
2668
|
+
const before = await deps.readTargetBalance();
|
|
2669
|
+
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2670
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2671
|
+
const after = await deps.readTargetBalance();
|
|
2672
|
+
const received = after - before;
|
|
2673
|
+
debugLog("owney-sdk", "swap: target balance after", {
|
|
2674
|
+
after: after.toString(),
|
|
2675
|
+
received: received.toString()
|
|
2676
|
+
});
|
|
2677
|
+
if (received <= 0n) {
|
|
4039
2678
|
throw new OwneyError(
|
|
4040
|
-
"
|
|
4041
|
-
|
|
4042
|
-
{
|
|
2679
|
+
"SWAP_REQUEST_FAILED",
|
|
2680
|
+
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
2681
|
+
{ rail: quote.rail, ...result }
|
|
4043
2682
|
);
|
|
4044
2683
|
}
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
2684
|
+
return { received: received.toString(), ...result };
|
|
2685
|
+
}
|
|
2686
|
+
async function runClassic(deps, options) {
|
|
2687
|
+
const {
|
|
2688
|
+
quote,
|
|
2689
|
+
walletAddress,
|
|
2690
|
+
slippage = DEFAULT_SLIPPAGE,
|
|
2691
|
+
direction,
|
|
2692
|
+
onStage
|
|
2693
|
+
} = options;
|
|
2694
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2695
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2696
|
+
const swapTxRequest = {
|
|
2697
|
+
from: {
|
|
2698
|
+
chainId: quote.src.chainId,
|
|
2699
|
+
symbol: quote.src.symbol,
|
|
2700
|
+
amount: amount.toString()
|
|
2701
|
+
},
|
|
2702
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2703
|
+
walletAddress,
|
|
2704
|
+
slippage,
|
|
2705
|
+
...direction ? { direction } : {}
|
|
2706
|
+
};
|
|
2707
|
+
onStage?.("quoting");
|
|
2708
|
+
debugLog("owney-sdk", "swap: fetching classic calldata");
|
|
2709
|
+
let { tx } = await deps.api.swapTx(swapTxRequest);
|
|
2710
|
+
const isNative = BigInt(tx.value ?? "0") > 0n;
|
|
2711
|
+
if (!isNative) {
|
|
2712
|
+
const needed = amount;
|
|
2713
|
+
const current = await deps.readAllowance(tx.to);
|
|
2714
|
+
debugLog("owney-sdk", "swap: allowance", {
|
|
2715
|
+
spender: tx.to,
|
|
2716
|
+
current: current.toString(),
|
|
2717
|
+
needed: needed.toString()
|
|
2718
|
+
});
|
|
2719
|
+
if (current < needed) {
|
|
2720
|
+
onStage?.("approving");
|
|
2721
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2722
|
+
await deps.approve(tx.to, MAX_UINT256);
|
|
2723
|
+
debugLog(
|
|
2724
|
+
"owney-sdk",
|
|
2725
|
+
"swap: re-fetching classic calldata after approval"
|
|
2726
|
+
);
|
|
2727
|
+
({ tx } = await deps.api.swapTx(swapTxRequest));
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
onStage?.("signing");
|
|
2731
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2732
|
+
debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
|
|
2733
|
+
const txHash = await deps.sendTransaction({
|
|
2734
|
+
to: tx.to,
|
|
2735
|
+
data: tx.data,
|
|
2736
|
+
value: tx.value ?? "0"
|
|
2737
|
+
});
|
|
2738
|
+
onStage?.("swapped");
|
|
2739
|
+
return { txHash };
|
|
2740
|
+
}
|
|
2741
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2742
|
+
const { quote, direction, onStage } = options;
|
|
2743
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2744
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2745
|
+
if (quote.spender && !isNativeSource) {
|
|
2746
|
+
const needed = amount;
|
|
2747
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2748
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2749
|
+
spender: quote.spender,
|
|
2750
|
+
current: current.toString(),
|
|
2751
|
+
needed: needed.toString()
|
|
2752
|
+
});
|
|
2753
|
+
if (current < needed) {
|
|
2754
|
+
onStage?.("approving");
|
|
2755
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2756
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2757
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2761
|
+
onStage?.("quoting");
|
|
2762
|
+
debugLog("owney-sdk", "swap: building fusion order", {
|
|
2763
|
+
secrets: secretHashes.length
|
|
2764
|
+
});
|
|
2765
|
+
const built = await deps.api.buildOrder({
|
|
2766
|
+
from: {
|
|
2767
|
+
chainId: quote.src.chainId,
|
|
2768
|
+
symbol: quote.src.symbol,
|
|
2769
|
+
// The trimmed amount — the order is re-quoted at this size server-side.
|
|
2770
|
+
amount: amount.toString()
|
|
2771
|
+
},
|
|
2772
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2773
|
+
walletAddress,
|
|
2774
|
+
secretHashes,
|
|
2775
|
+
...direction ? { direction } : {}
|
|
2776
|
+
});
|
|
2777
|
+
saveOrder({
|
|
2778
|
+
orderHash: built.orderHash,
|
|
2779
|
+
secrets,
|
|
2780
|
+
srcChainId: quote.src.chainId,
|
|
2781
|
+
srcSymbol: quote.src.symbol,
|
|
2782
|
+
dstChainId: quote.dst.chainId,
|
|
2783
|
+
dstSymbol: quote.dst.symbol,
|
|
2784
|
+
amount: amount.toString(),
|
|
2785
|
+
createdAt: Date.now()
|
|
2786
|
+
});
|
|
2787
|
+
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
2788
|
+
onStage?.("signing");
|
|
2789
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2790
|
+
debugLog("owney-sdk", "swap: awaiting signature in wallet", {
|
|
2791
|
+
signingOnChain: quote.src.chainId
|
|
2792
|
+
});
|
|
2793
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2794
|
+
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
2795
|
+
await deps.api.submitOrder({
|
|
2796
|
+
srcChainId: quote.src.chainId,
|
|
2797
|
+
// The ORDER STRUCT, not the typed-data envelope we just signed. Sending
|
|
2798
|
+
// the envelope here gets a bare 500 from the relayer.
|
|
2799
|
+
order: built.order,
|
|
2800
|
+
signature,
|
|
2801
|
+
quoteId: built.quoteId,
|
|
2802
|
+
// Single-fill orders must NOT carry secretHashes — the relayer rejects
|
|
2803
|
+
// them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
|
|
2804
|
+
// order's hashlock, so repeating it here is redundant, and only a
|
|
2805
|
+
// multi-fill order (a Merkle tree of hashes) needs them listed.
|
|
2806
|
+
...secretHashes.length > 1 ? { secretHashes } : {},
|
|
2807
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2808
|
+
});
|
|
2809
|
+
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
2810
|
+
try {
|
|
2811
|
+
await runFusionOrder(deps.runner, {
|
|
2812
|
+
orderHash: built.orderHash,
|
|
2813
|
+
secrets,
|
|
2814
|
+
...onStage ? { onStage } : {}
|
|
2815
|
+
});
|
|
2816
|
+
} catch (error) {
|
|
2817
|
+
if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
|
|
2818
|
+
clearOrder(built.orderHash);
|
|
2819
|
+
}
|
|
2820
|
+
throw error;
|
|
2821
|
+
}
|
|
2822
|
+
clearOrder(built.orderHash);
|
|
2823
|
+
return { orderHash: built.orderHash };
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// src/lib/swap/swap.arrival.ts
|
|
2827
|
+
var DEFAULT_TIMEOUT_MS2 = 18e4;
|
|
2828
|
+
var DEFAULT_POLL_MS2 = 4e3;
|
|
2829
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2830
|
+
async function awaitWithdrawalArrival(options) {
|
|
2831
|
+
const {
|
|
2832
|
+
readBalance,
|
|
2833
|
+
baseline,
|
|
2834
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2,
|
|
2835
|
+
pollMs = DEFAULT_POLL_MS2
|
|
2836
|
+
} = options;
|
|
2837
|
+
const deadline = Date.now() + timeoutMs;
|
|
2838
|
+
debugLog("owney-sdk", "withdraw: waiting for funds to land", {
|
|
2839
|
+
baseline: baseline.toString(),
|
|
2840
|
+
timeoutMs
|
|
2841
|
+
});
|
|
2842
|
+
let lastError;
|
|
2843
|
+
for (; ; ) {
|
|
2844
|
+
try {
|
|
2845
|
+
const balance = await readBalance();
|
|
2846
|
+
if (balance > baseline) {
|
|
2847
|
+
const arrived = balance - baseline;
|
|
2848
|
+
debugLog("owney-sdk", "withdraw: funds landed", {
|
|
2849
|
+
arrived: arrived.toString()
|
|
2850
|
+
});
|
|
2851
|
+
return arrived;
|
|
2852
|
+
}
|
|
2853
|
+
} catch (error) {
|
|
2854
|
+
lastError = error;
|
|
2855
|
+
debugLog("owney-sdk", "withdraw: balance read failed, retrying", {
|
|
2856
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
if (Date.now() >= deadline) {
|
|
2860
|
+
throw new OwneyError(
|
|
2861
|
+
"WITHDRAW_ARRIVAL_TIMEOUT",
|
|
2862
|
+
"The withdrawal was accepted but the funds had not arrived in time to swap them. They are on their way to your wallet in the original asset.",
|
|
2863
|
+
{
|
|
2864
|
+
baseline: baseline.toString(),
|
|
2865
|
+
waitedMs: timeoutMs,
|
|
2866
|
+
...lastError ? {
|
|
2867
|
+
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
2868
|
+
} : {}
|
|
2869
|
+
}
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
await sleep(pollMs);
|
|
4052
2873
|
}
|
|
4053
|
-
return json.data;
|
|
4054
2874
|
}
|
|
4055
2875
|
|
|
4056
2876
|
// src/lib/health-report.ts
|
|
4057
|
-
var
|
|
4058
|
-
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl =
|
|
2877
|
+
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2878
|
+
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
4059
2879
|
try {
|
|
4060
2880
|
await fetch(`${baseUrl}/api/v1/agent/health-report`, {
|
|
4061
2881
|
method: "POST",
|
|
@@ -4087,7 +2907,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
4087
2907
|
}
|
|
4088
2908
|
|
|
4089
2909
|
// src/lib/helpers/withdraw-helper.ts
|
|
4090
|
-
var
|
|
2910
|
+
var import_viem5 = require("viem");
|
|
4091
2911
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
4092
2912
|
const target = asset.toUpperCase();
|
|
4093
2913
|
return agents.map((agent) => {
|
|
@@ -4095,29 +2915,8 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
4095
2915
|
const tokenBalance = agentBalance?.tokens.find(
|
|
4096
2916
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
4097
2917
|
);
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
const chainNameById = {
|
|
4101
|
-
1: "ETHEREUM",
|
|
4102
|
-
8453: "BASE",
|
|
4103
|
-
42161: "ARBITRUM"
|
|
4104
|
-
};
|
|
4105
|
-
const targetChain = chainNameById[chainId];
|
|
4106
|
-
for (const position2 of agentBalance?.positions ?? []) {
|
|
4107
|
-
const positionChain = position2.chain.trim().toUpperCase();
|
|
4108
|
-
const matchesChain = positionChain === String(chainId) || targetChain !== void 0 && positionChain === targetChain;
|
|
4109
|
-
if (!matchesChain || position2.asset.toUpperCase() !== target) continue;
|
|
4110
|
-
if (position2.amountRaw !== void 0) {
|
|
4111
|
-
try {
|
|
4112
|
-
balance += BigInt(position2.amountRaw);
|
|
4113
|
-
continue;
|
|
4114
|
-
} catch {
|
|
4115
|
-
}
|
|
4116
|
-
}
|
|
4117
|
-
balance += (0, import_viem7.parseUnits)(position2.amount, decimals);
|
|
4118
|
-
}
|
|
4119
|
-
}
|
|
4120
|
-
return { agent, balance };
|
|
2918
|
+
if (!tokenBalance) return { agent, balance: 0n };
|
|
2919
|
+
return { agent, balance: (0, import_viem5.parseUnits)(tokenBalance.amount, decimals) };
|
|
4121
2920
|
});
|
|
4122
2921
|
}
|
|
4123
2922
|
function planProportionalShares(balances, requested, totalAvailable) {
|
|
@@ -4143,9 +2942,7 @@ function planProportionalShares(balances, requested, totalAvailable) {
|
|
|
4143
2942
|
return plans;
|
|
4144
2943
|
}
|
|
4145
2944
|
function planDisabledDrain(disabled, requested) {
|
|
4146
|
-
const sorted = [...disabled].filter((d) => d.balance > 0n).sort(
|
|
4147
|
-
(a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0
|
|
4148
|
-
);
|
|
2945
|
+
const sorted = [...disabled].filter((d) => d.balance > 0n).sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
4149
2946
|
const plans = [];
|
|
4150
2947
|
let remaining = requested;
|
|
4151
2948
|
for (const { agent, balance } of sorted) {
|
|
@@ -4196,13 +2993,6 @@ function balanceForApyScope(balance, chainId, tokenSymbol) {
|
|
|
4196
2993
|
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
4197
2994
|
}
|
|
4198
2995
|
const normalizedToken = tokenSymbol.toUpperCase();
|
|
4199
|
-
const snapshots = balance.assetBalances?.filter(
|
|
4200
|
-
(token) => Number(token.chainId) === chainId && String(token.asset).toUpperCase() === normalizedToken
|
|
4201
|
-
);
|
|
4202
|
-
if (snapshots?.length) {
|
|
4203
|
-
const amount = snapshots.reduce((sum, token) => sum + Number(token.amount), 0);
|
|
4204
|
-
if (Number.isFinite(amount)) return Math.max(0, amount);
|
|
4205
|
-
}
|
|
4206
2996
|
return balance.tokens.reduce((total, token) => {
|
|
4207
2997
|
if (Number(token.chainId) !== chainId || String(token.asset).toUpperCase() !== normalizedToken) {
|
|
4208
2998
|
return total;
|
|
@@ -4273,305 +3063,325 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
4273
3063
|
}
|
|
4274
3064
|
|
|
4275
3065
|
// src/client.ts
|
|
4276
|
-
var import_viem11 = require("viem");
|
|
4277
|
-
var import_chains4 = require("viem/chains");
|
|
4278
|
-
|
|
4279
|
-
// src/lib/sponsored-token-batch.ts
|
|
4280
|
-
var import_viem9 = require("viem");
|
|
4281
|
-
|
|
4282
|
-
// src/lib/permit2-batch.ts
|
|
4283
3066
|
var import_viem8 = require("viem");
|
|
4284
|
-
var
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
Deposit: [{ name: "recipients", type: "address[]" }],
|
|
4294
|
-
TokenPermissions: [
|
|
4295
|
-
{ name: "token", type: "address" },
|
|
4296
|
-
{ name: "amount", type: "uint256" }
|
|
4297
|
-
]
|
|
4298
|
-
};
|
|
4299
|
-
var PERMIT2_BATCH_ABI = (0, import_viem8.parseAbi)([
|
|
4300
|
-
"struct TokenPermissions { address token; uint256 amount; }",
|
|
4301
|
-
"struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
|
|
4302
|
-
"struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
|
|
4303
|
-
"function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
|
|
4304
|
-
"function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
|
|
4305
|
-
]);
|
|
4306
|
-
function batchPermit(b) {
|
|
4307
|
-
return {
|
|
4308
|
-
permitted: b.transfers.map((t) => ({
|
|
4309
|
-
token: b.token,
|
|
4310
|
-
amount: BigInt(t.amount)
|
|
4311
|
-
})),
|
|
4312
|
-
nonce: BigInt(b.nonce),
|
|
4313
|
-
deadline: BigInt(b.deadline)
|
|
4314
|
-
};
|
|
4315
|
-
}
|
|
4316
|
-
function batchTypedData(b, spender) {
|
|
3067
|
+
var import_chains2 = require("viem/chains");
|
|
3068
|
+
|
|
3069
|
+
// src/lib/transfer-auth.ts
|
|
3070
|
+
var import_viem6 = require("viem");
|
|
3071
|
+
var ERC20_META_ABI = [
|
|
3072
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
3073
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
3074
|
+
];
|
|
3075
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
4317
3076
|
return {
|
|
4318
|
-
domain: {
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
3077
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
3078
|
+
types: {
|
|
3079
|
+
TransferWithAuthorization: [
|
|
3080
|
+
{ name: "from", type: "address" },
|
|
3081
|
+
{ name: "to", type: "address" },
|
|
3082
|
+
{ name: "value", type: "uint256" },
|
|
3083
|
+
{ name: "validAfter", type: "uint256" },
|
|
3084
|
+
{ name: "validBefore", type: "uint256" },
|
|
3085
|
+
{ name: "nonce", type: "bytes32" }
|
|
3086
|
+
]
|
|
4322
3087
|
},
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
message: {
|
|
4326
|
-
...batchPermit(b),
|
|
4327
|
-
spender,
|
|
4328
|
-
witness: { recipients: b.transfers.map((t) => t.to) }
|
|
4329
|
-
}
|
|
3088
|
+
primaryType: "TransferWithAuthorization",
|
|
3089
|
+
message: input.message
|
|
4330
3090
|
};
|
|
4331
3091
|
}
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
to: t.to.toLowerCase(),
|
|
4339
|
-
amount: BigInt(t.amount).toString()
|
|
4340
|
-
}))
|
|
4341
|
-
);
|
|
4342
|
-
function read(key2) {
|
|
4343
|
-
return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
|
|
4344
|
-
}
|
|
4345
|
-
function save(key2, body) {
|
|
4346
|
-
const value = JSON.stringify({
|
|
4347
|
-
...body,
|
|
4348
|
-
transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
|
|
4349
|
-
});
|
|
4350
|
-
if (typeof window === "undefined") memory.set(key2, value);
|
|
4351
|
-
else window.localStorage.setItem(key2, value);
|
|
3092
|
+
async function readTokenMeta(publicClient, token) {
|
|
3093
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
3094
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
3095
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
3096
|
+
]);
|
|
3097
|
+
return { tokenName, tokenVersion };
|
|
4352
3098
|
}
|
|
4353
|
-
function
|
|
4354
|
-
|
|
4355
|
-
|
|
3099
|
+
function randomAuthNonce() {
|
|
3100
|
+
const bytes = new Uint8Array(32);
|
|
3101
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
3102
|
+
return (0, import_viem6.bytesToHex)(bytes);
|
|
4356
3103
|
}
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
3104
|
+
|
|
3105
|
+
// src/lib/sponsor-client.ts
|
|
3106
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3107
|
+
async function postSponsorTransferAuth(input) {
|
|
3108
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3109
|
+
let res;
|
|
3110
|
+
try {
|
|
3111
|
+
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
3112
|
+
method: "POST",
|
|
3113
|
+
headers: {
|
|
3114
|
+
"content-type": "application/json",
|
|
3115
|
+
"x-owney-api-key": input.apiKey
|
|
3116
|
+
},
|
|
3117
|
+
body: JSON.stringify(input.body)
|
|
3118
|
+
});
|
|
3119
|
+
} catch (networkError) {
|
|
3120
|
+
throw new OwneyError(
|
|
3121
|
+
"SPONSOR_REQUEST_FAILED",
|
|
3122
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
3123
|
+
{ cause: String(networkError) }
|
|
3124
|
+
);
|
|
3125
|
+
}
|
|
3126
|
+
const text = await res.text();
|
|
3127
|
+
let parsed = null;
|
|
3128
|
+
try {
|
|
3129
|
+
parsed = JSON.parse(text);
|
|
3130
|
+
} catch {
|
|
3131
|
+
}
|
|
3132
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
3133
|
+
throw new OwneyError(
|
|
3134
|
+
"SPONSOR_REQUEST_FAILED",
|
|
3135
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
3136
|
+
{
|
|
3137
|
+
statusCode: res.status,
|
|
3138
|
+
responseBody: text.slice(0, 500),
|
|
3139
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
3140
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
3141
|
+
safeToFallback: res.status === 503
|
|
3142
|
+
}
|
|
3143
|
+
);
|
|
4369
3144
|
}
|
|
4370
|
-
|
|
4371
|
-
inflight.set(key2, { plan, promise });
|
|
4372
|
-
return promise;
|
|
3145
|
+
return parsed.data;
|
|
4373
3146
|
}
|
|
4374
|
-
async function
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
3147
|
+
async function postSponsorPermit2Transfer(input) {
|
|
3148
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3149
|
+
let res;
|
|
3150
|
+
try {
|
|
3151
|
+
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
3152
|
+
method: "POST",
|
|
3153
|
+
headers: {
|
|
3154
|
+
"content-type": "application/json",
|
|
3155
|
+
"x-owney-api-key": input.apiKey
|
|
3156
|
+
},
|
|
3157
|
+
body: JSON.stringify(input.body)
|
|
3158
|
+
});
|
|
3159
|
+
} catch (networkError) {
|
|
3160
|
+
throw new OwneyError(
|
|
3161
|
+
"SPONSOR_REQUEST_FAILED",
|
|
3162
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
3163
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3166
|
+
const text = await res.text();
|
|
3167
|
+
let parsed = null;
|
|
3168
|
+
try {
|
|
3169
|
+
parsed = JSON.parse(text);
|
|
3170
|
+
} catch {
|
|
3171
|
+
}
|
|
3172
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
3173
|
+
throw new OwneyError(
|
|
3174
|
+
"SPONSOR_REQUEST_FAILED",
|
|
3175
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
3176
|
+
{
|
|
3177
|
+
statusCode: res.status,
|
|
3178
|
+
responseBody: text.slice(0, 500),
|
|
3179
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
4398
3180
|
}
|
|
4399
|
-
|
|
4400
|
-
apiKey: i.apiKey,
|
|
4401
|
-
baseUrl: i.baseUrl,
|
|
4402
|
-
body
|
|
4403
|
-
});
|
|
4404
|
-
if (result.txHash !== (0, import_viem9.keccak256)(body.serializedTransaction))
|
|
4405
|
-
throw new Error(
|
|
4406
|
-
"Sponsorship receipt does not match the pending transaction."
|
|
4407
|
-
);
|
|
4408
|
-
clear(key2);
|
|
4409
|
-
return result.txHash;
|
|
4410
|
-
} catch (error) {
|
|
4411
|
-
if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
|
|
4412
|
-
clear(key2);
|
|
4413
|
-
throw error;
|
|
4414
|
-
}
|
|
4415
|
-
};
|
|
4416
|
-
const saved = read(key2);
|
|
4417
|
-
if (saved) {
|
|
4418
|
-
const previous = JSON.parse(saved);
|
|
4419
|
-
if (previous.chainId !== i.chainId || !(0, import_viem9.isAddressEqual)(previous.from, i.owner) || !(0, import_viem9.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
|
|
4420
|
-
throw new Error(
|
|
4421
|
-
"Retry the previous token deposit and agent split first to reconcile its status."
|
|
4422
|
-
);
|
|
4423
|
-
i.onApproved?.();
|
|
4424
|
-
return send({ ...previous, transfers: i.transfers });
|
|
3181
|
+
);
|
|
4425
3182
|
}
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
3183
|
+
return parsed.data;
|
|
3184
|
+
}
|
|
3185
|
+
async function getSponsorRelayerAddress(input) {
|
|
3186
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3187
|
+
let res;
|
|
3188
|
+
try {
|
|
3189
|
+
res = await fetch(
|
|
3190
|
+
`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
3191
|
+
{
|
|
3192
|
+
headers: { "x-owney-api-key": input.apiKey }
|
|
3193
|
+
}
|
|
3194
|
+
);
|
|
3195
|
+
} catch (networkError) {
|
|
4432
3196
|
throw new OwneyError(
|
|
4433
|
-
"
|
|
4434
|
-
|
|
3197
|
+
"SPONSOR_REQUEST_FAILED",
|
|
3198
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
3199
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
4435
3200
|
);
|
|
4436
|
-
|
|
3201
|
+
}
|
|
3202
|
+
const text = await res.text();
|
|
3203
|
+
let parsed = null;
|
|
3204
|
+
try {
|
|
3205
|
+
parsed = JSON.parse(text);
|
|
3206
|
+
} catch {
|
|
3207
|
+
}
|
|
3208
|
+
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
4437
3209
|
throw new OwneyError(
|
|
4438
|
-
"
|
|
4439
|
-
|
|
3210
|
+
"SPONSOR_REQUEST_FAILED",
|
|
3211
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
3212
|
+
{
|
|
3213
|
+
statusCode: res.status,
|
|
3214
|
+
responseBody: text.slice(0, 500),
|
|
3215
|
+
safeToFallback: true
|
|
3216
|
+
}
|
|
4440
3217
|
);
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
baseUrl: i.baseUrl,
|
|
4444
|
-
chainId: i.chainId
|
|
4445
|
-
});
|
|
4446
|
-
const now = (await i.pub.getBlock()).timestamp;
|
|
4447
|
-
const unsigned = {
|
|
4448
|
-
chainId: i.chainId,
|
|
4449
|
-
token: i.token,
|
|
4450
|
-
from: i.owner,
|
|
4451
|
-
transfers: i.transfers,
|
|
4452
|
-
nonce: randomPermit2Nonce().toString(),
|
|
4453
|
-
deadline: (now + 900n).toString()
|
|
4454
|
-
};
|
|
4455
|
-
const signature = await i.wallet.signTypedData({
|
|
4456
|
-
account: i.owner,
|
|
4457
|
-
...batchTypedData(unsigned, relayer)
|
|
4458
|
-
});
|
|
4459
|
-
i.onApproved?.();
|
|
4460
|
-
return send({ ...unsigned, signature });
|
|
3218
|
+
}
|
|
3219
|
+
return parsed.data.relayer;
|
|
4461
3220
|
}
|
|
4462
3221
|
|
|
4463
|
-
// src/lib/sponsored-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
3222
|
+
// src/lib/sponsored-deposit.ts
|
|
3223
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
3224
|
+
function makeSponsoredDepositCallback(deps) {
|
|
3225
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
3226
|
+
return async (smartWallet, chainId, amount) => {
|
|
3227
|
+
const cid = chainId;
|
|
3228
|
+
const token = deps.tokenAddressByChain[cid];
|
|
3229
|
+
if (!token) {
|
|
4467
3230
|
throw new OwneyError(
|
|
4468
3231
|
"CHAIN_UNSUPPORTED",
|
|
4469
3232
|
`No sponsored token configured for chain ${chainId}`
|
|
4470
3233
|
);
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
3234
|
+
}
|
|
3235
|
+
const pub = deps.getPublicClient(cid);
|
|
3236
|
+
const wallet = deps.getWalletClient(cid);
|
|
3237
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
3238
|
+
try {
|
|
3239
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
3240
|
+
if (balance < BigInt(amount)) {
|
|
3241
|
+
throw new OwneyError(
|
|
3242
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
3243
|
+
"Insufficient balance for this deposit.",
|
|
3244
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
} catch (err) {
|
|
3248
|
+
if (err instanceof OwneyError) throw err;
|
|
3249
|
+
console.warn(
|
|
3250
|
+
"[owney-sdk] Deposit balance pre-check failed (non-fatal):",
|
|
3251
|
+
err instanceof Error ? err.message : String(err)
|
|
4476
3252
|
);
|
|
4477
|
-
|
|
4478
|
-
await
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
3253
|
+
}
|
|
3254
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
3255
|
+
const validAfter = 0n;
|
|
3256
|
+
const validBefore = BigInt(
|
|
3257
|
+
Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
|
|
3258
|
+
);
|
|
3259
|
+
const nonce = randomAuthNonce();
|
|
3260
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
4483
3261
|
token,
|
|
4484
|
-
chainId,
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
3262
|
+
chainId: cid,
|
|
3263
|
+
tokenName,
|
|
3264
|
+
tokenVersion,
|
|
3265
|
+
message: {
|
|
3266
|
+
from: deps.ownerAddress,
|
|
3267
|
+
to: smartWallet,
|
|
3268
|
+
value: BigInt(amount),
|
|
3269
|
+
validAfter,
|
|
3270
|
+
validBefore,
|
|
3271
|
+
nonce
|
|
3272
|
+
}
|
|
3273
|
+
});
|
|
3274
|
+
const authSignature = await wallet.signTypedData({
|
|
3275
|
+
account: deps.ownerAddress,
|
|
3276
|
+
...typedData
|
|
3277
|
+
});
|
|
3278
|
+
deps.onApproved?.();
|
|
3279
|
+
const result = await post({
|
|
3280
|
+
baseUrl: deps.baseUrl,
|
|
3281
|
+
apiKey: deps.apiKey,
|
|
3282
|
+
body: {
|
|
3283
|
+
chainId: cid,
|
|
3284
|
+
token,
|
|
3285
|
+
from: deps.ownerAddress,
|
|
3286
|
+
to: smartWallet,
|
|
3287
|
+
value: amount,
|
|
3288
|
+
validAfter: validAfter.toString(),
|
|
3289
|
+
validBefore: validBefore.toString(),
|
|
3290
|
+
nonce,
|
|
3291
|
+
authSignature,
|
|
3292
|
+
tokenName,
|
|
3293
|
+
tokenVersion
|
|
3294
|
+
}
|
|
4489
3295
|
});
|
|
3296
|
+
return result.txHash;
|
|
4490
3297
|
};
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
void promise.catch(() => {
|
|
4506
|
-
});
|
|
4507
|
-
return { promise, resolve, reject };
|
|
4508
|
-
}
|
|
4509
|
-
async function runAgentDepositBatch(chainId, legs, transfer) {
|
|
4510
|
-
const funding = deferred();
|
|
4511
|
-
const tasks = [];
|
|
4512
|
-
const transfers = [];
|
|
4513
|
-
try {
|
|
4514
|
-
for (const leg of legs) {
|
|
4515
|
-
const ready = deferred();
|
|
4516
|
-
let entered = false;
|
|
4517
|
-
const callback = makeVerificationAwareDepositCallback(
|
|
4518
|
-
(to, cid, amount, verification) => {
|
|
4519
|
-
if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
|
|
4520
|
-
const error = new Error(
|
|
4521
|
-
"Agent changed its prepared deposit share."
|
|
4522
|
-
);
|
|
4523
|
-
ready.reject(error);
|
|
4524
|
-
throw error;
|
|
4525
|
-
}
|
|
4526
|
-
entered = true;
|
|
4527
|
-
ready.resolve(toBatchTransfer(to, amount, verification));
|
|
4528
|
-
return funding.promise;
|
|
4529
|
-
}
|
|
4530
|
-
);
|
|
4531
|
-
const task = Promise.resolve().then(() => leg.run(callback));
|
|
4532
|
-
tasks.push(task);
|
|
4533
|
-
void task.then(
|
|
4534
|
-
() => {
|
|
4535
|
-
if (!entered)
|
|
4536
|
-
ready.reject(
|
|
4537
|
-
new Error("Agent did not prepare a deposit transfer.")
|
|
4538
|
-
);
|
|
4539
|
-
},
|
|
4540
|
-
(error) => ready.reject(error)
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
3301
|
+
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
3302
|
+
function makeSponsoredWethCallback(deps) {
|
|
3303
|
+
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
3304
|
+
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
3305
|
+
return async (smartWallet, chainId, amount) => {
|
|
3306
|
+
const cid = chainId;
|
|
3307
|
+
const token = deps.tokenAddressByChain[cid];
|
|
3308
|
+
if (!token) {
|
|
3309
|
+
throw new OwneyError(
|
|
3310
|
+
"CHAIN_UNSUPPORTED",
|
|
3311
|
+
`No sponsored WETH configured for chain ${chainId}`
|
|
4541
3312
|
);
|
|
4542
|
-
transfers.push(await ready.promise);
|
|
4543
3313
|
}
|
|
4544
|
-
const
|
|
4545
|
-
|
|
4546
|
-
const
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
if (
|
|
4551
|
-
|
|
4552
|
-
|
|
3314
|
+
const amountWei = BigInt(amount);
|
|
3315
|
+
const pub = deps.getPublicClient(cid);
|
|
3316
|
+
const wallet = deps.getWalletClient(cid);
|
|
3317
|
+
await ensureWalletOnChain(pub, wallet, cid);
|
|
3318
|
+
try {
|
|
3319
|
+
const balance = await readErc20Balance(pub, token, deps.ownerAddress);
|
|
3320
|
+
if (balance < amountWei) {
|
|
3321
|
+
throw new OwneyError(
|
|
3322
|
+
"DEPOSIT_INSUFFICIENT_BALANCE",
|
|
3323
|
+
"Insufficient WETH balance for this deposit.",
|
|
3324
|
+
{ token, chainId: cid, balance: balance.toString(), amount }
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3327
|
+
} catch (err) {
|
|
3328
|
+
if (err instanceof OwneyError) throw err;
|
|
3329
|
+
console.warn(
|
|
3330
|
+
"[owney-sdk] WETH balance pre-check failed (non-fatal):",
|
|
3331
|
+
err instanceof Error ? err.message : String(err)
|
|
3332
|
+
);
|
|
4553
3333
|
}
|
|
4554
|
-
|
|
3334
|
+
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
3335
|
+
if (allowance < amountWei) {
|
|
4555
3336
|
throw new OwneyError(
|
|
4556
|
-
"
|
|
4557
|
-
"
|
|
4558
|
-
{
|
|
4559
|
-
txHash,
|
|
4560
|
-
fundsSubmitted: true,
|
|
4561
|
-
agentResults,
|
|
4562
|
-
failedAgentIds: failures
|
|
4563
|
-
}
|
|
3337
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
3338
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
3339
|
+
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
4564
3340
|
);
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
3341
|
+
}
|
|
3342
|
+
const relayer = await get({
|
|
3343
|
+
baseUrl: deps.baseUrl,
|
|
3344
|
+
apiKey: deps.apiKey,
|
|
3345
|
+
chainId: cid
|
|
3346
|
+
});
|
|
3347
|
+
const nonce = randomPermit2Nonce();
|
|
3348
|
+
const deadline = BigInt(
|
|
3349
|
+
Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
|
|
3350
|
+
);
|
|
3351
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
3352
|
+
chainId: cid,
|
|
3353
|
+
message: {
|
|
3354
|
+
permitted: { token, amount: amountWei },
|
|
3355
|
+
spender: relayer,
|
|
3356
|
+
nonce,
|
|
3357
|
+
deadline
|
|
3358
|
+
}
|
|
3359
|
+
});
|
|
3360
|
+
const signature = await wallet.signTypedData({
|
|
3361
|
+
account: deps.ownerAddress,
|
|
3362
|
+
...typedData
|
|
3363
|
+
});
|
|
3364
|
+
deps.onApproved?.();
|
|
3365
|
+
const result = await post({
|
|
3366
|
+
baseUrl: deps.baseUrl,
|
|
3367
|
+
apiKey: deps.apiKey,
|
|
3368
|
+
body: {
|
|
3369
|
+
chainId: cid,
|
|
3370
|
+
token,
|
|
3371
|
+
from: deps.ownerAddress,
|
|
3372
|
+
to: smartWallet,
|
|
3373
|
+
amount,
|
|
3374
|
+
nonce: nonce.toString(),
|
|
3375
|
+
deadline: deadline.toString(),
|
|
3376
|
+
signature
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
return result.txHash;
|
|
3380
|
+
};
|
|
4571
3381
|
}
|
|
4572
3382
|
|
|
4573
3383
|
// src/lib/sponsored-calls-deposit.ts
|
|
4574
|
-
var
|
|
3384
|
+
var import_viem7 = require("viem");
|
|
4575
3385
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
4576
3386
|
var DEFAULT_MAX_POLLS = 30;
|
|
4577
3387
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -4579,7 +3389,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
4579
3389
|
method: "wallet_getCapabilities",
|
|
4580
3390
|
params: [owner]
|
|
4581
3391
|
});
|
|
4582
|
-
const forChain = caps?.[(0,
|
|
3392
|
+
const forChain = caps?.[(0, import_viem7.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
4583
3393
|
return Boolean(forChain?.paymasterService?.supported);
|
|
4584
3394
|
}
|
|
4585
3395
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -4597,7 +3407,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4597
3407
|
}
|
|
4598
3408
|
return new URL(configured, origin).toString();
|
|
4599
3409
|
};
|
|
4600
|
-
|
|
3410
|
+
return async (smartWallet, chainId, amount) => {
|
|
4601
3411
|
const cid = chainId;
|
|
4602
3412
|
const token = deps.tokenAddressByChain[cid];
|
|
4603
3413
|
if (!token) {
|
|
@@ -4613,53 +3423,22 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4613
3423
|
{ chainId }
|
|
4614
3424
|
);
|
|
4615
3425
|
}
|
|
4616
|
-
const
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
functionName: "transfer",
|
|
4622
|
-
args: [transfer.to, BigInt(transfer.amount)]
|
|
4623
|
-
})
|
|
4624
|
-
}));
|
|
4625
|
-
let paymasterUrl = absolutePaymasterUrl();
|
|
4626
|
-
for (const transfer of transfers) {
|
|
4627
|
-
const verification = transfer.yieldseeker;
|
|
4628
|
-
if (!verification) continue;
|
|
4629
|
-
if (chainId !== 8453)
|
|
4630
|
-
throw new OwneyError(
|
|
4631
|
-
"CHAIN_UNSUPPORTED",
|
|
4632
|
-
`Yieldseeker sponsorship is not available on chain ${chainId}.`
|
|
4633
|
-
);
|
|
4634
|
-
const { intent } = await postPaymasterIntent({
|
|
4635
|
-
baseUrl: deps.routingApiBaseUrl,
|
|
4636
|
-
apiKey: deps.apiKey,
|
|
4637
|
-
yieldseekerSignature: verification.signature,
|
|
4638
|
-
body: {
|
|
4639
|
-
chainId,
|
|
4640
|
-
token,
|
|
4641
|
-
from: deps.ownerAddress,
|
|
4642
|
-
to: transfer.to,
|
|
4643
|
-
amount: transfer.amount,
|
|
4644
|
-
yieldseekerUserId: verification.userId,
|
|
4645
|
-
yieldseekerAgentId: verification.agentId
|
|
4646
|
-
}
|
|
4647
|
-
});
|
|
4648
|
-
const url = new URL(paymasterUrl);
|
|
4649
|
-
url.searchParams.append("owneyIntent", intent);
|
|
4650
|
-
paymasterUrl = url.toString();
|
|
4651
|
-
}
|
|
3426
|
+
const data = (0, import_viem7.encodeFunctionData)({
|
|
3427
|
+
abi: import_viem7.erc20Abi,
|
|
3428
|
+
functionName: "transfer",
|
|
3429
|
+
args: [smartWallet, BigInt(amount)]
|
|
3430
|
+
});
|
|
4652
3431
|
const sendResult = await deps.provider.request({
|
|
4653
3432
|
method: "wallet_sendCalls",
|
|
4654
3433
|
params: [
|
|
4655
3434
|
{
|
|
4656
3435
|
version: "2.0.0",
|
|
4657
3436
|
from: deps.ownerAddress,
|
|
4658
|
-
chainId: (0,
|
|
4659
|
-
atomicRequired:
|
|
4660
|
-
calls,
|
|
3437
|
+
chainId: (0, import_viem7.toHex)(chainId),
|
|
3438
|
+
atomicRequired: false,
|
|
3439
|
+
calls: [{ to: token, value: "0x0", data }],
|
|
4661
3440
|
capabilities: {
|
|
4662
|
-
paymasterService: { url:
|
|
3441
|
+
paymasterService: { url: absolutePaymasterUrl() }
|
|
4663
3442
|
}
|
|
4664
3443
|
}
|
|
4665
3444
|
]
|
|
@@ -4679,24 +3458,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4679
3458
|
params: [callsId]
|
|
4680
3459
|
});
|
|
4681
3460
|
const txHash = status?.receipts?.[0]?.transactionHash;
|
|
4682
|
-
if (
|
|
4683
|
-
throw new OwneyError(
|
|
4684
|
-
"SPONSOR_REQUEST_FAILED",
|
|
4685
|
-
"The sponsored deposit did not complete successfully.",
|
|
4686
|
-
{ chainId, callsId, safeToFallback: false }
|
|
4687
|
-
);
|
|
4688
|
-
}
|
|
4689
|
-
if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
|
|
4690
|
-
if (status?.receipts?.some(
|
|
4691
|
-
(receipt) => receipt.transactionHash !== txHash
|
|
4692
|
-
))
|
|
4693
|
-
throw new OwneyError(
|
|
4694
|
-
"SPONSORED_CALLS_NO_RECEIPT",
|
|
4695
|
-
"The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
|
|
4696
|
-
{ chainId, callsId }
|
|
4697
|
-
);
|
|
4698
|
-
return txHash;
|
|
4699
|
-
}
|
|
3461
|
+
if (txHash) return txHash;
|
|
4700
3462
|
if (pollIntervalMs > 0) {
|
|
4701
3463
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
4702
3464
|
}
|
|
@@ -4707,16 +3469,9 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
4707
3469
|
{ chainId, callsId }
|
|
4708
3470
|
);
|
|
4709
3471
|
};
|
|
4710
|
-
const callback = makeVerificationAwareDepositCallback(
|
|
4711
|
-
(to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
|
|
4712
|
-
);
|
|
4713
|
-
registerDepositBatch(callback, batch);
|
|
4714
|
-
return callback;
|
|
4715
3472
|
}
|
|
4716
3473
|
|
|
4717
3474
|
// src/client.ts
|
|
4718
|
-
var PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS = 6;
|
|
4719
|
-
var PERMIT2_ALLOWANCE_VERIFY_DELAY_MS = 250;
|
|
4720
3475
|
function encodeMultiAgentCursor(map) {
|
|
4721
3476
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
4722
3477
|
}
|
|
@@ -4743,20 +3498,15 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
4743
3498
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
4744
3499
|
};
|
|
4745
3500
|
var VIEM_CHAIN2 = {
|
|
4746
|
-
8453:
|
|
4747
|
-
42161:
|
|
4748
|
-
1:
|
|
3501
|
+
8453: import_chains2.base,
|
|
3502
|
+
42161: import_chains2.arbitrum,
|
|
3503
|
+
1: import_chains2.mainnet
|
|
4749
3504
|
};
|
|
4750
3505
|
var SPONSORED_WETH_BY_CHAIN = {
|
|
4751
3506
|
8453: "0x4200000000000000000000000000000000000006",
|
|
4752
3507
|
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
4753
3508
|
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
4754
3509
|
};
|
|
4755
|
-
var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
|
|
4756
|
-
function sponsoredTokensFor(asset) {
|
|
4757
|
-
if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
|
|
4758
|
-
return SPONSORED_TOKENS_BY_ASSET[asset];
|
|
4759
|
-
}
|
|
4760
3510
|
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
4761
3511
|
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
4762
3512
|
}
|
|
@@ -4778,8 +3528,6 @@ var OwneySDK = class {
|
|
|
4778
3528
|
orgAgentConfig;
|
|
4779
3529
|
orgAgentConfigPromise = null;
|
|
4780
3530
|
zyfaiRpcUrls;
|
|
4781
|
-
yieldseekerApiBaseUrl;
|
|
4782
|
-
yieldseekerSiweOrigin;
|
|
4783
3531
|
routingApiBaseUrl;
|
|
4784
3532
|
referralSource;
|
|
4785
3533
|
cachedSponsoredCallback = null;
|
|
@@ -4802,8 +3550,6 @@ var OwneySDK = class {
|
|
|
4802
3550
|
this.apiKey = config.apiKey;
|
|
4803
3551
|
if (config.debug) setOwneyDebug(true);
|
|
4804
3552
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
4805
|
-
this.yieldseekerApiBaseUrl = config.yieldseekerApiBaseUrl;
|
|
4806
|
-
this.yieldseekerSiweOrigin = config.yieldseekerSiweOrigin;
|
|
4807
3553
|
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
4808
3554
|
this.paymasterServiceUrl = config.paymasterServiceUrl;
|
|
4809
3555
|
this.referralSource = config.referralSource;
|
|
@@ -4837,7 +3583,6 @@ var OwneySDK = class {
|
|
|
4837
3583
|
* After calling this, `connect()` must be called again before using agent methods.
|
|
4838
3584
|
*/
|
|
4839
3585
|
async disconnect() {
|
|
4840
|
-
this.state = null;
|
|
4841
3586
|
for (const agent of this.agents.values()) {
|
|
4842
3587
|
await agent.disconnect();
|
|
4843
3588
|
}
|
|
@@ -4891,13 +3636,18 @@ var OwneySDK = class {
|
|
|
4891
3636
|
}
|
|
4892
3637
|
return this.state.provider;
|
|
4893
3638
|
}
|
|
4894
|
-
/**
|
|
3639
|
+
/**
|
|
3640
|
+
* Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
|
|
3641
|
+
* used when the caller omits `depositCallback`. Wraps the connected EIP-1193
|
|
3642
|
+
* provider with viem `custom(provider)` to read token meta and sign the
|
|
3643
|
+
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
3644
|
+
*/
|
|
4895
3645
|
getDefaultSponsoredCallback(onApproved) {
|
|
4896
3646
|
if (!onApproved && this.cachedSponsoredCallback)
|
|
4897
3647
|
return this.cachedSponsoredCallback;
|
|
4898
3648
|
const provider = this.requireConnectedProvider();
|
|
4899
3649
|
const owner = this.state.walletAddress;
|
|
4900
|
-
const callback =
|
|
3650
|
+
const callback = makeSponsoredDepositCallback({
|
|
4901
3651
|
apiKey: this.apiKey,
|
|
4902
3652
|
baseUrl: this.routingApiBaseUrl,
|
|
4903
3653
|
ownerAddress: owner,
|
|
@@ -4906,32 +3656,35 @@ var OwneySDK = class {
|
|
|
4906
3656
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4907
3657
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4908
3658
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4909
|
-
getPublicClient: (cid) => (0,
|
|
3659
|
+
getPublicClient: (cid) => (0, import_viem8.createPublicClient)({
|
|
4910
3660
|
chain: VIEM_CHAIN2[cid],
|
|
4911
|
-
transport: (0,
|
|
3661
|
+
transport: (0, import_viem8.custom)(provider)
|
|
4912
3662
|
}),
|
|
4913
|
-
getWalletClient: (cid) => (0,
|
|
3663
|
+
getWalletClient: (cid) => (0, import_viem8.createWalletClient)({
|
|
4914
3664
|
account: owner,
|
|
4915
3665
|
chain: VIEM_CHAIN2[cid],
|
|
4916
|
-
transport: (0,
|
|
3666
|
+
transport: (0, import_viem8.custom)(provider)
|
|
4917
3667
|
})
|
|
4918
3668
|
});
|
|
4919
3669
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
4920
3670
|
return callback;
|
|
4921
3671
|
}
|
|
4922
|
-
/**
|
|
3672
|
+
/**
|
|
3673
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
3674
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
3675
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
3676
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
3677
|
+
*/
|
|
4923
3678
|
getDefaultSponsoredCallsCallback(asset, onApproved) {
|
|
4924
3679
|
const cached = this.cachedSponsoredCallsCallbacks.get(asset);
|
|
4925
3680
|
if (!onApproved && cached) return cached;
|
|
4926
3681
|
const provider = this.requireConnectedProvider();
|
|
4927
3682
|
const callback = makeSponsoredCallsCallback({
|
|
4928
|
-
apiKey: this.apiKey,
|
|
4929
|
-
routingApiBaseUrl: this.routingApiBaseUrl,
|
|
4930
3683
|
provider,
|
|
4931
3684
|
ownerAddress: this.state.walletAddress,
|
|
4932
3685
|
paymasterServiceUrl: this.paymasterServiceUrl,
|
|
4933
3686
|
onApproved,
|
|
4934
|
-
tokenAddressByChain:
|
|
3687
|
+
tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
|
|
4935
3688
|
});
|
|
4936
3689
|
if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
|
|
4937
3690
|
return callback;
|
|
@@ -4940,14 +3693,14 @@ var OwneySDK = class {
|
|
|
4940
3693
|
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
4941
3694
|
* callback used when the caller omits `depositCallback` for a WETH deposit.
|
|
4942
3695
|
* Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
4943
|
-
*
|
|
3696
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
4944
3697
|
*/
|
|
4945
3698
|
getDefaultWethSponsoredCallback(onApproved) {
|
|
4946
3699
|
if (!onApproved && this.cachedWethSponsoredCallback)
|
|
4947
3700
|
return this.cachedWethSponsoredCallback;
|
|
4948
3701
|
const provider = this.requireConnectedProvider();
|
|
4949
3702
|
const owner = this.state.walletAddress;
|
|
4950
|
-
const callback =
|
|
3703
|
+
const callback = makeSponsoredWethCallback({
|
|
4951
3704
|
apiKey: this.apiKey,
|
|
4952
3705
|
baseUrl: this.routingApiBaseUrl,
|
|
4953
3706
|
ownerAddress: owner,
|
|
@@ -4956,14 +3709,14 @@ var OwneySDK = class {
|
|
|
4956
3709
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
4957
3710
|
// PublicClient/WalletClient param types — structurally identical at
|
|
4958
3711
|
// runtime, but the two share a name TS treats as unrelated.
|
|
4959
|
-
getPublicClient: (cid) => (0,
|
|
3712
|
+
getPublicClient: (cid) => (0, import_viem8.createPublicClient)({
|
|
4960
3713
|
chain: VIEM_CHAIN2[cid],
|
|
4961
|
-
transport: (0,
|
|
3714
|
+
transport: (0, import_viem8.custom)(provider)
|
|
4962
3715
|
}),
|
|
4963
|
-
getWalletClient: (cid) => (0,
|
|
3716
|
+
getWalletClient: (cid) => (0, import_viem8.createWalletClient)({
|
|
4964
3717
|
account: owner,
|
|
4965
3718
|
chain: VIEM_CHAIN2[cid],
|
|
4966
|
-
transport: (0,
|
|
3719
|
+
transport: (0, import_viem8.custom)(provider)
|
|
4967
3720
|
})
|
|
4968
3721
|
});
|
|
4969
3722
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -5037,14 +3790,7 @@ var OwneySDK = class {
|
|
|
5037
3790
|
this.routingApiBaseUrl
|
|
5038
3791
|
);
|
|
5039
3792
|
this.disabledAgents.clear();
|
|
5040
|
-
for (const {
|
|
5041
|
-
key: key2,
|
|
5042
|
-
agent_type,
|
|
5043
|
-
is_enabled,
|
|
5044
|
-
is_configured
|
|
5045
|
-
} of agentKeys) {
|
|
5046
|
-
const configured = is_configured ?? Boolean(key2);
|
|
5047
|
-
if (!configured) continue;
|
|
3793
|
+
for (const { key: key2, agent_type, is_enabled } of agentKeys) {
|
|
5048
3794
|
const agent = this.createAgent(agent_type, key2);
|
|
5049
3795
|
if (!agent) continue;
|
|
5050
3796
|
this.agents.set(agent_type, agent);
|
|
@@ -5068,15 +3814,8 @@ var OwneySDK = class {
|
|
|
5068
3814
|
}
|
|
5069
3815
|
createAgent(agentId, key2) {
|
|
5070
3816
|
if (agentId === "zyfai") {
|
|
5071
|
-
if (!key2) return null;
|
|
5072
3817
|
return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
|
|
5073
3818
|
}
|
|
5074
|
-
if (agentId === "yieldseeker") {
|
|
5075
|
-
return new YieldseekerAgent(this.apiKey, {
|
|
5076
|
-
auth: { origin: this.yieldseekerSiweOrigin },
|
|
5077
|
-
baseUrl: this.yieldseekerApiBaseUrl ?? getYieldseekerProxyBaseUrl(this.routingApiBaseUrl)
|
|
5078
|
-
});
|
|
5079
|
-
}
|
|
5080
3819
|
return null;
|
|
5081
3820
|
}
|
|
5082
3821
|
/**
|
|
@@ -5121,10 +3860,9 @@ var OwneySDK = class {
|
|
|
5121
3860
|
* If provided, ALL specified agents must support the chainId or the call
|
|
5122
3861
|
* throws before activating any agent.
|
|
5123
3862
|
*/
|
|
5124
|
-
async activateAgent(chainId, agentId
|
|
3863
|
+
async activateAgent(chainId, agentId) {
|
|
5125
3864
|
const state = this.requireState();
|
|
5126
3865
|
await this.ensureAgentsInitialized();
|
|
5127
|
-
this.assertActivationSession(state);
|
|
5128
3866
|
if (agentId !== void 0) {
|
|
5129
3867
|
if (agentId.length === 0) {
|
|
5130
3868
|
throw new OwneyError(
|
|
@@ -5158,7 +3896,7 @@ var OwneySDK = class {
|
|
|
5158
3896
|
this.activeAgents.add(id);
|
|
5159
3897
|
}
|
|
5160
3898
|
state.chainId = chainId;
|
|
5161
|
-
await this.activateAgentsInTurn(agents, state, chainId
|
|
3899
|
+
await this.activateAgentsInTurn(agents, state, chainId);
|
|
5162
3900
|
return;
|
|
5163
3901
|
}
|
|
5164
3902
|
const compatible = [...this.agents.values()].filter(
|
|
@@ -5179,12 +3917,7 @@ var OwneySDK = class {
|
|
|
5179
3917
|
const enabledCompatible = compatible.filter(
|
|
5180
3918
|
(agent) => !this.isAgentDisabled(agent.id)
|
|
5181
3919
|
);
|
|
5182
|
-
await this.activateAgentsInTurn(enabledCompatible, state, chainId
|
|
5183
|
-
}
|
|
5184
|
-
assertActivationSession(state) {
|
|
5185
|
-
if (this.state !== state) {
|
|
5186
|
-
throw new OwneyError("NOT_CONNECTED", "Wallet connection changed. Please try again.");
|
|
5187
|
-
}
|
|
3920
|
+
await this.activateAgentsInTurn(enabledCompatible, state, chainId);
|
|
5188
3921
|
}
|
|
5189
3922
|
/**
|
|
5190
3923
|
* Activate agents ONE AT A TIME, each followed by its org policy.
|
|
@@ -5199,51 +3932,26 @@ var OwneySDK = class {
|
|
|
5199
3932
|
* Serializing costs no real wall-clock: the user can only approve one prompt
|
|
5200
3933
|
* at a time anyway.
|
|
5201
3934
|
*
|
|
5202
|
-
*
|
|
5203
|
-
*
|
|
5204
|
-
*
|
|
3935
|
+
* Every agent is attempted even if an earlier one fails, so one declined
|
|
3936
|
+
* signature can't deny the remaining agents their turn. The first failure is
|
|
3937
|
+
* rethrown (matching the previous `Promise.all` rejection) once all agents
|
|
3938
|
+
* have had a chance to activate.
|
|
5205
3939
|
*/
|
|
5206
|
-
async activateAgentsInTurn(agents, state, chainId
|
|
3940
|
+
async activateAgentsInTurn(agents, state, chainId) {
|
|
5207
3941
|
let firstError = null;
|
|
5208
|
-
const activatedAgentIds = [];
|
|
5209
|
-
const failedAgents = [];
|
|
5210
3942
|
for (const agent of agents) {
|
|
5211
|
-
this.assertActivationSession(state);
|
|
5212
3943
|
try {
|
|
5213
|
-
await agent.activateAgent(state, chainId
|
|
5214
|
-
this.assertActivationSession(state);
|
|
3944
|
+
await agent.activateAgent(state, chainId);
|
|
5215
3945
|
await this.applyOrgPolicyTo(agent, state, chainId);
|
|
5216
|
-
this.assertActivationSession(state);
|
|
5217
|
-
activatedAgentIds.push(agent.id);
|
|
5218
3946
|
} catch (error) {
|
|
5219
|
-
this.assertActivationSession(state);
|
|
5220
|
-
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.";
|
|
5221
|
-
failedAgents.push({
|
|
5222
|
-
agentId: agent.id,
|
|
5223
|
-
code: error instanceof OwneyError ? error.code : void 0,
|
|
5224
|
-
message,
|
|
5225
|
-
...error instanceof OwneyError && error.details ? { details: error.details } : {}
|
|
5226
|
-
});
|
|
5227
3947
|
if (firstError === null) {
|
|
5228
3948
|
firstError = error;
|
|
5229
3949
|
} else {
|
|
5230
3950
|
console.error(`activateAgent(${agent.id}) failed:`, error);
|
|
5231
3951
|
}
|
|
5232
|
-
break;
|
|
5233
3952
|
}
|
|
5234
3953
|
}
|
|
5235
|
-
if (firstError
|
|
5236
|
-
if (activatedAgentIds.length === 0) throw firstError;
|
|
5237
|
-
const failedAgentIds = failedAgents.map(({ agentId }) => agentId);
|
|
5238
|
-
const activeNames = activatedAgentIds.map(this.formatAgentName).join(", ");
|
|
5239
|
-
const failureMessages = failedAgents.map(
|
|
5240
|
-
({ agentId, message }) => `${this.formatAgentName(agentId)} activation failed: ${message}`
|
|
5241
|
-
).join(" ");
|
|
5242
|
-
throw new OwneyError(
|
|
5243
|
-
"AGENT_ACTIVATION_PARTIAL_FAILURE",
|
|
5244
|
-
`${activeNames} activated. ${failureMessages}`,
|
|
5245
|
-
{ activatedAgentIds, failedAgentIds, failures: failedAgents }
|
|
5246
|
-
);
|
|
3954
|
+
if (firstError !== null) throw firstError;
|
|
5247
3955
|
}
|
|
5248
3956
|
/**
|
|
5249
3957
|
* Deposit into a specific agent, or distribute across all eligible agents.
|
|
@@ -5253,8 +3961,7 @@ var OwneySDK = class {
|
|
|
5253
3961
|
* @param options.asset - Asset symbol to deposit (e.g. "USDC")
|
|
5254
3962
|
* @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
|
|
5255
3963
|
* When agentId is omitted, this callback is invoked once per eligible agent with that agent's
|
|
5256
|
-
* split amount and smart wallet address
|
|
5257
|
-
* all shares into one signature; custom callbacks still run once per agent.
|
|
3964
|
+
* split amount and smart wallet address — expect multiple wallet prompts.
|
|
5258
3965
|
* @param options.agentId - Optional explicit target. Otherwise split equally,
|
|
5259
3966
|
* or fund remaining agents when a recovery deposit cannot meet every minimum.
|
|
5260
3967
|
* @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
|
|
@@ -5339,40 +4046,6 @@ var OwneySDK = class {
|
|
|
5339
4046
|
}
|
|
5340
4047
|
);
|
|
5341
4048
|
}
|
|
5342
|
-
const batchTransfer = getDepositBatchTransfer(effectiveCallback);
|
|
5343
|
-
if (!depositCallback && batchTransfer) {
|
|
5344
|
-
return runAgentDepositBatch(
|
|
5345
|
-
chainId,
|
|
5346
|
-
agentAmounts.map(({ agent, amount: amount2 }) => ({
|
|
5347
|
-
id: agent.id,
|
|
5348
|
-
amount: amount2,
|
|
5349
|
-
run: (callback) => withFailureReporting(
|
|
5350
|
-
this.apiKey,
|
|
5351
|
-
agent.id,
|
|
5352
|
-
() => agent.deposit(state, chainId, amount2, asset, callback),
|
|
5353
|
-
this.routingApiBaseUrl
|
|
5354
|
-
)
|
|
5355
|
-
})),
|
|
5356
|
-
async (cid, transfers) => {
|
|
5357
|
-
try {
|
|
5358
|
-
return await batchTransfer(cid, transfers);
|
|
5359
|
-
} catch (error) {
|
|
5360
|
-
if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
|
|
5361
|
-
throw error;
|
|
5362
|
-
const requiredAmount = transfers.reduce(
|
|
5363
|
-
(sum, transfer) => sum + BigInt(transfer.amount),
|
|
5364
|
-
0n
|
|
5365
|
-
);
|
|
5366
|
-
await this.approvePermit2(
|
|
5367
|
-
asset,
|
|
5368
|
-
requiredAmount,
|
|
5369
|
-
cid
|
|
5370
|
-
);
|
|
5371
|
-
return batchTransfer(cid, transfers);
|
|
5372
|
-
}
|
|
5373
|
-
}
|
|
5374
|
-
);
|
|
5375
|
-
}
|
|
5376
4049
|
const agentResults = {};
|
|
5377
4050
|
for (const [
|
|
5378
4051
|
index,
|
|
@@ -5412,7 +4085,7 @@ var OwneySDK = class {
|
|
|
5412
4085
|
*
|
|
5413
4086
|
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
5414
4087
|
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
5415
|
-
*
|
|
4088
|
+
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
5416
4089
|
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
5417
4090
|
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
5418
4091
|
* per call so a wallet/agent that keeps reporting the allowance as
|
|
@@ -5449,16 +4122,12 @@ var OwneySDK = class {
|
|
|
5449
4122
|
try {
|
|
5450
4123
|
return await attempt(effectiveCallback);
|
|
5451
4124
|
} catch (error) {
|
|
5452
|
-
if (!approvalAttempted && appCallback === void 0 &&
|
|
4125
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
5453
4126
|
approvalAttempted = true;
|
|
5454
4127
|
console.warn(
|
|
5455
|
-
"[owney-sdk] First
|
|
5456
|
-
);
|
|
5457
|
-
await this.approvePermit2(
|
|
5458
|
-
asset,
|
|
5459
|
-
BigInt(amount),
|
|
5460
|
-
chainId
|
|
4128
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
5461
4129
|
);
|
|
4130
|
+
await this.approvePermit2();
|
|
5462
4131
|
continue;
|
|
5463
4132
|
}
|
|
5464
4133
|
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
@@ -5496,10 +4165,10 @@ var OwneySDK = class {
|
|
|
5496
4165
|
agent,
|
|
5497
4166
|
amount: i === agents.length - 1 ? perAgent + remainder : perAgent
|
|
5498
4167
|
}));
|
|
5499
|
-
const
|
|
4168
|
+
const valid = splits.filter(
|
|
5500
4169
|
(s) => s.amount > 0n && (exempt.has(s.agent.id) || s.amount >= this.getMinDepositAmount(s.agent, chainId, asset))
|
|
5501
4170
|
);
|
|
5502
|
-
if (
|
|
4171
|
+
if (valid.length === agents.length) {
|
|
5503
4172
|
return splits.map((s) => ({ agent: s.agent, amount: String(s.amount) }));
|
|
5504
4173
|
}
|
|
5505
4174
|
const targets = agentsRequiringActivation.length > 0 ? agentsRequiringActivation : agents;
|
|
@@ -5518,11 +4187,6 @@ var OwneySDK = class {
|
|
|
5518
4187
|
)
|
|
5519
4188
|
}));
|
|
5520
4189
|
}
|
|
5521
|
-
formatAgentName(agentId) {
|
|
5522
|
-
if (agentId === "zyfai") return "Zyfai";
|
|
5523
|
-
if (agentId === "yieldseeker") return "Yieldseeker";
|
|
5524
|
-
return agentId;
|
|
5525
|
-
}
|
|
5526
4190
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
5527
4191
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
5528
4192
|
const parsedAmount = BigInt(amount);
|
|
@@ -5554,12 +4218,12 @@ var OwneySDK = class {
|
|
|
5554
4218
|
(t) => t.chainId === chainId && t.asset.toLowerCase() === target
|
|
5555
4219
|
);
|
|
5556
4220
|
const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
|
|
5557
|
-
const
|
|
4221
|
+
const position = (balance.positions ?? []).find((p) => {
|
|
5558
4222
|
const positionChain = p.chain.trim().toUpperCase();
|
|
5559
4223
|
const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
|
|
5560
4224
|
return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
|
|
5561
4225
|
});
|
|
5562
|
-
return !!token && Number(token.amount) > 0 || !!
|
|
4226
|
+
return !!token && Number(token.amount) > 0 || !!position;
|
|
5563
4227
|
} catch (error) {
|
|
5564
4228
|
if (requireReliableRead) {
|
|
5565
4229
|
throw new OwneyError(
|
|
@@ -5605,6 +4269,354 @@ var OwneySDK = class {
|
|
|
5605
4269
|
return eligible;
|
|
5606
4270
|
}
|
|
5607
4271
|
// --- Fund operations ---
|
|
4272
|
+
// --- Swap to yield (ROUT-242) ---
|
|
4273
|
+
/** Lazily built so an app that never swaps pays nothing for it. */
|
|
4274
|
+
swapApiClient;
|
|
4275
|
+
swapApi() {
|
|
4276
|
+
this.swapApiClient ??= createSwapApi(
|
|
4277
|
+
this.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
|
|
4278
|
+
this.apiKey
|
|
4279
|
+
);
|
|
4280
|
+
return this.swapApiClient;
|
|
4281
|
+
}
|
|
4282
|
+
/**
|
|
4283
|
+
* Put the wallet on `chainId`, or fail with something actionable.
|
|
4284
|
+
*
|
|
4285
|
+
* Reuses the same guard the deposit rail uses, which re-reads the chain after
|
|
4286
|
+
* switching — some wallets resolve wallet_switchEthereumChain before the
|
|
4287
|
+
* network has actually changed.
|
|
4288
|
+
*/
|
|
4289
|
+
async ensureSwapChain(chainId) {
|
|
4290
|
+
const provider = this.requireConnectedProvider();
|
|
4291
|
+
const state = this.requireState();
|
|
4292
|
+
const chain = VIEM_CHAIN2[chainId];
|
|
4293
|
+
if (!chain) {
|
|
4294
|
+
throw new OwneyError(
|
|
4295
|
+
"CHAIN_UNSUPPORTED",
|
|
4296
|
+
`Chain ${chainId} is not supported`,
|
|
4297
|
+
{ chainId }
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4300
|
+
await ensureWalletOnChain(
|
|
4301
|
+
(0, import_viem8.createPublicClient)({ chain, transport: (0, import_viem8.custom)(provider) }),
|
|
4302
|
+
(0, import_viem8.createWalletClient)({
|
|
4303
|
+
account: state.walletAddress,
|
|
4304
|
+
chain,
|
|
4305
|
+
transport: (0, import_viem8.custom)(provider)
|
|
4306
|
+
}),
|
|
4307
|
+
chainId
|
|
4308
|
+
);
|
|
4309
|
+
}
|
|
4310
|
+
/**
|
|
4311
|
+
* Binds the executor's abstract deps to this client's wallet.
|
|
4312
|
+
*
|
|
4313
|
+
* Kept as a builder rather than baked into the executor so the whole swap
|
|
4314
|
+
* flow stays testable without a provider — the executor never imports viem.
|
|
4315
|
+
*/
|
|
4316
|
+
buildSwapDeps(quote) {
|
|
4317
|
+
const state = this.requireState();
|
|
4318
|
+
const provider = this.requireConnectedProvider();
|
|
4319
|
+
const srcChain = VIEM_CHAIN2[quote.src.chainId];
|
|
4320
|
+
const dstChain = VIEM_CHAIN2[quote.dst.chainId];
|
|
4321
|
+
const wallet = (0, import_viem8.createWalletClient)({
|
|
4322
|
+
account: state.walletAddress,
|
|
4323
|
+
chain: srcChain,
|
|
4324
|
+
transport: (0, import_viem8.custom)(provider)
|
|
4325
|
+
});
|
|
4326
|
+
const srcPublic = (0, import_viem8.createPublicClient)({
|
|
4327
|
+
chain: srcChain,
|
|
4328
|
+
transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
|
|
4329
|
+
});
|
|
4330
|
+
const dstPublic = (0, import_viem8.createPublicClient)({
|
|
4331
|
+
chain: dstChain,
|
|
4332
|
+
transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
|
|
4333
|
+
});
|
|
4334
|
+
return {
|
|
4335
|
+
api: this.swapApi(),
|
|
4336
|
+
// Native-aware, like readSourceBalance below. Native ETH is never a
|
|
4337
|
+
// DEPOSIT target, so this only ever mattered once withdrawal shipped —
|
|
4338
|
+
// and there it is the headline case. balanceOf() on the 0xEeee sentinel
|
|
4339
|
+
// reverts, which would have read as "the swap landed nothing".
|
|
4340
|
+
readTargetBalance: async () => {
|
|
4341
|
+
const dst = quote.dst.address;
|
|
4342
|
+
if (dst.toLowerCase().startsWith("0xeeee")) {
|
|
4343
|
+
return dstPublic.getBalance({ address: state.walletAddress });
|
|
4344
|
+
}
|
|
4345
|
+
return dstPublic.readContract({
|
|
4346
|
+
address: dst,
|
|
4347
|
+
abi: import_viem8.erc20Abi,
|
|
4348
|
+
functionName: "balanceOf",
|
|
4349
|
+
args: [state.walletAddress]
|
|
4350
|
+
});
|
|
4351
|
+
},
|
|
4352
|
+
sendTransaction: async (tx) => {
|
|
4353
|
+
const hash = await wallet.sendTransaction({
|
|
4354
|
+
to: tx.to,
|
|
4355
|
+
data: tx.data,
|
|
4356
|
+
value: BigInt(tx.value || "0"),
|
|
4357
|
+
account: state.walletAddress,
|
|
4358
|
+
chain: srcChain
|
|
4359
|
+
});
|
|
4360
|
+
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
4361
|
+
timeout: receiptTimeoutMs(quote.src.chainId),
|
|
4362
|
+
hash,
|
|
4363
|
+
confirmations: 1
|
|
4364
|
+
});
|
|
4365
|
+
if (receipt.status !== "success") {
|
|
4366
|
+
throw new OwneyError(
|
|
4367
|
+
"SWAP_REQUEST_FAILED",
|
|
4368
|
+
`Swap transaction reverted (tx ${hash})`,
|
|
4369
|
+
{ hash }
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
return hash;
|
|
4373
|
+
},
|
|
4374
|
+
signTypedData: (typedData) => wallet.signTypedData({
|
|
4375
|
+
account: state.walletAddress,
|
|
4376
|
+
...typedData
|
|
4377
|
+
}),
|
|
4378
|
+
// Chain-bound like every other read here: the wallet provider's chain is
|
|
4379
|
+
// not ours to rely on mid-swap.
|
|
4380
|
+
readSourceBalance: async () => {
|
|
4381
|
+
const src = quote.src.address;
|
|
4382
|
+
if (src.toLowerCase().startsWith("0xeeee")) {
|
|
4383
|
+
return srcPublic.getBalance({ address: state.walletAddress });
|
|
4384
|
+
}
|
|
4385
|
+
return srcPublic.readContract({
|
|
4386
|
+
address: src,
|
|
4387
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4388
|
+
functionName: "balanceOf",
|
|
4389
|
+
args: [state.walletAddress]
|
|
4390
|
+
});
|
|
4391
|
+
},
|
|
4392
|
+
readAllowance: (spender) => srcPublic.readContract({
|
|
4393
|
+
address: quote.src.address,
|
|
4394
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4395
|
+
functionName: "allowance",
|
|
4396
|
+
args: [state.walletAddress, spender]
|
|
4397
|
+
}),
|
|
4398
|
+
approve: async (spender, amount) => {
|
|
4399
|
+
const hash = await wallet.writeContract({
|
|
4400
|
+
address: quote.src.address,
|
|
4401
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4402
|
+
functionName: "approve",
|
|
4403
|
+
args: [spender, amount],
|
|
4404
|
+
account: state.walletAddress,
|
|
4405
|
+
chain: srcChain
|
|
4406
|
+
});
|
|
4407
|
+
await srcPublic.waitForTransactionReceipt({
|
|
4408
|
+
hash,
|
|
4409
|
+
confirmations: 1,
|
|
4410
|
+
timeout: receiptTimeoutMs(quote.src.chainId)
|
|
4411
|
+
});
|
|
4412
|
+
return hash;
|
|
4413
|
+
},
|
|
4414
|
+
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4415
|
+
runner: {
|
|
4416
|
+
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4417
|
+
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
4418
|
+
orderStatus: (h) => this.swapApi().orderStatus(h),
|
|
4419
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4420
|
+
now: () => Date.now()
|
|
4421
|
+
}
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
/**
|
|
4425
|
+
* Assets the user may pay with, and what each chain deposits into.
|
|
4426
|
+
*
|
|
4427
|
+
* The source list is deliberately wider than the deposit list: it includes
|
|
4428
|
+
* native ETH and USDT, which Owney never holds but users often do.
|
|
4429
|
+
*/
|
|
4430
|
+
async getSwapTokens() {
|
|
4431
|
+
return this.swapApi().listTokens();
|
|
4432
|
+
}
|
|
4433
|
+
/**
|
|
4434
|
+
* Search the assets a user may pay with, across every supported chain.
|
|
4435
|
+
*
|
|
4436
|
+
* `getSwapTokens` returns the short list worth rendering unprompted. This
|
|
4437
|
+
* reaches everything else the routing API will accept — thousands per chain
|
|
4438
|
+
* once the wider allowlist is enabled, which is why it is a query rather
|
|
4439
|
+
* than a download.
|
|
4440
|
+
*
|
|
4441
|
+
* Results are filtered server-side to what a quote will accept, so anything
|
|
4442
|
+
* returned can be paid with. They are NOT ranked by trustworthiness: several
|
|
4443
|
+
* tokens can share a ticker, and `providers` (how many token lists carry the
|
|
4444
|
+
* address) is the only usable signal for telling them apart. Surface it.
|
|
4445
|
+
*
|
|
4446
|
+
* Returns nothing for a blank query rather than asking for the whole list.
|
|
4447
|
+
*/
|
|
4448
|
+
async searchSwapTokens(params) {
|
|
4449
|
+
const query = params.query.trim();
|
|
4450
|
+
if (!query) return { tokens: [] };
|
|
4451
|
+
return this.swapApi().searchTokens({
|
|
4452
|
+
query,
|
|
4453
|
+
...params.chainId === void 0 ? {} : { chainId: params.chainId },
|
|
4454
|
+
...params.limit === void 0 ? {} : { limit: params.limit }
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4457
|
+
/**
|
|
4458
|
+
* Price a swap without committing to it.
|
|
4459
|
+
*
|
|
4460
|
+
* `dstAmountMin` is the number to validate against a deposit minimum —
|
|
4461
|
+
* `dst.amount` is an estimate that a decaying auction or slippage can undercut,
|
|
4462
|
+
* and a swap landing below the floor leaves the user swapped but not
|
|
4463
|
+
* deposited.
|
|
4464
|
+
*/
|
|
4465
|
+
async getSwapQuote(params) {
|
|
4466
|
+
const state = this.requireState();
|
|
4467
|
+
return this.swapApi().quote({
|
|
4468
|
+
...params,
|
|
4469
|
+
walletAddress: state.walletAddress
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4472
|
+
/**
|
|
4473
|
+
* Swap an asset the user holds into a deposit asset, then deposit it.
|
|
4474
|
+
*
|
|
4475
|
+
* Kept separate from `deposit()` rather than bolted on as an option: the
|
|
4476
|
+
* return shape differs, the staging callback is meaningless on the plain
|
|
4477
|
+
* path, and integrators who never swap should not have to reason about any
|
|
4478
|
+
* of it.
|
|
4479
|
+
*
|
|
4480
|
+
* The deposit runs on the MEASURED arrival, not the quote. A quote is an
|
|
4481
|
+
* estimate, so depositing the quoted figure would either strand dust or try
|
|
4482
|
+
* to move funds that never came.
|
|
4483
|
+
*
|
|
4484
|
+
* Failure modes differ in a way callers must respect. A same-chain swap is
|
|
4485
|
+
* atomic — if it fails, nothing moved. A cross-chain swap escrows the user's
|
|
4486
|
+
* funds first, so SWAP_ORDER_EXPIRED / REFUNDED / CANCELLED all mean the
|
|
4487
|
+
* money left the wallet. Only the former can honestly say "nothing has left
|
|
4488
|
+
* your wallet".
|
|
4489
|
+
*/
|
|
4490
|
+
async swapAndDeposit(options) {
|
|
4491
|
+
const state = this.requireState();
|
|
4492
|
+
const api = this.swapApi();
|
|
4493
|
+
const quote = await api.quote({
|
|
4494
|
+
from: options.from,
|
|
4495
|
+
to: options.to,
|
|
4496
|
+
walletAddress: state.walletAddress
|
|
4497
|
+
});
|
|
4498
|
+
await this.ensureSwapChain(quote.src.chainId);
|
|
4499
|
+
const swap = await executeSwap(this.buildSwapDeps(quote), {
|
|
4500
|
+
quote,
|
|
4501
|
+
walletAddress: state.walletAddress,
|
|
4502
|
+
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4503
|
+
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4504
|
+
});
|
|
4505
|
+
options.onSwapProgress?.("depositing");
|
|
4506
|
+
await this.ensureSwapChain(quote.dst.chainId);
|
|
4507
|
+
const deposit = await this.deposit({
|
|
4508
|
+
amount: swap.received,
|
|
4509
|
+
asset: options.to.symbol,
|
|
4510
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
4511
|
+
});
|
|
4512
|
+
return { swap, deposit };
|
|
4513
|
+
}
|
|
4514
|
+
/**
|
|
4515
|
+
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
4516
|
+
* to hold, delivered to their own wallet.
|
|
4517
|
+
*
|
|
4518
|
+
* The mirror of `swapAndDeposit()`, with one structural difference that
|
|
4519
|
+
* drives the whole implementation: a deposit swap starts from funds already
|
|
4520
|
+
* sitting in the wallet, but a withdrawal has to wait for them. The agent's
|
|
4521
|
+
* provider acknowledges a withdrawal and *then* queues the on-chain transfer
|
|
4522
|
+
* to the EOA, so `withdraw()` resolving means "accepted", not "arrived".
|
|
4523
|
+
* Quoting before the tokens land would size the swap against a balance that
|
|
4524
|
+
* is not there yet.
|
|
4525
|
+
*
|
|
4526
|
+
* The swap is therefore sized from the MEASURED arrival, exactly as the
|
|
4527
|
+
* deposit path sizes its deposit from the measured swap output. On a full
|
|
4528
|
+
* withdrawal there is no other number available — "MAX" has no figure until
|
|
4529
|
+
* the agent picks one.
|
|
4530
|
+
*
|
|
4531
|
+
* **Failure here is not symmetrical with the deposit path.** A failed
|
|
4532
|
+
* deposit-swap leaves the user holding what they started with. A failed
|
|
4533
|
+
* withdrawal-swap leaves them holding the AGENT'S asset in their own wallet:
|
|
4534
|
+
* the money is out, safe, and in the wrong denomination. Both
|
|
4535
|
+
* `WITHDRAW_ARRIVAL_TIMEOUT` and `WITHDRAW_SWAP_FAILED` carry `withdrawn` for
|
|
4536
|
+
* that reason — the UI has to tell the user where their money actually is,
|
|
4537
|
+
* and must never present either as a lost withdrawal.
|
|
4538
|
+
*/
|
|
4539
|
+
async withdrawAndSwap(options) {
|
|
4540
|
+
const state = this.requireState();
|
|
4541
|
+
const activeChainId = this.requireChainId();
|
|
4542
|
+
if (options.from.chainId !== activeChainId) {
|
|
4543
|
+
throw new OwneyError(
|
|
4544
|
+
"CHAIN_MISMATCH",
|
|
4545
|
+
`Cannot withdraw from chain ${options.from.chainId} while the active chain is ${activeChainId}. Activate on that chain first.`,
|
|
4546
|
+
{ requested: options.from.chainId, active: activeChainId }
|
|
4547
|
+
);
|
|
4548
|
+
}
|
|
4549
|
+
const asset = SupportedAssets.find(
|
|
4550
|
+
(a) => a.chainId === options.from.chainId && a.symbol === options.from.symbol.toUpperCase()
|
|
4551
|
+
);
|
|
4552
|
+
if (!asset) {
|
|
4553
|
+
throw new OwneyError(
|
|
4554
|
+
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
4555
|
+
`${options.from.symbol} on chain ${options.from.chainId} is not an asset Owney holds`,
|
|
4556
|
+
{ ...options.from }
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
const srcChain = VIEM_CHAIN2[options.from.chainId];
|
|
4560
|
+
const srcPublic = (0, import_viem8.createPublicClient)({
|
|
4561
|
+
chain: srcChain,
|
|
4562
|
+
transport: swapReadTransport(options.from.chainId, this.zyfaiRpcUrls)
|
|
4563
|
+
});
|
|
4564
|
+
const readWalletBalance = () => srcPublic.readContract({
|
|
4565
|
+
address: asset.address,
|
|
4566
|
+
abi: import_viem8.erc20Abi,
|
|
4567
|
+
functionName: "balanceOf",
|
|
4568
|
+
args: [state.walletAddress]
|
|
4569
|
+
});
|
|
4570
|
+
const baseline = await readWalletBalance();
|
|
4571
|
+
debugLog("owney-sdk", "withdrawAndSwap: baseline", {
|
|
4572
|
+
asset: `${asset.symbol}@${asset.chainId}`,
|
|
4573
|
+
baseline: baseline.toString()
|
|
4574
|
+
});
|
|
4575
|
+
options.onSwapProgress?.("withdrawing");
|
|
4576
|
+
const withdraw = await this.withdraw({
|
|
4577
|
+
asset: options.from.symbol,
|
|
4578
|
+
...options.amount === void 0 ? {} : { amount: (0, import_viem8.parseUnits)(options.amount, asset.decimals).toString() },
|
|
4579
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
4580
|
+
});
|
|
4581
|
+
const arrived = await awaitWithdrawalArrival({
|
|
4582
|
+
readBalance: readWalletBalance,
|
|
4583
|
+
baseline,
|
|
4584
|
+
...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs }
|
|
4585
|
+
});
|
|
4586
|
+
const withdrawn = arrived.toString();
|
|
4587
|
+
options.onSwapProgress?.("withdrawn");
|
|
4588
|
+
try {
|
|
4589
|
+
const quote = await this.swapApi().quote({
|
|
4590
|
+
from: { ...options.from, amount: withdrawn },
|
|
4591
|
+
to: options.to,
|
|
4592
|
+
direction: "withdraw",
|
|
4593
|
+
walletAddress: state.walletAddress
|
|
4594
|
+
});
|
|
4595
|
+
await this.ensureSwapChain(quote.src.chainId);
|
|
4596
|
+
const swap = await executeSwap(this.buildSwapDeps(quote), {
|
|
4597
|
+
quote,
|
|
4598
|
+
walletAddress: state.walletAddress,
|
|
4599
|
+
direction: "withdraw",
|
|
4600
|
+
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4601
|
+
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4602
|
+
});
|
|
4603
|
+
return { withdraw, withdrawn, swap };
|
|
4604
|
+
} catch (error) {
|
|
4605
|
+
throw new OwneyError(
|
|
4606
|
+
"WITHDRAW_SWAP_FAILED",
|
|
4607
|
+
`Withdrew ${withdrawn} ${asset.symbol} to your wallet, but the swap to ${options.to.symbol} did not complete. The funds are in your wallet as ${asset.symbol}.`,
|
|
4608
|
+
{
|
|
4609
|
+
withdrawn,
|
|
4610
|
+
asset: asset.symbol,
|
|
4611
|
+
chainId: asset.chainId,
|
|
4612
|
+
intendedSymbol: options.to.symbol,
|
|
4613
|
+
intendedChainId: options.to.chainId,
|
|
4614
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
4615
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
4616
|
+
}
|
|
4617
|
+
);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
5608
4620
|
/**
|
|
5609
4621
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
5610
4622
|
* Validates that the asset is supported by the target agent(s) on the active chain.
|
|
@@ -5674,10 +4686,6 @@ var OwneySDK = class {
|
|
|
5674
4686
|
}
|
|
5675
4687
|
const requested = BigInt(amount);
|
|
5676
4688
|
const aggregated = await this.getBalances();
|
|
5677
|
-
const unavailableAgents = eligibleAgents.filter(
|
|
5678
|
-
(agent) => !(agent.id in aggregated.agentBalances)
|
|
5679
|
-
);
|
|
5680
|
-
const unavailableAgentIds = unavailableAgents.map((agent) => agent.id);
|
|
5681
4689
|
const balances = projectAgentBalancesForAsset(
|
|
5682
4690
|
eligibleAgents,
|
|
5683
4691
|
aggregated.agentBalances,
|
|
@@ -5686,18 +4694,7 @@ var OwneySDK = class {
|
|
|
5686
4694
|
assetInfo.decimals
|
|
5687
4695
|
);
|
|
5688
4696
|
const totalAvailable = balances.reduce((sum, x) => sum + x.balance, 0n);
|
|
5689
|
-
if (totalAvailable
|
|
5690
|
-
throw new OwneyError(
|
|
5691
|
-
"WITHDRAW_BALANCE_UNAVAILABLE",
|
|
5692
|
-
`Cannot safely plan the withdrawal because balance data is unavailable for: ${unavailableAgentIds.join(", ")}.`,
|
|
5693
|
-
{
|
|
5694
|
-
asset,
|
|
5695
|
-
unavailableAgents: unavailableAgentIds,
|
|
5696
|
-
agentErrors: aggregated.agentErrors
|
|
5697
|
-
}
|
|
5698
|
-
);
|
|
5699
|
-
}
|
|
5700
|
-
if (totalAvailable < requested && unavailableAgents.length === 0) {
|
|
4697
|
+
if (totalAvailable < requested) {
|
|
5701
4698
|
throw new OwneyError(
|
|
5702
4699
|
"WITHDRAW_INSUFFICIENT_BALANCE",
|
|
5703
4700
|
`Requested withdrawal "${amount}" exceeds available balance "${totalAvailable.toString()}" for asset "${asset}".`,
|
|
@@ -5708,7 +4705,6 @@ var OwneySDK = class {
|
|
|
5708
4705
|
}
|
|
5709
4706
|
);
|
|
5710
4707
|
}
|
|
5711
|
-
const plannedTarget = totalAvailable < requested ? totalAvailable : requested;
|
|
5712
4708
|
const disabledBalances = balances.filter(
|
|
5713
4709
|
(b) => this.isAgentDisabled(b.agent.id)
|
|
5714
4710
|
);
|
|
@@ -5717,7 +4713,7 @@ var OwneySDK = class {
|
|
|
5717
4713
|
);
|
|
5718
4714
|
const { plans: disabledPlans, remaining: afterDrain } = planDisabledDrain(
|
|
5719
4715
|
disabledBalances,
|
|
5720
|
-
|
|
4716
|
+
requested
|
|
5721
4717
|
);
|
|
5722
4718
|
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
5723
4719
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
@@ -5727,9 +4723,7 @@ var OwneySDK = class {
|
|
|
5727
4723
|
}));
|
|
5728
4724
|
const plans = [...disabledPlans, ...enabledPlans];
|
|
5729
4725
|
const results = {};
|
|
5730
|
-
const agentErrors = {
|
|
5731
|
-
...aggregated.agentErrors ?? {}
|
|
5732
|
-
};
|
|
4726
|
+
const agentErrors = {};
|
|
5733
4727
|
for (let i = 0; i < plans.length; i++) {
|
|
5734
4728
|
const p = plans[i];
|
|
5735
4729
|
if (p.planned === 0n) continue;
|
|
@@ -5776,8 +4770,7 @@ var OwneySDK = class {
|
|
|
5776
4770
|
requested: amount,
|
|
5777
4771
|
withdrawn: withdrawn.toString(),
|
|
5778
4772
|
partialResults: results,
|
|
5779
|
-
agentErrors
|
|
5780
|
-
...unavailableAgentIds.length > 0 ? { unavailableAgents: unavailableAgentIds } : {}
|
|
4773
|
+
agentErrors
|
|
5781
4774
|
}
|
|
5782
4775
|
);
|
|
5783
4776
|
}
|
|
@@ -5794,25 +4787,24 @@ var OwneySDK = class {
|
|
|
5794
4787
|
const chainId = this.requireChainId();
|
|
5795
4788
|
if (agentId) {
|
|
5796
4789
|
const agent = this.getAgent(agentId);
|
|
5797
|
-
const result = await this.readAgent(
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
4790
|
+
const result = await this.readAgent(
|
|
4791
|
+
agent,
|
|
4792
|
+
"balances",
|
|
4793
|
+
() => agent.getBalances(state, chainId)
|
|
4794
|
+
);
|
|
4795
|
+
return result;
|
|
5802
4796
|
}
|
|
5803
4797
|
let totalBalance = 0;
|
|
5804
4798
|
const results = {};
|
|
5805
4799
|
const entries = [...this.getActiveAgents().entries()];
|
|
5806
4800
|
const balanceResults = await Promise.allSettled(
|
|
5807
4801
|
entries.map(async ([id, agent]) => {
|
|
5808
|
-
const b = await this.readAgent(
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
}
|
|
5815
|
-
];
|
|
4802
|
+
const b = await this.readAgent(
|
|
4803
|
+
agent,
|
|
4804
|
+
"balances",
|
|
4805
|
+
() => agent.getBalances(state, chainId)
|
|
4806
|
+
);
|
|
4807
|
+
return [id, b];
|
|
5816
4808
|
})
|
|
5817
4809
|
);
|
|
5818
4810
|
let successCount = 0;
|
|
@@ -5832,9 +4824,9 @@ var OwneySDK = class {
|
|
|
5832
4824
|
const reason = settledResult.reason;
|
|
5833
4825
|
agentFailures.push(reason);
|
|
5834
4826
|
const retryDelay = rateLimitDelay(reason);
|
|
5835
|
-
if (retryDelay !== void 0)
|
|
4827
|
+
if (retryDelay !== void 0)
|
|
4828
|
+
agentRetryAt[agentId2] = Date.now() + retryDelay;
|
|
5836
4829
|
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
5837
|
-
console.error(`[owney-sdk] Balance fetch failed for agent "${agentId2}":`, reason);
|
|
5838
4830
|
}
|
|
5839
4831
|
if (successCount === 0) {
|
|
5840
4832
|
throw new OwneyError(
|
|
@@ -5860,14 +4852,22 @@ var OwneySDK = class {
|
|
|
5860
4852
|
const chainId = this.requireChainId();
|
|
5861
4853
|
if (agentId) {
|
|
5862
4854
|
const agent = this.getAgent(agentId);
|
|
5863
|
-
return this.readAgent(
|
|
4855
|
+
return this.readAgent(
|
|
4856
|
+
agent,
|
|
4857
|
+
"earnings",
|
|
4858
|
+
() => agent.getEarnings(state, chainId)
|
|
4859
|
+
);
|
|
5864
4860
|
}
|
|
5865
4861
|
let totalEarnings = 0;
|
|
5866
4862
|
const results = {};
|
|
5867
4863
|
const entries = [...this.getActiveAgents().entries()];
|
|
5868
4864
|
const earningsResults = await Promise.all(
|
|
5869
4865
|
entries.map(async ([id, agent]) => {
|
|
5870
|
-
const e = await this.readAgent(
|
|
4866
|
+
const e = await this.readAgent(
|
|
4867
|
+
agent,
|
|
4868
|
+
"earnings",
|
|
4869
|
+
() => agent.getEarnings(state, chainId)
|
|
4870
|
+
);
|
|
5871
4871
|
return [id, e];
|
|
5872
4872
|
})
|
|
5873
4873
|
);
|
|
@@ -6012,11 +5012,12 @@ var OwneySDK = class {
|
|
|
6012
5012
|
),
|
|
6013
5013
|
Promise.all(
|
|
6014
5014
|
entries.map(async ([id, agent]) => {
|
|
6015
|
-
const b = await this.readAgent(
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
5015
|
+
const b = await this.readAgent(
|
|
5016
|
+
agent,
|
|
5017
|
+
"balances",
|
|
5018
|
+
() => agent.getBalances(state, chainId)
|
|
5019
|
+
);
|
|
5020
|
+
return [id, balanceForApyScope(b, chainId, tokenSymbol)];
|
|
6020
5021
|
})
|
|
6021
5022
|
)
|
|
6022
5023
|
]);
|
|
@@ -6075,7 +5076,12 @@ var OwneySDK = class {
|
|
|
6075
5076
|
const { agentId, filters } = options ?? {};
|
|
6076
5077
|
if (agentId) {
|
|
6077
5078
|
const agent = this.getAgent(agentId);
|
|
6078
|
-
return this.readAgent(
|
|
5079
|
+
return this.readAgent(
|
|
5080
|
+
agent,
|
|
5081
|
+
"history",
|
|
5082
|
+
() => agent.getHistory(state, chainId, filters),
|
|
5083
|
+
filters
|
|
5084
|
+
);
|
|
6079
5085
|
}
|
|
6080
5086
|
const activeAgents = [...this.getActiveAgents().values()];
|
|
6081
5087
|
const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
|
|
@@ -6132,13 +5138,21 @@ var OwneySDK = class {
|
|
|
6132
5138
|
const chainId = this.requireChainId();
|
|
6133
5139
|
if (agentId) {
|
|
6134
5140
|
const agent = this.getAgent(agentId);
|
|
6135
|
-
return this.readAgent(
|
|
5141
|
+
return this.readAgent(
|
|
5142
|
+
agent,
|
|
5143
|
+
"profile",
|
|
5144
|
+
() => agent.getUserProfile(state, chainId)
|
|
5145
|
+
);
|
|
6136
5146
|
}
|
|
6137
5147
|
const results = {};
|
|
6138
5148
|
const entries = [...this.getActiveAgents().entries()];
|
|
6139
5149
|
const profileResults = await Promise.all(
|
|
6140
5150
|
entries.map(async ([id, agent]) => {
|
|
6141
|
-
const p = await this.readAgent(
|
|
5151
|
+
const p = await this.readAgent(
|
|
5152
|
+
agent,
|
|
5153
|
+
"profile",
|
|
5154
|
+
() => agent.getUserProfile(state, chainId)
|
|
5155
|
+
);
|
|
6142
5156
|
return [id, p];
|
|
6143
5157
|
})
|
|
6144
5158
|
);
|
|
@@ -6177,47 +5191,43 @@ var OwneySDK = class {
|
|
|
6177
5191
|
return pending;
|
|
6178
5192
|
}
|
|
6179
5193
|
/**
|
|
6180
|
-
*
|
|
6181
|
-
*
|
|
6182
|
-
*
|
|
6183
|
-
*
|
|
6184
|
-
*
|
|
6185
|
-
*
|
|
6186
|
-
* @param requiredAmount Raw base-unit amount the pending deposit must cover.
|
|
6187
|
-
* @param expectedChainId Chain captured by the deposit that requested approval.
|
|
5194
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
5195
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
5196
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
5197
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
5198
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
6188
5199
|
* @returns the approval transaction hash.
|
|
6189
5200
|
*/
|
|
6190
|
-
async approvePermit2(asset = "WETH"
|
|
5201
|
+
async approvePermit2(asset = "WETH") {
|
|
5202
|
+
void asset;
|
|
6191
5203
|
const state = this.requireState();
|
|
6192
|
-
const chainId =
|
|
6193
|
-
this.
|
|
6194
|
-
const token =
|
|
5204
|
+
const chainId = this.requireChainId();
|
|
5205
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
5206
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
6195
5207
|
if (!token) {
|
|
6196
5208
|
throw new OwneyError(
|
|
6197
5209
|
"CHAIN_UNSUPPORTED",
|
|
6198
|
-
`No sponsored
|
|
5210
|
+
`No sponsored WETH on chain ${chainId}`
|
|
6199
5211
|
);
|
|
6200
5212
|
}
|
|
6201
5213
|
const provider = this.requireConnectedProvider();
|
|
6202
|
-
const
|
|
6203
|
-
chain: VIEM_CHAIN2[chainId],
|
|
6204
|
-
transport: (0, import_viem11.custom)(provider)
|
|
6205
|
-
});
|
|
6206
|
-
const approvalAmount = permit2ApprovalAmount(requiredAmount);
|
|
6207
|
-
const wallet = (0, import_viem11.createWalletClient)({
|
|
5214
|
+
const wallet = (0, import_viem8.createWalletClient)({
|
|
6208
5215
|
account: state.walletAddress,
|
|
6209
5216
|
chain: VIEM_CHAIN2[chainId],
|
|
6210
|
-
transport: (0,
|
|
5217
|
+
transport: (0, import_viem8.custom)(provider)
|
|
6211
5218
|
});
|
|
6212
|
-
await ensureWalletOnChain(publicClient, wallet, chainId);
|
|
6213
5219
|
const hash = await wallet.writeContract({
|
|
6214
5220
|
address: token,
|
|
6215
5221
|
abi: ERC20_ALLOWANCE_ABI,
|
|
6216
5222
|
functionName: "approve",
|
|
6217
|
-
args: [PERMIT2_ADDRESS,
|
|
5223
|
+
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
6218
5224
|
account: state.walletAddress,
|
|
6219
5225
|
chain: VIEM_CHAIN2[chainId]
|
|
6220
5226
|
});
|
|
5227
|
+
const publicClient = (0, import_viem8.createPublicClient)({
|
|
5228
|
+
chain: VIEM_CHAIN2[chainId],
|
|
5229
|
+
transport: (0, import_viem8.custom)(provider)
|
|
5230
|
+
});
|
|
6221
5231
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
6222
5232
|
hash,
|
|
6223
5233
|
confirmations: 1
|
|
@@ -6225,42 +5235,7 @@ var OwneySDK = class {
|
|
|
6225
5235
|
if (receipt.status !== "success") {
|
|
6226
5236
|
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
6227
5237
|
}
|
|
6228
|
-
|
|
6229
|
-
let verificationError;
|
|
6230
|
-
for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS; attempt += 1) {
|
|
6231
|
-
try {
|
|
6232
|
-
observedAllowance = await readPermit2Allowance(
|
|
6233
|
-
publicClient,
|
|
6234
|
-
token,
|
|
6235
|
-
state.walletAddress,
|
|
6236
|
-
attempt === 0 ? receipt.blockNumber : void 0
|
|
6237
|
-
);
|
|
6238
|
-
verificationError = void 0;
|
|
6239
|
-
if (observedAllowance >= requiredAmount) return hash;
|
|
6240
|
-
} catch (error) {
|
|
6241
|
-
verificationError = error;
|
|
6242
|
-
}
|
|
6243
|
-
if (attempt + 1 < PERMIT2_ALLOWANCE_VERIFY_ATTEMPTS) {
|
|
6244
|
-
await new Promise(
|
|
6245
|
-
(resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_VERIFY_DELAY_MS)
|
|
6246
|
-
);
|
|
6247
|
-
}
|
|
6248
|
-
}
|
|
6249
|
-
throw new OwneyError(
|
|
6250
|
-
"PERMIT2_APPROVAL_REQUIRED",
|
|
6251
|
-
"Permit2 approval was confirmed, but the required token allowance was not observable.",
|
|
6252
|
-
{
|
|
6253
|
-
approvalConfirmed: true,
|
|
6254
|
-
approvalTxHash: hash,
|
|
6255
|
-
owner: state.walletAddress,
|
|
6256
|
-
token,
|
|
6257
|
-
spender: PERMIT2_ADDRESS,
|
|
6258
|
-
chainId,
|
|
6259
|
-
requiredAmount: requiredAmount.toString(),
|
|
6260
|
-
observedAllowance: observedAllowance.toString(),
|
|
6261
|
-
...verificationError instanceof Error ? { verificationError: verificationError.message } : {}
|
|
6262
|
-
}
|
|
6263
|
-
);
|
|
5238
|
+
return hash;
|
|
6264
5239
|
}
|
|
6265
5240
|
// --- Discovery (no wallet required) ---
|
|
6266
5241
|
/**
|
|
@@ -6285,15 +5260,23 @@ var OwneySDK = class {
|
|
|
6285
5260
|
const agentOptions = { tokenSymbol, chainId };
|
|
6286
5261
|
if (agentId) {
|
|
6287
5262
|
const agent = this.getAgent(agentId);
|
|
6288
|
-
return this.readAgent(
|
|
5263
|
+
return this.readAgent(
|
|
5264
|
+
agent,
|
|
5265
|
+
"agentApy",
|
|
5266
|
+
() => agent.getAgentApy(days, agentOptions),
|
|
5267
|
+
{ days, ...agentOptions }
|
|
5268
|
+
);
|
|
6289
5269
|
}
|
|
6290
5270
|
const results = {};
|
|
6291
|
-
const agentEntries = [...this.agents.entries()]
|
|
6292
|
-
([id]) => !this.isAgentDisabled(id)
|
|
6293
|
-
);
|
|
5271
|
+
const agentEntries = [...this.agents.entries()];
|
|
6294
5272
|
const apyResults = await Promise.all(
|
|
6295
5273
|
agentEntries.map(async ([id, agent]) => {
|
|
6296
|
-
const apy = await this.readAgent(
|
|
5274
|
+
const apy = await this.readAgent(
|
|
5275
|
+
agent,
|
|
5276
|
+
"agentApy",
|
|
5277
|
+
() => agent.getAgentApy(days, agentOptions),
|
|
5278
|
+
{ days, ...agentOptions }
|
|
5279
|
+
);
|
|
6297
5280
|
return [id, apy];
|
|
6298
5281
|
})
|
|
6299
5282
|
);
|
|
@@ -6320,7 +5303,11 @@ var OwneySDK = class {
|
|
|
6320
5303
|
const entries = [...activeAgents.entries()];
|
|
6321
5304
|
const balanceResults = await Promise.allSettled(
|
|
6322
5305
|
entries.map(async ([id, agent]) => {
|
|
6323
|
-
const b = await this.readAgent(
|
|
5306
|
+
const b = await this.readAgent(
|
|
5307
|
+
agent,
|
|
5308
|
+
"balances",
|
|
5309
|
+
() => agent.getBalances(state, chainId)
|
|
5310
|
+
);
|
|
6324
5311
|
return [id, b.positions ?? []];
|
|
6325
5312
|
})
|
|
6326
5313
|
);
|
|
@@ -6365,13 +5352,13 @@ var OwneySDK = class {
|
|
|
6365
5352
|
};
|
|
6366
5353
|
|
|
6367
5354
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
6368
|
-
var
|
|
6369
|
-
var
|
|
5355
|
+
var import_viem9 = require("viem");
|
|
5356
|
+
var import_siwe = require("siwe");
|
|
6370
5357
|
var import_sdk2 = require("@zyfai/sdk");
|
|
6371
5358
|
|
|
6372
5359
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
6373
|
-
var
|
|
6374
|
-
var
|
|
5360
|
+
var KEY_PREFIX3 = "owney.siwx.session";
|
|
5361
|
+
var storage3 = () => {
|
|
6375
5362
|
if (typeof window === "undefined") return null;
|
|
6376
5363
|
try {
|
|
6377
5364
|
return window.localStorage;
|
|
@@ -6379,8 +5366,8 @@ var storage4 = () => {
|
|
|
6379
5366
|
return null;
|
|
6380
5367
|
}
|
|
6381
5368
|
};
|
|
6382
|
-
var
|
|
6383
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
5369
|
+
var buildKey2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
|
|
5370
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
|
|
6384
5371
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
6385
5372
|
var readLegacySiwxSession = (store, address) => {
|
|
6386
5373
|
if (!store) return null;
|
|
@@ -6411,17 +5398,17 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
6411
5398
|
};
|
|
6412
5399
|
var readSiwxSession = (address, chainId) => {
|
|
6413
5400
|
if (typeof window === "undefined") return null;
|
|
6414
|
-
const key2 =
|
|
6415
|
-
const store =
|
|
6416
|
-
let
|
|
5401
|
+
const key2 = buildKey2(address);
|
|
5402
|
+
const store = storage3();
|
|
5403
|
+
let raw = null;
|
|
6417
5404
|
try {
|
|
6418
|
-
|
|
5405
|
+
raw = store?.getItem(key2) ?? null;
|
|
6419
5406
|
} catch {
|
|
6420
|
-
|
|
5407
|
+
raw = null;
|
|
6421
5408
|
}
|
|
6422
|
-
if (
|
|
5409
|
+
if (raw) {
|
|
6423
5410
|
try {
|
|
6424
|
-
return JSON.parse(
|
|
5411
|
+
return JSON.parse(raw);
|
|
6425
5412
|
} catch {
|
|
6426
5413
|
memorySiwxSessions.delete(key2);
|
|
6427
5414
|
try {
|
|
@@ -6440,18 +5427,18 @@ var readSiwxSession = (address, chainId) => {
|
|
|
6440
5427
|
};
|
|
6441
5428
|
var writeSiwxSession = (address, _chainId, session) => {
|
|
6442
5429
|
if (typeof window === "undefined") return;
|
|
6443
|
-
const key2 =
|
|
5430
|
+
const key2 = buildKey2(address);
|
|
6444
5431
|
memorySiwxSessions.set(key2, session);
|
|
6445
|
-
const store =
|
|
5432
|
+
const store = storage3();
|
|
6446
5433
|
try {
|
|
6447
5434
|
store?.setItem(key2, JSON.stringify(session));
|
|
6448
5435
|
} catch {
|
|
6449
5436
|
}
|
|
6450
5437
|
};
|
|
6451
5438
|
var clearSiwxSession = (address, _chainId) => {
|
|
6452
|
-
const key2 =
|
|
5439
|
+
const key2 = buildKey2(address);
|
|
6453
5440
|
memorySiwxSessions.delete(key2);
|
|
6454
|
-
const store =
|
|
5441
|
+
const store = storage3();
|
|
6455
5442
|
try {
|
|
6456
5443
|
store?.removeItem(key2);
|
|
6457
5444
|
} catch {
|
|
@@ -6491,8 +5478,8 @@ function buildSIWXConfig(deps) {
|
|
|
6491
5478
|
statement: STATEMENT,
|
|
6492
5479
|
issuedAt,
|
|
6493
5480
|
toString() {
|
|
6494
|
-
return new
|
|
6495
|
-
address: (0,
|
|
5481
|
+
return new import_siwe.SiweMessage({
|
|
5482
|
+
address: (0, import_viem9.getAddress)(accountAddress),
|
|
6496
5483
|
chainId: numericChainId(chainId),
|
|
6497
5484
|
domain,
|
|
6498
5485
|
uri,
|
|
@@ -6534,7 +5521,7 @@ function buildSIWXConfig(deps) {
|
|
|
6534
5521
|
const persistSession = async (session) => {
|
|
6535
5522
|
const address = session.data.accountAddress;
|
|
6536
5523
|
const id = numericChainId(session.data.chainId);
|
|
6537
|
-
const message = new
|
|
5524
|
+
const message = new import_siwe.SiweMessage(session.message);
|
|
6538
5525
|
const login = await post("/auth/login", {
|
|
6539
5526
|
message,
|
|
6540
5527
|
signature: session.signature,
|
|
@@ -6570,9 +5557,9 @@ function buildSIWXConfig(deps) {
|
|
|
6570
5557
|
}
|
|
6571
5558
|
function createOwneySIWX(config) {
|
|
6572
5559
|
const zyfai = new import_sdk2.ZyfaiSDK({ apiKey: config.apiKey });
|
|
6573
|
-
const
|
|
5560
|
+
const http4 = zyfai.httpClient;
|
|
6574
5561
|
return buildSIWXConfig({
|
|
6575
|
-
post: (url, data) =>
|
|
5562
|
+
post: (url, data) => http4.post(url, data),
|
|
6576
5563
|
referralSource: config.referralSource
|
|
6577
5564
|
});
|
|
6578
5565
|
}
|
|
@@ -6584,7 +5571,7 @@ function createOwneySIWX(config) {
|
|
|
6584
5571
|
NotConnectedError,
|
|
6585
5572
|
OwneyError,
|
|
6586
5573
|
OwneySDK,
|
|
6587
|
-
YieldseekerAgent,
|
|
6588
5574
|
createOwneySIWX,
|
|
5575
|
+
listPendingSwaps,
|
|
6589
5576
|
setOwneyDebug
|
|
6590
5577
|
});
|