@owney/sdk 0.6.6-beta.1 → 0.6.6-beta.3
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/dist/index.cjs +699 -755
- package/dist/index.d.cts +144 -82
- package/dist/index.d.ts +144 -82
- package/dist/index.js +698 -265
- package/package.json +5 -4
- package/dist/chunk-GMNQBWUB.js +0 -55
- package/dist/surfliquid.agent-EPFISJPR.js +0 -390
package/dist/index.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AgentChainIncompatibleError,
|
|
3
|
-
AgentNotFoundError,
|
|
4
|
-
NotConnectedError,
|
|
5
|
-
OwneyError
|
|
6
|
-
} from "./chunk-GMNQBWUB.js";
|
|
7
|
-
|
|
8
1
|
// src/agents/zyfai/zyfai.agent.ts
|
|
9
2
|
import { ZyfaiSDK } from "@zyfai/sdk";
|
|
10
3
|
import { createPublicClient, http } from "viem";
|
|
11
|
-
import { base, arbitrum } from "viem/chains";
|
|
4
|
+
import { base, arbitrum, mainnet } from "viem/chains";
|
|
12
5
|
|
|
13
6
|
// src/types/config.ts
|
|
14
7
|
var P_SUPPORTED_CHAINS = {
|
|
15
|
-
chainIds: [8453, 42161],
|
|
16
|
-
chains: ["BASE", "ARBITRUM"]
|
|
8
|
+
chainIds: [8453, 42161, 1],
|
|
9
|
+
chains: ["BASE", "ARBITRUM", "ETHEREUM"]
|
|
17
10
|
};
|
|
18
|
-
var SUPPORTED_TOKENS = ["USDC", "USDT"];
|
|
11
|
+
var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
|
|
19
12
|
var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
|
|
20
13
|
var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
|
|
21
14
|
var SupportedAssets = [
|
|
@@ -36,9 +29,82 @@ var SupportedAssets = [
|
|
|
36
29
|
symbol: "USDT",
|
|
37
30
|
chainId: 42161,
|
|
38
31
|
decimals: 6
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
address: `0x4200000000000000000000000000000000000006`,
|
|
35
|
+
symbol: "WETH",
|
|
36
|
+
chainId: 8453,
|
|
37
|
+
decimals: 18
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`,
|
|
41
|
+
symbol: "WETH",
|
|
42
|
+
chainId: 42161,
|
|
43
|
+
decimals: 18
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
address: `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`,
|
|
47
|
+
symbol: "USDC",
|
|
48
|
+
chainId: 1,
|
|
49
|
+
decimals: 6
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
address: `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`,
|
|
53
|
+
symbol: "WETH",
|
|
54
|
+
chainId: 1,
|
|
55
|
+
decimals: 18
|
|
39
56
|
}
|
|
40
57
|
];
|
|
41
58
|
|
|
59
|
+
// src/errors.ts
|
|
60
|
+
var OwneyError = class extends Error {
|
|
61
|
+
code;
|
|
62
|
+
details;
|
|
63
|
+
agentId;
|
|
64
|
+
constructor(code, message, details, agentId) {
|
|
65
|
+
const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
|
|
66
|
+
super(`${prefix} ${message}`);
|
|
67
|
+
this.name = "OwneyError";
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.details = details;
|
|
70
|
+
this.agentId = agentId;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var AgentNotFoundError = class extends OwneyError {
|
|
74
|
+
constructor(agentId, available) {
|
|
75
|
+
super(
|
|
76
|
+
"AGENT_NOT_FOUND",
|
|
77
|
+
`Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
|
|
78
|
+
{ agentId, available },
|
|
79
|
+
agentId
|
|
80
|
+
);
|
|
81
|
+
this.name = "AgentNotFoundError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var NotConnectedError = class extends OwneyError {
|
|
85
|
+
constructor() {
|
|
86
|
+
super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
|
|
87
|
+
this.name = "NotConnectedError";
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var AgentChainIncompatibleError = class extends OwneyError {
|
|
91
|
+
incompatibleAgents;
|
|
92
|
+
connectedChainId;
|
|
93
|
+
constructor(incompatibleAgents, connectedChainId) {
|
|
94
|
+
const details = incompatibleAgents.map(
|
|
95
|
+
({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
|
|
96
|
+
).join("; ");
|
|
97
|
+
super(
|
|
98
|
+
"AGENT_CHAIN_INCOMPATIBLE",
|
|
99
|
+
`Chain ${connectedChainId} is not supported by the following agents: ${details}`,
|
|
100
|
+
{ incompatibleAgents, connectedChainId }
|
|
101
|
+
);
|
|
102
|
+
this.name = "AgentChainIncompatibleError";
|
|
103
|
+
this.incompatibleAgents = incompatibleAgents;
|
|
104
|
+
this.connectedChainId = connectedChainId;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
42
108
|
// src/lib/utils.ts
|
|
43
109
|
var isValidChainId = (chainId) => {
|
|
44
110
|
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
|
@@ -53,8 +119,7 @@ var isValidChainId = (chainId) => {
|
|
|
53
119
|
function hexToDecimal(hex, decimals = 6) {
|
|
54
120
|
const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
|
|
55
121
|
const parsed = BigInt(normalized);
|
|
56
|
-
if (decimals <= 0)
|
|
57
|
-
return Number(parsed);
|
|
122
|
+
if (decimals <= 0) return Number(parsed);
|
|
58
123
|
const divisor = BigInt(10 ** decimals);
|
|
59
124
|
const whole = parsed / divisor;
|
|
60
125
|
const remainder = parsed % divisor;
|
|
@@ -67,6 +132,20 @@ var DayFilterMapping = {
|
|
|
67
132
|
};
|
|
68
133
|
|
|
69
134
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
135
|
+
var DECIMALS_BY_SYMBOL = {
|
|
136
|
+
usdc: 6,
|
|
137
|
+
usdt: 6,
|
|
138
|
+
weth: 18,
|
|
139
|
+
eth: 18,
|
|
140
|
+
cbeth: 18,
|
|
141
|
+
wbtc: 8,
|
|
142
|
+
cbbtc: 8
|
|
143
|
+
};
|
|
144
|
+
function tokenDecimals(symbol, explicit) {
|
|
145
|
+
if (typeof explicit === "number" && Number.isFinite(explicit))
|
|
146
|
+
return explicit;
|
|
147
|
+
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
148
|
+
}
|
|
70
149
|
function mapDeposit(raw) {
|
|
71
150
|
return {
|
|
72
151
|
txHash: raw.txHash,
|
|
@@ -83,7 +162,8 @@ function mapWithdraw(raw) {
|
|
|
83
162
|
}
|
|
84
163
|
var CHAIN_ID_TO_NAME = {
|
|
85
164
|
8453: "BASE",
|
|
86
|
-
42161: "ARBITRUM"
|
|
165
|
+
42161: "ARBITRUM",
|
|
166
|
+
1: "ETHEREUM"
|
|
87
167
|
};
|
|
88
168
|
var NAME_TO_CHAIN_ID = Object.entries(
|
|
89
169
|
CHAIN_ID_TO_NAME
|
|
@@ -93,54 +173,52 @@ var NAME_TO_CHAIN_ID = Object.entries(
|
|
|
93
173
|
}, {});
|
|
94
174
|
function resolveChainId(chain) {
|
|
95
175
|
const asNum = Number(chain);
|
|
96
|
-
if (Number.isFinite(asNum) && asNum > 0)
|
|
97
|
-
return asNum;
|
|
176
|
+
if (Number.isFinite(asNum) && asNum > 0) return asNum;
|
|
98
177
|
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
99
178
|
}
|
|
100
179
|
function mapBalances(raw, _chainId, smartWallet) {
|
|
101
180
|
const portfolio = raw.portfolio;
|
|
102
181
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
103
|
-
const rawPositions = portfolio.positions ?? [];
|
|
104
|
-
const detailByKey = /* @__PURE__ */ new Map();
|
|
105
|
-
for (const p of rawPositions) {
|
|
106
|
-
const cid = resolveChainId(p.chain ?? "");
|
|
107
|
-
const symbol = (p.token_symbol ?? "").toUpperCase();
|
|
108
|
-
if (Number.isFinite(cid) && symbol)
|
|
109
|
-
detailByKey.set(`${cid}:${symbol}`, p);
|
|
110
|
-
}
|
|
111
182
|
let totalBalance = 0;
|
|
112
183
|
const tokens = [];
|
|
113
|
-
const positions = [];
|
|
114
184
|
for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
|
|
115
185
|
const positionChainId = Number(chainIdKey);
|
|
116
|
-
const chainName = CHAIN_ID_TO_NAME[positionChainId] ?? "BASE";
|
|
117
186
|
for (const [symbol, { balance, decimals }] of Object.entries(
|
|
118
187
|
tokensBySymbol ?? {}
|
|
119
188
|
)) {
|
|
120
189
|
const amount = hexToDecimal(balance || "0", decimals);
|
|
121
190
|
totalBalance += amount;
|
|
122
191
|
tokens.push({
|
|
123
|
-
chain:
|
|
192
|
+
chain: CHAIN_ID_TO_NAME[positionChainId] ?? "BASE",
|
|
124
193
|
chainId: positionChainId,
|
|
125
194
|
asset: symbol,
|
|
126
195
|
amount: String(amount)
|
|
127
196
|
});
|
|
128
|
-
const detail = detailByKey.get(`${positionChainId}:${symbol.toUpperCase()}`);
|
|
129
|
-
positions.push({
|
|
130
|
-
chain: chainName,
|
|
131
|
-
protocol: detail?.protocol_name ?? detail?.protocol_id ?? "",
|
|
132
|
-
protocolId: detail?.protocol_id,
|
|
133
|
-
pool: detail?.pool,
|
|
134
|
-
asset: symbol,
|
|
135
|
-
amount: String(amount),
|
|
136
|
-
apy: detail?.pool_apy,
|
|
137
|
-
tvl: detail?.pool_tvl,
|
|
138
|
-
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
139
|
-
// structural read keeps it undefined today and auto-populates later.
|
|
140
|
-
liquidity: detail?.liquidity
|
|
141
|
-
});
|
|
142
197
|
}
|
|
143
198
|
}
|
|
199
|
+
const positions = (portfolio.positions ?? []).map((p) => ({
|
|
200
|
+
chain: p.chain ?? "",
|
|
201
|
+
protocol: p.protocol_name ?? p.protocol_id ?? "",
|
|
202
|
+
protocolId: p.protocol_id,
|
|
203
|
+
pool: p.pool,
|
|
204
|
+
asset: p.token_symbol ?? "",
|
|
205
|
+
// Position amount is hex-encoded base units (like the token balances
|
|
206
|
+
// above). `PositionSlot` doesn't type a `decimals` field, but runtime
|
|
207
|
+
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
208
|
+
// when present, otherwise fall back to the per-symbol lookup so
|
|
209
|
+
// non-6-decimal assets like WETH don't render 1e12 too large.
|
|
210
|
+
amount: String(
|
|
211
|
+
hexToDecimal(
|
|
212
|
+
p.amount || "0",
|
|
213
|
+
tokenDecimals(p.token_symbol, p.decimals)
|
|
214
|
+
)
|
|
215
|
+
),
|
|
216
|
+
apy: p.pool_apy,
|
|
217
|
+
tvl: p.pool_tvl,
|
|
218
|
+
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
219
|
+
// structural read keeps it undefined today and auto-populates later.
|
|
220
|
+
liquidity: p.liquidity
|
|
221
|
+
}));
|
|
144
222
|
return {
|
|
145
223
|
smartWallet,
|
|
146
224
|
totalBalance: String(totalBalance),
|
|
@@ -179,41 +257,39 @@ function mapEarnings(raw, smartWallet) {
|
|
|
179
257
|
};
|
|
180
258
|
}
|
|
181
259
|
function mapWeightedApyByChain(raw) {
|
|
182
|
-
if (!raw)
|
|
183
|
-
return void 0;
|
|
260
|
+
if (!raw) return void 0;
|
|
184
261
|
const out = {};
|
|
185
262
|
for (const [chainKey, tokenApy] of Object.entries(raw)) {
|
|
186
263
|
const chainId = Number(chainKey);
|
|
187
|
-
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
188
|
-
continue;
|
|
264
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
189
265
|
const perAsset = {};
|
|
190
266
|
for (const [symbol, apy] of Object.entries(tokenApy)) {
|
|
191
267
|
const token = symbol;
|
|
192
|
-
if (!SUPPORTED_TOKENS.includes(token))
|
|
193
|
-
continue;
|
|
268
|
+
if (!SUPPORTED_TOKENS.includes(token)) continue;
|
|
194
269
|
perAsset[token] = apy;
|
|
195
270
|
}
|
|
196
|
-
if (Object.keys(perAsset).length > 0)
|
|
197
|
-
out[chainId] = perAsset;
|
|
271
|
+
if (Object.keys(perAsset).length > 0) out[chainId] = perAsset;
|
|
198
272
|
}
|
|
199
273
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
200
274
|
}
|
|
201
|
-
function rawPoolApyForChain(entry, chainId) {
|
|
275
|
+
function rawPoolApyForChain(entry, chainId, tokenSymbol) {
|
|
202
276
|
let weightedSum = 0;
|
|
203
277
|
let totalBalance = 0;
|
|
204
278
|
for (const p of entry.positions ?? []) {
|
|
205
|
-
if (p.chainId !== chainId)
|
|
206
|
-
|
|
279
|
+
if (p.chainId !== chainId) continue;
|
|
280
|
+
if (tokenSymbol && p.tokenSymbol !== tokenSymbol) continue;
|
|
207
281
|
const balance = p.balance ?? 0;
|
|
208
|
-
if (balance <= 0 || typeof p.apy !== "number")
|
|
209
|
-
continue;
|
|
282
|
+
if (balance <= 0 || typeof p.apy !== "number") continue;
|
|
210
283
|
weightedSum += p.apy * balance;
|
|
211
284
|
totalBalance += balance;
|
|
212
285
|
}
|
|
213
286
|
return totalBalance > 0 ? weightedSum / totalBalance : null;
|
|
214
287
|
}
|
|
215
|
-
function mapApyHistory(raw, chainId) {
|
|
216
|
-
const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
|
|
288
|
+
function mapApyHistory(raw, chainId, tokenSymbol) {
|
|
289
|
+
const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
|
|
290
|
+
date,
|
|
291
|
+
apy: rawPoolApyForChain(entry, chainId, tokenSymbol)
|
|
292
|
+
})).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
|
|
217
293
|
return {
|
|
218
294
|
walletAddress: raw.walletAddress,
|
|
219
295
|
weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
|
|
@@ -236,6 +312,7 @@ function mapEntries(rawEntries, chainId) {
|
|
|
236
312
|
"Withdraw from protocol": "Withdraw",
|
|
237
313
|
Rebalance: "Rebalance"
|
|
238
314
|
};
|
|
315
|
+
void chainId;
|
|
239
316
|
return rawEntries.map((entry) => {
|
|
240
317
|
const rawLog = entry.rebalanceLog;
|
|
241
318
|
const positions = entry.positions ?? [];
|
|
@@ -257,7 +334,15 @@ function mapEntries(rawEntries, chainId) {
|
|
|
257
334
|
fromPool: from.pool,
|
|
258
335
|
toPool: to.pool,
|
|
259
336
|
tokenSymbol: matched?.token_symbol ?? "",
|
|
260
|
-
amount: matched ? String(
|
|
337
|
+
amount: matched ? String(
|
|
338
|
+
hexToDecimal(
|
|
339
|
+
matched.amount || "0x",
|
|
340
|
+
tokenDecimals(
|
|
341
|
+
matched.token_symbol,
|
|
342
|
+
matched.decimals
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
) : "",
|
|
261
346
|
status: "success"
|
|
262
347
|
}
|
|
263
348
|
];
|
|
@@ -276,7 +361,12 @@ function mapEntries(rawEntries, chainId) {
|
|
|
276
361
|
txHashes: entry.transactionHash ? [entry.transactionHash] : [],
|
|
277
362
|
chainId: entry.chainId,
|
|
278
363
|
tokenSymbol: entry?.positions?.[0]?.token_symbol,
|
|
279
|
-
amount: hasAmount ? String(
|
|
364
|
+
amount: hasAmount ? String(
|
|
365
|
+
hexToDecimal(
|
|
366
|
+
rawAmount,
|
|
367
|
+
tokenDecimals(entry?.positions?.[0]?.token_symbol)
|
|
368
|
+
)
|
|
369
|
+
) : void 0
|
|
280
370
|
}
|
|
281
371
|
],
|
|
282
372
|
rebalanceLog
|
|
@@ -301,8 +391,7 @@ function mapApyByStrategy(raw) {
|
|
|
301
391
|
const supported = SupportedAssets.find(
|
|
302
392
|
(asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
|
|
303
393
|
);
|
|
304
|
-
if (!supported)
|
|
305
|
-
continue;
|
|
394
|
+
if (!supported) continue;
|
|
306
395
|
const chainKey = supported.chainId;
|
|
307
396
|
const symbolKey = supported.symbol;
|
|
308
397
|
const bucket = apyPerAsset[chainKey] ?? {};
|
|
@@ -332,10 +421,8 @@ function computeAllocationApy(positions) {
|
|
|
332
421
|
totalValue += value;
|
|
333
422
|
const chainId = resolveChainId(p.chain);
|
|
334
423
|
const token = p.asset;
|
|
335
|
-
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
336
|
-
|
|
337
|
-
if (!SUPPORTED_TOKENS.includes(token))
|
|
338
|
-
continue;
|
|
424
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
|
|
425
|
+
if (!SUPPORTED_TOKENS.includes(token)) continue;
|
|
339
426
|
const perAsset = buckets[chainId] ?? {};
|
|
340
427
|
const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
|
|
341
428
|
cell.weightedSum += apy * value;
|
|
@@ -352,8 +439,7 @@ function computeAllocationApy(positions) {
|
|
|
352
439
|
out[token] = cell.weightedSum / cell.value;
|
|
353
440
|
}
|
|
354
441
|
}
|
|
355
|
-
if (Object.keys(out).length > 0)
|
|
356
|
-
apyByChainAndAsset[chainId] = out;
|
|
442
|
+
if (Object.keys(out).length > 0) apyByChainAndAsset[chainId] = out;
|
|
357
443
|
}
|
|
358
444
|
const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
|
|
359
445
|
return { totalApy, apyByChainAndAsset };
|
|
@@ -370,19 +456,14 @@ function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
|
370
456
|
let total = 0n;
|
|
371
457
|
let matched = false;
|
|
372
458
|
for (const log of logs) {
|
|
373
|
-
if (log.address.toLowerCase() !== wantToken)
|
|
374
|
-
|
|
375
|
-
if ((log.topics[
|
|
376
|
-
|
|
377
|
-
if (topicToAddress(log.topics[2]) !== wantRecipient)
|
|
378
|
-
continue;
|
|
379
|
-
if (!log.data || log.data === "0x")
|
|
380
|
-
continue;
|
|
459
|
+
if (log.address.toLowerCase() !== wantToken) continue;
|
|
460
|
+
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
|
|
461
|
+
if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
|
|
462
|
+
if (!log.data || log.data === "0x") continue;
|
|
381
463
|
total += BigInt(log.data);
|
|
382
464
|
matched = true;
|
|
383
465
|
}
|
|
384
|
-
if (!matched)
|
|
385
|
-
return void 0;
|
|
466
|
+
if (!matched) return void 0;
|
|
386
467
|
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
387
468
|
}
|
|
388
469
|
|
|
@@ -398,8 +479,7 @@ function encodeHistoryCursor(payload) {
|
|
|
398
479
|
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
399
480
|
}
|
|
400
481
|
function decodeHistoryCursor(token) {
|
|
401
|
-
if (!token)
|
|
402
|
-
throw new InvalidHistoryCursorError("Cursor is empty.");
|
|
482
|
+
if (!token) throw new InvalidHistoryCursorError("Cursor is empty.");
|
|
403
483
|
let parsed;
|
|
404
484
|
try {
|
|
405
485
|
const json = Buffer.from(token, "base64").toString("utf8");
|
|
@@ -416,8 +496,7 @@ function decodeHistoryCursor(token) {
|
|
|
416
496
|
// src/agents/zyfai/zyfai.auth-cache.ts
|
|
417
497
|
var KEY_PREFIX = "owney.zyfai.session";
|
|
418
498
|
var storage = () => {
|
|
419
|
-
if (typeof window === "undefined")
|
|
420
|
-
return null;
|
|
499
|
+
if (typeof window === "undefined") return null;
|
|
421
500
|
try {
|
|
422
501
|
return window.localStorage;
|
|
423
502
|
} catch {
|
|
@@ -427,15 +506,13 @@ var storage = () => {
|
|
|
427
506
|
var buildKey = (address, chainId) => `${KEY_PREFIX}:${address.toLowerCase()}:${chainId}`;
|
|
428
507
|
var isJwtExpired = (token) => {
|
|
429
508
|
const parts = token.split(".");
|
|
430
|
-
if (parts.length !== 3)
|
|
431
|
-
return false;
|
|
509
|
+
if (parts.length !== 3) return false;
|
|
432
510
|
try {
|
|
433
511
|
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
434
512
|
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
435
513
|
const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
|
|
436
514
|
const claims = JSON.parse(decoded);
|
|
437
|
-
if (typeof claims.exp !== "number")
|
|
438
|
-
return false;
|
|
515
|
+
if (typeof claims.exp !== "number") return false;
|
|
439
516
|
return Date.now() / 1e3 >= claims.exp - 30;
|
|
440
517
|
} catch {
|
|
441
518
|
return false;
|
|
@@ -443,11 +520,9 @@ var isJwtExpired = (token) => {
|
|
|
443
520
|
};
|
|
444
521
|
var readSession = (address, chainId) => {
|
|
445
522
|
const store = storage();
|
|
446
|
-
if (!store)
|
|
447
|
-
return null;
|
|
523
|
+
if (!store) return null;
|
|
448
524
|
const raw = store.getItem(buildKey(address, chainId));
|
|
449
|
-
if (!raw)
|
|
450
|
-
return null;
|
|
525
|
+
if (!raw) return null;
|
|
451
526
|
try {
|
|
452
527
|
const parsed = JSON.parse(raw);
|
|
453
528
|
if (!parsed.token || isJwtExpired(parsed.token)) {
|
|
@@ -462,8 +537,7 @@ var readSession = (address, chainId) => {
|
|
|
462
537
|
};
|
|
463
538
|
var writeSession = (address, chainId, session) => {
|
|
464
539
|
const store = storage();
|
|
465
|
-
if (!store)
|
|
466
|
-
return;
|
|
540
|
+
if (!store) return;
|
|
467
541
|
try {
|
|
468
542
|
store.setItem(buildKey(address, chainId), JSON.stringify(session));
|
|
469
543
|
} catch {
|
|
@@ -471,33 +545,67 @@ var writeSession = (address, chainId, session) => {
|
|
|
471
545
|
};
|
|
472
546
|
var clearSession = (address, chainId) => {
|
|
473
547
|
const store = storage();
|
|
474
|
-
if (!store)
|
|
475
|
-
return;
|
|
548
|
+
if (!store) return;
|
|
476
549
|
try {
|
|
477
550
|
store.removeItem(buildKey(address, chainId));
|
|
478
551
|
} catch {
|
|
479
552
|
}
|
|
480
553
|
};
|
|
481
554
|
|
|
555
|
+
// src/agents/zyfai/autoSelect.ts
|
|
556
|
+
function buildAutoSelectUpdate(current, asset) {
|
|
557
|
+
if (current.autoSelectProtocols === true) return null;
|
|
558
|
+
return {
|
|
559
|
+
asset,
|
|
560
|
+
autoSelectProtocols: true,
|
|
561
|
+
...current.chains ? { chains: current.chains } : {}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
482
565
|
// src/agents/zyfai/zyfai.agent.ts
|
|
483
566
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
484
567
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
485
|
-
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
568
|
+
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
569
|
+
1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
570
|
+
};
|
|
571
|
+
var WETH_ADDRESS_BY_CHAIN = {
|
|
572
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
573
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
574
|
+
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
486
575
|
};
|
|
487
|
-
var VIEM_CHAIN = { 8453: base, 42161: arbitrum };
|
|
576
|
+
var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
|
|
488
577
|
var ZyfaiAgent = class {
|
|
489
578
|
id = "zyfai";
|
|
490
|
-
supportedChainIds = [8453, 42161];
|
|
579
|
+
supportedChainIds = [8453, 42161, 1];
|
|
491
580
|
supportedAssets = [
|
|
492
581
|
{
|
|
493
582
|
chainId: 8453,
|
|
494
583
|
chain: "BASE",
|
|
495
|
-
assets: [
|
|
584
|
+
assets: [
|
|
585
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
586
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
587
|
+
]
|
|
496
588
|
},
|
|
497
589
|
{
|
|
498
590
|
chainId: 42161,
|
|
499
591
|
chain: "ARBITRUM",
|
|
500
|
-
assets: [
|
|
592
|
+
assets: [
|
|
593
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
594
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
595
|
+
]
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
chainId: 1,
|
|
599
|
+
chain: "ETHEREUM",
|
|
600
|
+
// Zyfai enforces per-asset minimum portfolios on Ethereum mainnet, keyed
|
|
601
|
+
// by MIN_PORTFOLIO_BALANCE[1] in @zyfai/sdk. Lowered in @zyfai/sdk@0.2.40.
|
|
602
|
+
// USDC: 100 (100 * 1e6). WETH: 0.005 (5 * 1e15).
|
|
603
|
+
// NOTE: the @zyfai/sdk README claims WETH has no minimum — that's stale;
|
|
604
|
+
// the shipped code enforces 0.005 WETH, so match it or deposits reject on-chain.
|
|
605
|
+
assets: [
|
|
606
|
+
{ symbol: "USDC", minDepositAmount: "100000000" },
|
|
607
|
+
{ symbol: "WETH", minDepositAmount: "5000000000000000" }
|
|
608
|
+
]
|
|
501
609
|
}
|
|
502
610
|
];
|
|
503
611
|
sdk;
|
|
@@ -521,8 +629,7 @@ var ZyfaiAgent = class {
|
|
|
521
629
|
}
|
|
522
630
|
getPublicClient(chainId) {
|
|
523
631
|
const cached = this.publicClients.get(chainId);
|
|
524
|
-
if (cached)
|
|
525
|
-
return cached;
|
|
632
|
+
if (cached) return cached;
|
|
526
633
|
const client = createPublicClient({
|
|
527
634
|
chain: VIEM_CHAIN[chainId],
|
|
528
635
|
transport: http(this.rpcUrls[chainId])
|
|
@@ -532,33 +639,43 @@ var ZyfaiAgent = class {
|
|
|
532
639
|
}
|
|
533
640
|
/**
|
|
534
641
|
* Fills in the true withdrawn amount for `Withdraw` entries by decoding the
|
|
535
|
-
*
|
|
642
|
+
* on-chain `Transfer` that lands in the user's wallet. The Zyfai history
|
|
536
643
|
* payload only reports the *remaining* position for a partial withdraw (see
|
|
537
644
|
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
538
645
|
* balance instead of the amount withdrawn.
|
|
539
646
|
*
|
|
647
|
+
* The withdrawn asset varies per entry (USDC or WETH), so it's resolved
|
|
648
|
+
* from `tx.tokenSymbol` (set by `mapEntries` from the entry's own
|
|
649
|
+
* position) rather than assumed to always be USDC. Zyfai sometimes
|
|
650
|
+
* denominates WETH as lowercase "eth" — that's normalized to WETH.
|
|
651
|
+
* Entries with no symbol (older payload shapes) fall back to USDC to
|
|
652
|
+
* preserve prior behavior.
|
|
653
|
+
*
|
|
540
654
|
* Mutates `entries` in place. Runs all lookups concurrently and is tolerant
|
|
541
655
|
* of RPC failures: an entry whose amount can't be resolved keeps
|
|
542
656
|
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
543
657
|
* rather than a wrong number.
|
|
544
658
|
*/
|
|
545
659
|
async resolveWithdrawnAmounts(entries, chainId, recipient) {
|
|
546
|
-
const asset = SupportedAssets.find(
|
|
547
|
-
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
548
|
-
);
|
|
549
|
-
if (!asset)
|
|
550
|
-
return;
|
|
551
660
|
const withdrawals = entries.filter(
|
|
552
661
|
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
553
662
|
);
|
|
554
|
-
if (withdrawals.length === 0)
|
|
555
|
-
return;
|
|
663
|
+
if (withdrawals.length === 0) return;
|
|
556
664
|
const client = this.getPublicClient(chainId);
|
|
557
665
|
await Promise.all(
|
|
558
666
|
withdrawals.map(async (entry) => {
|
|
559
667
|
const tx = entry.transactions[0];
|
|
560
668
|
const txHash = tx.txHashes[0];
|
|
561
|
-
const
|
|
669
|
+
const rawSymbol = tx.tokenSymbol;
|
|
670
|
+
const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
|
|
671
|
+
const asset = SupportedAssets.find(
|
|
672
|
+
(a) => a.chainId === chainId && a.symbol === normalizedSymbol
|
|
673
|
+
) ?? SupportedAssets.find(
|
|
674
|
+
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
675
|
+
);
|
|
676
|
+
if (!asset) return;
|
|
677
|
+
const cacheKey = `${txHash}:${asset.address}`;
|
|
678
|
+
const cached = this.withdrawAmountCache.get(cacheKey);
|
|
562
679
|
if (cached !== void 0) {
|
|
563
680
|
tx.amount = cached;
|
|
564
681
|
return;
|
|
@@ -574,7 +691,7 @@ var ZyfaiAgent = class {
|
|
|
574
691
|
asset.decimals
|
|
575
692
|
);
|
|
576
693
|
if (amount !== void 0) {
|
|
577
|
-
this.withdrawAmountCache.set(
|
|
694
|
+
this.withdrawAmountCache.set(cacheKey, amount);
|
|
578
695
|
tx.amount = amount;
|
|
579
696
|
}
|
|
580
697
|
} catch {
|
|
@@ -593,13 +710,11 @@ var ZyfaiAgent = class {
|
|
|
593
710
|
* for some apps and the patch only matters in the browser anyway.
|
|
594
711
|
*/
|
|
595
712
|
installAuthCache() {
|
|
596
|
-
if (typeof window === "undefined")
|
|
597
|
-
return;
|
|
713
|
+
if (typeof window === "undefined") return;
|
|
598
714
|
const internals = this.sdk;
|
|
599
715
|
const original = internals.authenticateUser.bind(internals);
|
|
600
716
|
internals.authenticateUser = async () => {
|
|
601
|
-
if (internals.authenticatedUserId !== null)
|
|
602
|
-
return;
|
|
717
|
+
if (internals.authenticatedUserId !== null) return;
|
|
603
718
|
const address = internals.walletClient?.account?.address;
|
|
604
719
|
const chainId = internals.currentChainId;
|
|
605
720
|
if (address && chainId) {
|
|
@@ -674,12 +789,10 @@ var ZyfaiAgent = class {
|
|
|
674
789
|
return await this.sdk.connectAccount(provider, chainId);
|
|
675
790
|
} catch (error) {
|
|
676
791
|
const message = error instanceof Error ? error.message : String(error);
|
|
677
|
-
if (!message.toLowerCase().includes("unauthorized"))
|
|
678
|
-
throw error;
|
|
792
|
+
if (!message.toLowerCase().includes("unauthorized")) throw error;
|
|
679
793
|
const internals = this.sdk;
|
|
680
794
|
const address = internals.walletClient?.account?.address;
|
|
681
|
-
if (address)
|
|
682
|
-
clearSession(address, chainId);
|
|
795
|
+
if (address) clearSession(address, chainId);
|
|
683
796
|
return await this.sdk.connectAccount(provider, chainId);
|
|
684
797
|
}
|
|
685
798
|
}
|
|
@@ -692,6 +805,22 @@ var ZyfaiAgent = class {
|
|
|
692
805
|
this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
|
|
693
806
|
if (!wallet.isDeployed) {
|
|
694
807
|
await this.sdk.deploySafe(address, validChainId, "aggressive", true);
|
|
808
|
+
try {
|
|
809
|
+
if (validChainId === 42161) {
|
|
810
|
+
await this.sdk.updateUserProfile({
|
|
811
|
+
asset: "WETH",
|
|
812
|
+
chains: [validChainId],
|
|
813
|
+
protocols: ["c24a33fa-4a87-4376-b756-93bd3957d3ea"],
|
|
814
|
+
// AAVE protocol ID
|
|
815
|
+
autoSelectProtocols: false
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
} catch (err) {
|
|
819
|
+
console.warn(
|
|
820
|
+
"Failed to configure AAVE protocol after Safe deployment:",
|
|
821
|
+
err instanceof Error ? err.message : String(err)
|
|
822
|
+
);
|
|
823
|
+
}
|
|
695
824
|
}
|
|
696
825
|
const user = await this.sdk.getUserDetails();
|
|
697
826
|
if (!user.hasActiveSessionKey) {
|
|
@@ -733,7 +862,7 @@ var ZyfaiAgent = class {
|
|
|
733
862
|
throw error;
|
|
734
863
|
}
|
|
735
864
|
}
|
|
736
|
-
async deposit(state, chainId, amount, depositCallback) {
|
|
865
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
737
866
|
try {
|
|
738
867
|
await this.connectFull(state, chainId);
|
|
739
868
|
const validChainId = isValidChainId(chainId);
|
|
@@ -747,13 +876,15 @@ var ZyfaiAgent = class {
|
|
|
747
876
|
validChainId,
|
|
748
877
|
amount
|
|
749
878
|
);
|
|
750
|
-
|
|
879
|
+
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
880
|
+
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
751
881
|
return { txHash, smartWallet: wallet.address, amount };
|
|
752
882
|
}
|
|
753
883
|
const raw = await this.sdk.depositFunds(
|
|
754
884
|
this.getAddress(),
|
|
755
885
|
validChainId,
|
|
756
|
-
amount
|
|
886
|
+
amount,
|
|
887
|
+
asset
|
|
757
888
|
);
|
|
758
889
|
return mapDeposit(raw);
|
|
759
890
|
} catch (error) {
|
|
@@ -791,10 +922,10 @@ var ZyfaiAgent = class {
|
|
|
791
922
|
const raw = await this.sdk.getOnchainEarnings(smartWallet);
|
|
792
923
|
return mapEarnings(raw, smartWallet);
|
|
793
924
|
}
|
|
794
|
-
async getAccountApy(state, chainId, days) {
|
|
925
|
+
async getAccountApy(state, chainId, days, tokenSymbol) {
|
|
795
926
|
const { smartWallet } = await this.resolveSmartWallet(state, chainId);
|
|
796
927
|
const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
|
|
797
|
-
return mapApyHistory(raw, chainId);
|
|
928
|
+
return mapApyHistory(raw, chainId, tokenSymbol);
|
|
798
929
|
}
|
|
799
930
|
async getHistory(state, chainId, options) {
|
|
800
931
|
const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
|
|
@@ -849,6 +980,20 @@ var ZyfaiAgent = class {
|
|
|
849
980
|
}
|
|
850
981
|
return mapUserProfile(raw, this.connectedAddress);
|
|
851
982
|
}
|
|
983
|
+
async ensureAutoSelectProtocols(state, chainId, asset) {
|
|
984
|
+
await this.connectAuth(state, chainId);
|
|
985
|
+
const current = await this.sdk.getUserDetails(asset);
|
|
986
|
+
const update = buildAutoSelectUpdate(
|
|
987
|
+
{
|
|
988
|
+
autoSelectProtocols: current.autoSelectProtocols,
|
|
989
|
+
chains: current.chains
|
|
990
|
+
},
|
|
991
|
+
asset
|
|
992
|
+
);
|
|
993
|
+
if (!update) return false;
|
|
994
|
+
await this.sdk.updateUserProfile(update);
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
852
997
|
// --- IAgent: Discovery (no wallet required) ---
|
|
853
998
|
async getAgentApy(days, options) {
|
|
854
999
|
const raw = await this.sdk.getAPYPerStrategy(
|
|
@@ -934,8 +1079,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
934
1079
|
const tokenBalance = agentBalance?.tokens.find(
|
|
935
1080
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
936
1081
|
);
|
|
937
|
-
if (!tokenBalance)
|
|
938
|
-
return { agent, balance: 0n };
|
|
1082
|
+
if (!tokenBalance) return { agent, balance: 0n };
|
|
939
1083
|
return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
|
|
940
1084
|
});
|
|
941
1085
|
}
|
|
@@ -952,11 +1096,9 @@ function planProportionalShares(balances, requested, totalAvailable) {
|
|
|
952
1096
|
return diff > 0n ? 1 : diff < 0n ? -1 : 0;
|
|
953
1097
|
});
|
|
954
1098
|
for (const p of byHeadroom) {
|
|
955
|
-
if (remainder === 0n)
|
|
956
|
-
break;
|
|
1099
|
+
if (remainder === 0n) break;
|
|
957
1100
|
const headroom = p.balance - p.planned;
|
|
958
|
-
if (headroom <= 0n)
|
|
959
|
-
continue;
|
|
1101
|
+
if (headroom <= 0n) continue;
|
|
960
1102
|
const take = headroom < remainder ? headroom : remainder;
|
|
961
1103
|
p.planned += take;
|
|
962
1104
|
remainder -= take;
|
|
@@ -985,8 +1127,7 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
|
985
1127
|
(s, c) => s + (c.balance - c.planned),
|
|
986
1128
|
0n
|
|
987
1129
|
);
|
|
988
|
-
if (totalHeadroom === 0n)
|
|
989
|
-
return;
|
|
1130
|
+
if (totalHeadroom === 0n) return;
|
|
990
1131
|
let distributed = 0n;
|
|
991
1132
|
for (const c of candidates) {
|
|
992
1133
|
const headroom = c.balance - c.planned;
|
|
@@ -997,11 +1138,9 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
|
|
|
997
1138
|
}
|
|
998
1139
|
let leftover = amount - distributed;
|
|
999
1140
|
for (const c of candidates) {
|
|
1000
|
-
if (leftover === 0n)
|
|
1001
|
-
break;
|
|
1141
|
+
if (leftover === 0n) break;
|
|
1002
1142
|
const headroom = c.balance - c.planned;
|
|
1003
|
-
if (headroom <= 0n)
|
|
1004
|
-
continue;
|
|
1143
|
+
if (headroom <= 0n) continue;
|
|
1005
1144
|
const take = headroom < leftover ? headroom : leftover;
|
|
1006
1145
|
c.planned += take;
|
|
1007
1146
|
leftover -= take;
|
|
@@ -1018,16 +1157,13 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
1018
1157
|
for (const id of Object.keys(agentApys)) {
|
|
1019
1158
|
const cells = agentApys[id].apyByChainAndAsset;
|
|
1020
1159
|
const balance = agentBalances[id] ?? 0;
|
|
1021
|
-
if (!cells || balance <= 0)
|
|
1022
|
-
continue;
|
|
1160
|
+
if (!cells || balance <= 0) continue;
|
|
1023
1161
|
for (const [chainKey, perAsset] of Object.entries(cells)) {
|
|
1024
|
-
if (!perAsset)
|
|
1025
|
-
continue;
|
|
1162
|
+
if (!perAsset) continue;
|
|
1026
1163
|
const chainId = Number(chainKey);
|
|
1027
1164
|
for (const [asset, apyValue] of Object.entries(perAsset)) {
|
|
1028
1165
|
const apy = Number(apyValue ?? 0);
|
|
1029
|
-
if (apy === 0)
|
|
1030
|
-
continue;
|
|
1166
|
+
if (apy === 0) continue;
|
|
1031
1167
|
sums[chainId] ??= {};
|
|
1032
1168
|
weights[chainId] ??= {};
|
|
1033
1169
|
sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
|
|
@@ -1041,8 +1177,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
1041
1177
|
const perAssetOut = {};
|
|
1042
1178
|
for (const asset of Object.keys(sums[chainId])) {
|
|
1043
1179
|
const w = weights[chainId][asset];
|
|
1044
|
-
if (w > 0)
|
|
1045
|
-
perAssetOut[asset] = sums[chainId][asset] / w;
|
|
1180
|
+
if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
|
|
1046
1181
|
}
|
|
1047
1182
|
if (Object.keys(perAssetOut).length > 0) {
|
|
1048
1183
|
out[chainId] = perAssetOut;
|
|
@@ -1057,7 +1192,7 @@ import {
|
|
|
1057
1192
|
createWalletClient,
|
|
1058
1193
|
custom
|
|
1059
1194
|
} from "viem";
|
|
1060
|
-
import { base as base2, arbitrum as arbitrum2 } from "viem/chains";
|
|
1195
|
+
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
1061
1196
|
|
|
1062
1197
|
// src/lib/transfer-auth.ts
|
|
1063
1198
|
import { bytesToHex } from "viem";
|
|
@@ -1103,7 +1238,10 @@ async function postSponsorTransferAuth(input) {
|
|
|
1103
1238
|
try {
|
|
1104
1239
|
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
1105
1240
|
method: "POST",
|
|
1106
|
-
headers: {
|
|
1241
|
+
headers: {
|
|
1242
|
+
"content-type": "application/json",
|
|
1243
|
+
"x-owney-api-key": input.apiKey
|
|
1244
|
+
},
|
|
1107
1245
|
body: JSON.stringify(input.body)
|
|
1108
1246
|
});
|
|
1109
1247
|
} catch (networkError) {
|
|
@@ -1123,11 +1261,91 @@ async function postSponsorTransferAuth(input) {
|
|
|
1123
1261
|
throw new OwneyError(
|
|
1124
1262
|
"SPONSOR_REQUEST_FAILED",
|
|
1125
1263
|
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1126
|
-
{
|
|
1264
|
+
{
|
|
1265
|
+
statusCode: res.status,
|
|
1266
|
+
responseBody: text.slice(0, 500),
|
|
1267
|
+
// 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
|
|
1268
|
+
// before broadcast, so it is safe to fall back to a user-paid deposit.
|
|
1269
|
+
safeToFallback: res.status === 503
|
|
1270
|
+
}
|
|
1127
1271
|
);
|
|
1128
1272
|
}
|
|
1129
1273
|
return parsed.data;
|
|
1130
1274
|
}
|
|
1275
|
+
async function postSponsorPermit2Transfer(input) {
|
|
1276
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1277
|
+
let res;
|
|
1278
|
+
try {
|
|
1279
|
+
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
1280
|
+
method: "POST",
|
|
1281
|
+
headers: {
|
|
1282
|
+
"content-type": "application/json",
|
|
1283
|
+
"x-owney-api-key": input.apiKey
|
|
1284
|
+
},
|
|
1285
|
+
body: JSON.stringify(input.body)
|
|
1286
|
+
});
|
|
1287
|
+
} catch (networkError) {
|
|
1288
|
+
throw new OwneyError(
|
|
1289
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1290
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1291
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
const text = await res.text();
|
|
1295
|
+
let parsed = null;
|
|
1296
|
+
try {
|
|
1297
|
+
parsed = JSON.parse(text);
|
|
1298
|
+
} catch {
|
|
1299
|
+
}
|
|
1300
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
1301
|
+
throw new OwneyError(
|
|
1302
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1303
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1304
|
+
{
|
|
1305
|
+
statusCode: res.status,
|
|
1306
|
+
responseBody: text.slice(0, 500),
|
|
1307
|
+
safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
return parsed.data;
|
|
1312
|
+
}
|
|
1313
|
+
async function getSponsorRelayerAddress(input) {
|
|
1314
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1315
|
+
let res;
|
|
1316
|
+
try {
|
|
1317
|
+
res = await fetch(
|
|
1318
|
+
`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
1319
|
+
{
|
|
1320
|
+
headers: { "x-owney-api-key": input.apiKey }
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
} catch (networkError) {
|
|
1324
|
+
throw new OwneyError(
|
|
1325
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1326
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1327
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
const text = await res.text();
|
|
1331
|
+
let parsed = null;
|
|
1332
|
+
try {
|
|
1333
|
+
parsed = JSON.parse(text);
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1336
|
+
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
1337
|
+
throw new OwneyError(
|
|
1338
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1339
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1340
|
+
{
|
|
1341
|
+
statusCode: res.status,
|
|
1342
|
+
responseBody: text.slice(0, 500),
|
|
1343
|
+
safeToFallback: true
|
|
1344
|
+
}
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
return parsed.data.relayer;
|
|
1348
|
+
}
|
|
1131
1349
|
|
|
1132
1350
|
// src/lib/sponsored-deposit.ts
|
|
1133
1351
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
@@ -1179,6 +1397,114 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
1179
1397
|
};
|
|
1180
1398
|
}
|
|
1181
1399
|
|
|
1400
|
+
// src/lib/permit2.ts
|
|
1401
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
1402
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
1403
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
1404
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
1405
|
+
{
|
|
1406
|
+
type: "function",
|
|
1407
|
+
name: "allowance",
|
|
1408
|
+
stateMutability: "view",
|
|
1409
|
+
inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }],
|
|
1410
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
type: "function",
|
|
1414
|
+
name: "approve",
|
|
1415
|
+
stateMutability: "nonpayable",
|
|
1416
|
+
inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
|
|
1417
|
+
outputs: [{ name: "", type: "bool" }]
|
|
1418
|
+
}
|
|
1419
|
+
];
|
|
1420
|
+
function buildPermitTransferFromTypedData(input) {
|
|
1421
|
+
return {
|
|
1422
|
+
domain: { name: "Permit2", chainId: input.chainId, verifyingContract: PERMIT2_ADDRESS },
|
|
1423
|
+
types: {
|
|
1424
|
+
PermitTransferFrom: [
|
|
1425
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
1426
|
+
{ name: "spender", type: "address" },
|
|
1427
|
+
{ name: "nonce", type: "uint256" },
|
|
1428
|
+
{ name: "deadline", type: "uint256" }
|
|
1429
|
+
],
|
|
1430
|
+
TokenPermissions: [
|
|
1431
|
+
{ name: "token", type: "address" },
|
|
1432
|
+
{ name: "amount", type: "uint256" }
|
|
1433
|
+
]
|
|
1434
|
+
},
|
|
1435
|
+
primaryType: "PermitTransferFrom",
|
|
1436
|
+
message: input.message
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function randomPermit2Nonce() {
|
|
1440
|
+
const bytes = new Uint8Array(32);
|
|
1441
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1442
|
+
return BigInt(bytesToHex2(bytes));
|
|
1443
|
+
}
|
|
1444
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
1445
|
+
return publicClient.readContract({
|
|
1446
|
+
address: token,
|
|
1447
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
1448
|
+
functionName: "allowance",
|
|
1449
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
1454
|
+
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
1455
|
+
function makeSponsoredWethCallback(deps) {
|
|
1456
|
+
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
1457
|
+
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
1458
|
+
return async (smartWallet, chainId, amount) => {
|
|
1459
|
+
const cid = chainId;
|
|
1460
|
+
const token = deps.tokenAddressByChain[cid];
|
|
1461
|
+
if (!token) {
|
|
1462
|
+
throw new OwneyError("CHAIN_UNSUPPORTED", `No sponsored WETH configured for chain ${chainId}`);
|
|
1463
|
+
}
|
|
1464
|
+
const amountWei = BigInt(amount);
|
|
1465
|
+
const pub = deps.getPublicClient(cid);
|
|
1466
|
+
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
1467
|
+
if (allowance < amountWei) {
|
|
1468
|
+
throw new OwneyError(
|
|
1469
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
1470
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
1471
|
+
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
const relayer = await get({ baseUrl: deps.baseUrl, apiKey: deps.apiKey, chainId: cid });
|
|
1475
|
+
const nonce = randomPermit2Nonce();
|
|
1476
|
+
const deadline = BigInt(Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS);
|
|
1477
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
1478
|
+
chainId: cid,
|
|
1479
|
+
message: {
|
|
1480
|
+
permitted: { token, amount: amountWei },
|
|
1481
|
+
spender: relayer,
|
|
1482
|
+
nonce,
|
|
1483
|
+
deadline
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
const wallet = deps.getWalletClient(cid);
|
|
1487
|
+
const signature = await wallet.signTypedData(
|
|
1488
|
+
{ account: deps.ownerAddress, ...typedData }
|
|
1489
|
+
);
|
|
1490
|
+
const result = await post({
|
|
1491
|
+
baseUrl: deps.baseUrl,
|
|
1492
|
+
apiKey: deps.apiKey,
|
|
1493
|
+
body: {
|
|
1494
|
+
chainId: cid,
|
|
1495
|
+
token,
|
|
1496
|
+
from: deps.ownerAddress,
|
|
1497
|
+
to: smartWallet,
|
|
1498
|
+
amount,
|
|
1499
|
+
nonce: nonce.toString(),
|
|
1500
|
+
deadline: deadline.toString(),
|
|
1501
|
+
signature
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
return result.txHash;
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1182
1508
|
// src/client.ts
|
|
1183
1509
|
function encodeMultiAgentCursor(map) {
|
|
1184
1510
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
@@ -1202,13 +1528,22 @@ function decodeMultiAgentCursor(token) {
|
|
|
1202
1528
|
}
|
|
1203
1529
|
var SPONSORED_USDC_BY_CHAIN = {
|
|
1204
1530
|
8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1205
|
-
42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
|
|
1531
|
+
42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
|
|
1532
|
+
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
1206
1533
|
};
|
|
1207
1534
|
var VIEM_CHAIN2 = {
|
|
1208
1535
|
8453: base2,
|
|
1209
|
-
42161: arbitrum2
|
|
1536
|
+
42161: arbitrum2,
|
|
1537
|
+
1: mainnet2
|
|
1538
|
+
};
|
|
1539
|
+
var SPONSORED_WETH_BY_CHAIN = {
|
|
1540
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
1541
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
1542
|
+
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
1210
1543
|
};
|
|
1211
|
-
|
|
1544
|
+
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
1545
|
+
return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
1546
|
+
}
|
|
1212
1547
|
var OwneySDK = class {
|
|
1213
1548
|
agents = /* @__PURE__ */ new Map();
|
|
1214
1549
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -1221,11 +1556,14 @@ var OwneySDK = class {
|
|
|
1221
1556
|
state = null;
|
|
1222
1557
|
apiKey;
|
|
1223
1558
|
zyfaiRpcUrls;
|
|
1559
|
+
routingApiBaseUrl;
|
|
1224
1560
|
cachedSponsoredCallback = null;
|
|
1561
|
+
cachedWethSponsoredCallback = null;
|
|
1225
1562
|
initializingAgentsPromise = null;
|
|
1226
1563
|
constructor(config) {
|
|
1227
1564
|
this.apiKey = config.apiKey;
|
|
1228
1565
|
this.zyfaiRpcUrls = config.zyfaiRpcUrls;
|
|
1566
|
+
this.routingApiBaseUrl = config.routingApiBaseUrl;
|
|
1229
1567
|
}
|
|
1230
1568
|
/**
|
|
1231
1569
|
* Establish connection state for all subsequent SDK calls.
|
|
@@ -1246,6 +1584,7 @@ var OwneySDK = class {
|
|
|
1246
1584
|
}
|
|
1247
1585
|
this.state = { provider, walletAddress, chainId: null };
|
|
1248
1586
|
this.cachedSponsoredCallback = null;
|
|
1587
|
+
this.cachedWethSponsoredCallback = null;
|
|
1249
1588
|
}
|
|
1250
1589
|
/**
|
|
1251
1590
|
* Disconnect from all agents and clear connection state.
|
|
@@ -1259,6 +1598,7 @@ var OwneySDK = class {
|
|
|
1259
1598
|
this.disabledAgents.clear();
|
|
1260
1599
|
this.state = null;
|
|
1261
1600
|
this.cachedSponsoredCallback = null;
|
|
1601
|
+
this.cachedWethSponsoredCallback = null;
|
|
1262
1602
|
}
|
|
1263
1603
|
isAgentDisabled(agentId) {
|
|
1264
1604
|
return this.disabledAgents.has(agentId);
|
|
@@ -1278,8 +1618,7 @@ var OwneySDK = class {
|
|
|
1278
1618
|
return this.state?.chainId ?? null;
|
|
1279
1619
|
}
|
|
1280
1620
|
requireState() {
|
|
1281
|
-
if (!this.state)
|
|
1282
|
-
throw new NotConnectedError();
|
|
1621
|
+
if (!this.state) throw new NotConnectedError();
|
|
1283
1622
|
return this.state;
|
|
1284
1623
|
}
|
|
1285
1624
|
requireChainId() {
|
|
@@ -1309,12 +1648,12 @@ var OwneySDK = class {
|
|
|
1309
1648
|
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
1310
1649
|
*/
|
|
1311
1650
|
getDefaultSponsoredCallback() {
|
|
1312
|
-
if (this.cachedSponsoredCallback)
|
|
1313
|
-
return this.cachedSponsoredCallback;
|
|
1651
|
+
if (this.cachedSponsoredCallback) return this.cachedSponsoredCallback;
|
|
1314
1652
|
const provider = this.requireConnectedProvider();
|
|
1315
1653
|
const owner = this.state.walletAddress;
|
|
1316
1654
|
this.cachedSponsoredCallback = makeSponsoredDepositCallback({
|
|
1317
1655
|
apiKey: this.apiKey,
|
|
1656
|
+
baseUrl: this.routingApiBaseUrl,
|
|
1318
1657
|
ownerAddress: owner,
|
|
1319
1658
|
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
1320
1659
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
@@ -1332,6 +1671,37 @@ var OwneySDK = class {
|
|
|
1332
1671
|
});
|
|
1333
1672
|
return this.cachedSponsoredCallback;
|
|
1334
1673
|
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
1676
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
1677
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
1678
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
1679
|
+
*/
|
|
1680
|
+
getDefaultWethSponsoredCallback() {
|
|
1681
|
+
if (this.cachedWethSponsoredCallback)
|
|
1682
|
+
return this.cachedWethSponsoredCallback;
|
|
1683
|
+
const provider = this.requireConnectedProvider();
|
|
1684
|
+
const owner = this.state.walletAddress;
|
|
1685
|
+
this.cachedWethSponsoredCallback = makeSponsoredWethCallback({
|
|
1686
|
+
apiKey: this.apiKey,
|
|
1687
|
+
baseUrl: this.routingApiBaseUrl,
|
|
1688
|
+
ownerAddress: owner,
|
|
1689
|
+
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
1690
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
1691
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
1692
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
1693
|
+
getPublicClient: (cid) => createPublicClient2({
|
|
1694
|
+
chain: VIEM_CHAIN2[cid],
|
|
1695
|
+
transport: custom(provider)
|
|
1696
|
+
}),
|
|
1697
|
+
getWalletClient: (cid) => createWalletClient({
|
|
1698
|
+
account: owner,
|
|
1699
|
+
chain: VIEM_CHAIN2[cid],
|
|
1700
|
+
transport: custom(provider)
|
|
1701
|
+
})
|
|
1702
|
+
});
|
|
1703
|
+
return this.cachedWethSponsoredCallback;
|
|
1704
|
+
}
|
|
1335
1705
|
getAgent(agentId) {
|
|
1336
1706
|
const agent = this.agents.get(agentId);
|
|
1337
1707
|
if (!agent) {
|
|
@@ -1343,22 +1713,19 @@ var OwneySDK = class {
|
|
|
1343
1713
|
const active = /* @__PURE__ */ new Map();
|
|
1344
1714
|
for (const id of this.activeAgents) {
|
|
1345
1715
|
const agent = this.agents.get(id);
|
|
1346
|
-
if (agent)
|
|
1347
|
-
active.set(id, agent);
|
|
1716
|
+
if (agent) active.set(id, agent);
|
|
1348
1717
|
}
|
|
1349
1718
|
return active;
|
|
1350
1719
|
}
|
|
1351
1720
|
async ensureAgentsInitialized() {
|
|
1352
|
-
if (this.agents.size > 0)
|
|
1353
|
-
return;
|
|
1721
|
+
if (this.agents.size > 0) return;
|
|
1354
1722
|
if (!this.initializingAgentsPromise) {
|
|
1355
1723
|
this.initializingAgentsPromise = (async () => {
|
|
1356
1724
|
const agentKeys = await fetchAgentKeys(this.apiKey);
|
|
1357
1725
|
this.disabledAgents.clear();
|
|
1358
|
-
for (const { key, agent_type,
|
|
1359
|
-
const agent =
|
|
1360
|
-
if (!agent)
|
|
1361
|
-
continue;
|
|
1726
|
+
for (const { key, agent_type, is_enabled } of agentKeys) {
|
|
1727
|
+
const agent = this.createAgent(agent_type, key);
|
|
1728
|
+
if (!agent) continue;
|
|
1362
1729
|
this.agents.set(agent_type, agent);
|
|
1363
1730
|
if (is_enabled === false) {
|
|
1364
1731
|
this.disabledAgents.add(agent_type);
|
|
@@ -1378,16 +1745,10 @@ var OwneySDK = class {
|
|
|
1378
1745
|
this.initializingAgentsPromise = null;
|
|
1379
1746
|
}
|
|
1380
1747
|
}
|
|
1381
|
-
|
|
1748
|
+
createAgent(agentId, key) {
|
|
1382
1749
|
if (agentId === "zyfai") {
|
|
1383
1750
|
return new ZyfaiAgent(key, this.zyfaiRpcUrls);
|
|
1384
1751
|
}
|
|
1385
|
-
if (agentId === "surfliquid") {
|
|
1386
|
-
if (!projectName)
|
|
1387
|
-
return null;
|
|
1388
|
-
const { SurfLiquidAgent } = await import("./surfliquid.agent-EPFISJPR.js");
|
|
1389
|
-
return new SurfLiquidAgent({ projectName, appId: key });
|
|
1390
|
-
}
|
|
1391
1752
|
return null;
|
|
1392
1753
|
}
|
|
1393
1754
|
// --- Account lifecycle ---
|
|
@@ -1491,7 +1852,7 @@ var OwneySDK = class {
|
|
|
1491
1852
|
}
|
|
1492
1853
|
const state = this.requireState();
|
|
1493
1854
|
const chainId = this.requireChainId();
|
|
1494
|
-
const effectiveCallback = depositCallback ?? this.getDefaultSponsoredCallback();
|
|
1855
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback() : this.getDefaultSponsoredCallback());
|
|
1495
1856
|
if (agentId) {
|
|
1496
1857
|
if (this.isAgentDisabled(agentId)) {
|
|
1497
1858
|
throw new OwneyError(
|
|
@@ -1503,10 +1864,14 @@ var OwneySDK = class {
|
|
|
1503
1864
|
const agent = this.getAgent(agentId);
|
|
1504
1865
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1505
1866
|
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
1506
|
-
return
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1867
|
+
return this.depositWithFallback(
|
|
1868
|
+
agent,
|
|
1869
|
+
state,
|
|
1870
|
+
chainId,
|
|
1871
|
+
amount,
|
|
1872
|
+
asset,
|
|
1873
|
+
effectiveCallback,
|
|
1874
|
+
depositCallback
|
|
1510
1875
|
);
|
|
1511
1876
|
}
|
|
1512
1877
|
const totalAmount = BigInt(amount);
|
|
@@ -1536,26 +1901,77 @@ var OwneySDK = class {
|
|
|
1536
1901
|
);
|
|
1537
1902
|
}
|
|
1538
1903
|
const agentResults = {};
|
|
1539
|
-
const agentErrors = {};
|
|
1540
1904
|
for (const { agent, amount: agentAmount } of agentAmounts) {
|
|
1905
|
+
agentResults[agent.id] = await this.depositWithFallback(
|
|
1906
|
+
agent,
|
|
1907
|
+
state,
|
|
1908
|
+
chainId,
|
|
1909
|
+
agentAmount,
|
|
1910
|
+
asset,
|
|
1911
|
+
effectiveCallback,
|
|
1912
|
+
depositCallback
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
return { agentResults };
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
1919
|
+
* two independent auto-recovery mechanisms:
|
|
1920
|
+
*
|
|
1921
|
+
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
1922
|
+
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
1923
|
+
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
1924
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
1925
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
1926
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
1927
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
1928
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
1929
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
1930
|
+
* them to pay for a different one via the fallback below.
|
|
1931
|
+
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
1932
|
+
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
1933
|
+
* and retry once with `undefined`, falling through to a user-paid
|
|
1934
|
+
* native deposit. This still applies after a successful Permit2
|
|
1935
|
+
* approval retry (the retried sponsored attempt can itself hit a safe
|
|
1936
|
+
* sponsor failure).
|
|
1937
|
+
*
|
|
1938
|
+
* Any other error — an app-supplied callback's own failure in particular —
|
|
1939
|
+
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
1940
|
+
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
1941
|
+
*/
|
|
1942
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
1943
|
+
const attempt = (callback) => withFailureReporting(
|
|
1944
|
+
this.apiKey,
|
|
1945
|
+
agent.id,
|
|
1946
|
+
() => agent.deposit(
|
|
1947
|
+
state,
|
|
1948
|
+
chainId,
|
|
1949
|
+
amount,
|
|
1950
|
+
asset,
|
|
1951
|
+
callback
|
|
1952
|
+
)
|
|
1953
|
+
);
|
|
1954
|
+
let approvalAttempted = false;
|
|
1955
|
+
for (; ; ) {
|
|
1541
1956
|
try {
|
|
1542
|
-
|
|
1543
|
-
this.apiKey,
|
|
1544
|
-
agent.id,
|
|
1545
|
-
() => agent.deposit(state, chainId, agentAmount, effectiveCallback)
|
|
1546
|
-
);
|
|
1957
|
+
return await attempt(effectiveCallback);
|
|
1547
1958
|
} catch (error) {
|
|
1548
|
-
|
|
1959
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
1960
|
+
approvalAttempted = true;
|
|
1961
|
+
console.warn(
|
|
1962
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
1963
|
+
);
|
|
1964
|
+
await this.approvePermit2();
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
|
|
1968
|
+
console.warn(
|
|
1969
|
+
`[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
|
|
1970
|
+
error
|
|
1971
|
+
);
|
|
1972
|
+
return attempt(void 0);
|
|
1549
1973
|
}
|
|
1550
1974
|
}
|
|
1551
|
-
if (Object.keys(agentResults).length === 0) {
|
|
1552
|
-
throw new OwneyError(
|
|
1553
|
-
"DEPOSIT_ALL_FAILED",
|
|
1554
|
-
`All ${agentAmounts.length} agent deposit(s) failed.`,
|
|
1555
|
-
{ agentErrors }
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1558
|
-
return Object.keys(agentErrors).length > 0 ? { agentResults, agentErrors } : { agentResults };
|
|
1559
1975
|
}
|
|
1560
1976
|
getMinDepositAmount(agent, chainId, asset) {
|
|
1561
1977
|
const chainAssets = agent.supportedAssets.find(
|
|
@@ -1565,8 +1981,7 @@ var OwneySDK = class {
|
|
|
1565
1981
|
return BigInt(assetEntry?.minDepositAmount ?? "0");
|
|
1566
1982
|
}
|
|
1567
1983
|
splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
|
|
1568
|
-
if (agents.length === 0)
|
|
1569
|
-
return [];
|
|
1984
|
+
if (agents.length === 0) return [];
|
|
1570
1985
|
const perAgent = totalAmount / BigInt(agents.length);
|
|
1571
1986
|
const remainder = totalAmount % BigInt(agents.length);
|
|
1572
1987
|
const splits = agents.map((agent, i) => ({
|
|
@@ -1590,10 +2005,8 @@ var OwneySDK = class {
|
|
|
1590
2005
|
async validateMinDepositAmount(agent, state, chainId, asset, amount) {
|
|
1591
2006
|
const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
|
|
1592
2007
|
const parsedAmount = BigInt(amount);
|
|
1593
|
-
if (parsedAmount >= minDepositAmount)
|
|
1594
|
-
|
|
1595
|
-
if (await this.hasExistingBalance(agent, state, chainId, asset))
|
|
1596
|
-
return;
|
|
2008
|
+
if (parsedAmount >= minDepositAmount) return;
|
|
2009
|
+
if (await this.hasExistingBalance(agent, state, chainId, asset)) return;
|
|
1597
2010
|
throw new OwneyError(
|
|
1598
2011
|
"DEPOSIT_AMOUNT_BELOW_MINIMUM",
|
|
1599
2012
|
`Amount "${amount}" is below the required minimum deposit "${minDepositAmount.toString()}" for agent "${agent.id}" on chain ${chainId} for asset "${asset}".`,
|
|
@@ -1656,34 +2069,6 @@ var OwneySDK = class {
|
|
|
1656
2069
|
}
|
|
1657
2070
|
return eligible;
|
|
1658
2071
|
}
|
|
1659
|
-
/**
|
|
1660
|
-
* Agent ids the routing API provisioned for this org that support the given
|
|
1661
|
-
* chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
|
|
1662
|
-
* surfliquid first). Returns `[]` when the org has no compatible agent — never
|
|
1663
|
-
* throws on an empty org. Loads agent keys on first call (apiKey only, no
|
|
1664
|
-
* wallet), so the UI can resolve which agent to use before the user connects.
|
|
1665
|
-
*
|
|
1666
|
-
* This is the source of truth for agent availability: an agent appears here
|
|
1667
|
-
* iff the routing API returned its key. No per-app feature flags.
|
|
1668
|
-
*/
|
|
1669
|
-
async getEligibleAgentIds(chainId, asset) {
|
|
1670
|
-
try {
|
|
1671
|
-
await this.ensureAgentsInitialized();
|
|
1672
|
-
} catch (error) {
|
|
1673
|
-
if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
|
|
1674
|
-
return [];
|
|
1675
|
-
}
|
|
1676
|
-
throw error;
|
|
1677
|
-
}
|
|
1678
|
-
return [...this.agents.values()].filter((agent) => {
|
|
1679
|
-
const chainAssets = agent.supportedAssets.find(
|
|
1680
|
-
(sa) => sa.chainId === chainId
|
|
1681
|
-
);
|
|
1682
|
-
return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
|
|
1683
|
-
}).map((agent) => agent.id).sort(
|
|
1684
|
-
(a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
|
|
1685
|
-
);
|
|
1686
|
-
}
|
|
1687
2072
|
// --- Fund operations ---
|
|
1688
2073
|
/**
|
|
1689
2074
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
@@ -1719,17 +2104,9 @@ var OwneySDK = class {
|
|
|
1719
2104
|
}
|
|
1720
2105
|
const eligibleAgents = this.getEligibleAgents(chainId, asset);
|
|
1721
2106
|
if (!amount) {
|
|
1722
|
-
const aggregated2 = await this.getBalances();
|
|
1723
|
-
const funded = projectAgentBalancesForAsset(
|
|
1724
|
-
eligibleAgents,
|
|
1725
|
-
aggregated2.agentBalances,
|
|
1726
|
-
chainId,
|
|
1727
|
-
asset,
|
|
1728
|
-
assetInfo.decimals
|
|
1729
|
-
).filter((b) => b.balance > 0n);
|
|
1730
2107
|
const results2 = {};
|
|
1731
2108
|
const agentErrors2 = {};
|
|
1732
|
-
for (const
|
|
2109
|
+
for (const agent of eligibleAgents) {
|
|
1733
2110
|
try {
|
|
1734
2111
|
results2[agent.id] = await agent.withdraw(state, chainId, token);
|
|
1735
2112
|
} catch (err) {
|
|
@@ -1746,10 +2123,9 @@ var OwneySDK = class {
|
|
|
1746
2123
|
{ asset, agentErrors: agentErrors2 }
|
|
1747
2124
|
);
|
|
1748
2125
|
}
|
|
1749
|
-
const totalWithdrawn = funded.filter(({ agent }) => results2[agent.id] !== void 0).reduce((sum, { balance }) => sum + balance, 0n);
|
|
1750
2126
|
return {
|
|
1751
2127
|
agentResult: results2,
|
|
1752
|
-
totalWithdrawn:
|
|
2128
|
+
totalWithdrawn: sumWithdrawnAmount(results2).toString()
|
|
1753
2129
|
};
|
|
1754
2130
|
}
|
|
1755
2131
|
const requested = BigInt(amount);
|
|
@@ -1794,8 +2170,7 @@ var OwneySDK = class {
|
|
|
1794
2170
|
const agentErrors = {};
|
|
1795
2171
|
for (let i = 0; i < plans.length; i++) {
|
|
1796
2172
|
const p = plans[i];
|
|
1797
|
-
if (p.planned === 0n)
|
|
1798
|
-
continue;
|
|
2173
|
+
if (p.planned === 0n) continue;
|
|
1799
2174
|
try {
|
|
1800
2175
|
results[p.agent.id] = await p.agent.withdraw(
|
|
1801
2176
|
state,
|
|
@@ -1929,19 +2304,30 @@ var OwneySDK = class {
|
|
|
1929
2304
|
*/
|
|
1930
2305
|
async getAccountApy({
|
|
1931
2306
|
agentId,
|
|
1932
|
-
days
|
|
2307
|
+
days,
|
|
2308
|
+
tokenSymbol
|
|
1933
2309
|
}) {
|
|
1934
2310
|
const state = this.requireState();
|
|
1935
2311
|
const chainId = this.requireChainId();
|
|
1936
2312
|
if (agentId) {
|
|
1937
|
-
return this.getAgent(agentId).getAccountApy(
|
|
2313
|
+
return this.getAgent(agentId).getAccountApy(
|
|
2314
|
+
state,
|
|
2315
|
+
chainId,
|
|
2316
|
+
days,
|
|
2317
|
+
tokenSymbol
|
|
2318
|
+
);
|
|
1938
2319
|
}
|
|
1939
2320
|
const activeAgents = this.getActiveAgents();
|
|
1940
2321
|
const entries = [...activeAgents.entries()];
|
|
1941
2322
|
const [apyResults, balanceResults] = await Promise.all([
|
|
1942
2323
|
Promise.all(
|
|
1943
2324
|
entries.map(async ([id, agent]) => {
|
|
1944
|
-
const apy = await agent.getAccountApy(
|
|
2325
|
+
const apy = await agent.getAccountApy(
|
|
2326
|
+
state,
|
|
2327
|
+
chainId,
|
|
2328
|
+
days,
|
|
2329
|
+
tokenSymbol
|
|
2330
|
+
);
|
|
1945
2331
|
return [id, apy];
|
|
1946
2332
|
})
|
|
1947
2333
|
),
|
|
@@ -1964,15 +2350,13 @@ var OwneySDK = class {
|
|
|
1964
2350
|
let relevantBalance = 0;
|
|
1965
2351
|
for (const id of Object.keys(results)) {
|
|
1966
2352
|
const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
|
|
1967
|
-
if (agentApy === 0)
|
|
1968
|
-
continue;
|
|
2353
|
+
if (agentApy === 0) continue;
|
|
1969
2354
|
relevantBalance += balances[id];
|
|
1970
2355
|
}
|
|
1971
2356
|
if (relevantBalance > 0) {
|
|
1972
2357
|
for (const id of Object.keys(results)) {
|
|
1973
2358
|
const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
|
|
1974
|
-
if (agentApy === 0)
|
|
1975
|
-
continue;
|
|
2359
|
+
if (agentApy === 0) continue;
|
|
1976
2360
|
const weight = balances[id] / relevantBalance;
|
|
1977
2361
|
totalApy += agentApy * weight;
|
|
1978
2362
|
}
|
|
@@ -2027,8 +2411,7 @@ var OwneySDK = class {
|
|
|
2027
2411
|
const nextCursorMap = {};
|
|
2028
2412
|
let hasMore = false;
|
|
2029
2413
|
for (const { agentId: id, page } of perAgent) {
|
|
2030
|
-
if (!page)
|
|
2031
|
-
continue;
|
|
2414
|
+
if (!page) continue;
|
|
2032
2415
|
merged.push(...page.data);
|
|
2033
2416
|
if (page.hasMore && page.nextCursor) {
|
|
2034
2417
|
nextCursorMap[id] = page.nextCursor;
|
|
@@ -2037,16 +2420,12 @@ var OwneySDK = class {
|
|
|
2037
2420
|
}
|
|
2038
2421
|
merged.sort((a, b) => {
|
|
2039
2422
|
const dateDiff = new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
2040
|
-
if (dateDiff !== 0)
|
|
2041
|
-
|
|
2042
|
-
if (a.agent !== b.agent)
|
|
2043
|
-
return a.agent < b.agent ? -1 : 1;
|
|
2423
|
+
if (dateDiff !== 0) return dateDiff;
|
|
2424
|
+
if (a.agent !== b.agent) return a.agent < b.agent ? -1 : 1;
|
|
2044
2425
|
const aTx = a.transactions?.[0]?.txHashes?.[0] ?? "";
|
|
2045
2426
|
const bTx = b.transactions?.[0]?.txHashes?.[0] ?? "";
|
|
2046
|
-
if (aTx !== bTx)
|
|
2047
|
-
|
|
2048
|
-
if (a.action !== b.action)
|
|
2049
|
-
return a.action < b.action ? -1 : 1;
|
|
2427
|
+
if (aTx !== bTx) return aTx < bTx ? -1 : 1;
|
|
2428
|
+
if (a.action !== b.action) return a.action < b.action ? -1 : 1;
|
|
2050
2429
|
return 0;
|
|
2051
2430
|
});
|
|
2052
2431
|
return {
|
|
@@ -2081,6 +2460,67 @@ var OwneySDK = class {
|
|
|
2081
2460
|
agentUserProfile: results
|
|
2082
2461
|
};
|
|
2083
2462
|
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Ensure the given asset uses backend protocol auto-selection for the active
|
|
2465
|
+
* chain's account. No-op (returns false) for agents that don't support it.
|
|
2466
|
+
* @param asset - "USDC" or "WETH".
|
|
2467
|
+
* @param agentId - Agent to target. Defaults to "zyfai".
|
|
2468
|
+
* @returns whether a profile write occurred.
|
|
2469
|
+
*/
|
|
2470
|
+
async ensureAutoSelectProtocols(asset, agentId = "zyfai") {
|
|
2471
|
+
const state = this.requireState();
|
|
2472
|
+
const chainId = this.requireChainId();
|
|
2473
|
+
const agent = this.getAgent(agentId);
|
|
2474
|
+
if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
|
|
2475
|
+
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
2479
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
2480
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
2481
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
2482
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
2483
|
+
* @returns the approval transaction hash.
|
|
2484
|
+
*/
|
|
2485
|
+
async approvePermit2(asset = "WETH") {
|
|
2486
|
+
void asset;
|
|
2487
|
+
const state = this.requireState();
|
|
2488
|
+
const chainId = this.requireChainId();
|
|
2489
|
+
this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
|
|
2490
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
2491
|
+
if (!token) {
|
|
2492
|
+
throw new OwneyError(
|
|
2493
|
+
"CHAIN_UNSUPPORTED",
|
|
2494
|
+
`No sponsored WETH on chain ${chainId}`
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
const provider = this.requireConnectedProvider();
|
|
2498
|
+
const wallet = createWalletClient({
|
|
2499
|
+
account: state.walletAddress,
|
|
2500
|
+
chain: VIEM_CHAIN2[chainId],
|
|
2501
|
+
transport: custom(provider)
|
|
2502
|
+
});
|
|
2503
|
+
const hash = await wallet.writeContract({
|
|
2504
|
+
address: token,
|
|
2505
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2506
|
+
functionName: "approve",
|
|
2507
|
+
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
2508
|
+
account: state.walletAddress,
|
|
2509
|
+
chain: VIEM_CHAIN2[chainId]
|
|
2510
|
+
});
|
|
2511
|
+
const publicClient = createPublicClient2({
|
|
2512
|
+
chain: VIEM_CHAIN2[chainId],
|
|
2513
|
+
transport: custom(provider)
|
|
2514
|
+
});
|
|
2515
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
2516
|
+
hash,
|
|
2517
|
+
confirmations: 1
|
|
2518
|
+
});
|
|
2519
|
+
if (receipt.status !== "success") {
|
|
2520
|
+
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
2521
|
+
}
|
|
2522
|
+
return hash;
|
|
2523
|
+
}
|
|
2084
2524
|
// --- Discovery (no wallet required) ---
|
|
2085
2525
|
/**
|
|
2086
2526
|
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|
|
@@ -2186,8 +2626,7 @@ import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
|
2186
2626
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
2187
2627
|
var KEY_PREFIX2 = "owney.siwx.session";
|
|
2188
2628
|
var storage2 = () => {
|
|
2189
|
-
if (typeof window === "undefined")
|
|
2190
|
-
return null;
|
|
2629
|
+
if (typeof window === "undefined") return null;
|
|
2191
2630
|
try {
|
|
2192
2631
|
return window.localStorage;
|
|
2193
2632
|
} catch {
|
|
@@ -2197,11 +2636,9 @@ var storage2 = () => {
|
|
|
2197
2636
|
var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
|
|
2198
2637
|
var readSiwxSession = (address, chainId) => {
|
|
2199
2638
|
const store = storage2();
|
|
2200
|
-
if (!store)
|
|
2201
|
-
return null;
|
|
2639
|
+
if (!store) return null;
|
|
2202
2640
|
const raw = store.getItem(buildKey2(address, chainId));
|
|
2203
|
-
if (!raw)
|
|
2204
|
-
return null;
|
|
2641
|
+
if (!raw) return null;
|
|
2205
2642
|
try {
|
|
2206
2643
|
return JSON.parse(raw);
|
|
2207
2644
|
} catch {
|
|
@@ -2211,8 +2648,7 @@ var readSiwxSession = (address, chainId) => {
|
|
|
2211
2648
|
};
|
|
2212
2649
|
var writeSiwxSession = (address, chainId, session) => {
|
|
2213
2650
|
const store = storage2();
|
|
2214
|
-
if (!store)
|
|
2215
|
-
return;
|
|
2651
|
+
if (!store) return;
|
|
2216
2652
|
try {
|
|
2217
2653
|
store.setItem(buildKey2(address, chainId), JSON.stringify(session));
|
|
2218
2654
|
} catch {
|
|
@@ -2220,8 +2656,7 @@ var writeSiwxSession = (address, chainId, session) => {
|
|
|
2220
2656
|
};
|
|
2221
2657
|
var clearSiwxSession = (address, chainId) => {
|
|
2222
2658
|
const store = storage2();
|
|
2223
|
-
if (!store)
|
|
2224
|
-
return;
|
|
2659
|
+
if (!store) return;
|
|
2225
2660
|
try {
|
|
2226
2661
|
store.removeItem(buildKey2(address, chainId));
|
|
2227
2662
|
} catch {
|
|
@@ -2284,12 +2719,10 @@ function buildSIWXConfig(deps) {
|
|
|
2284
2719
|
const jwt = readSession(address, id);
|
|
2285
2720
|
const stored = readSiwxSession(address, id);
|
|
2286
2721
|
if (!jwt) {
|
|
2287
|
-
if (stored)
|
|
2288
|
-
clearSiwxSession(address, id);
|
|
2722
|
+
if (stored) clearSiwxSession(address, id);
|
|
2289
2723
|
return [];
|
|
2290
2724
|
}
|
|
2291
|
-
if (!stored)
|
|
2292
|
-
return [];
|
|
2725
|
+
if (!stored) return [];
|
|
2293
2726
|
const session = {
|
|
2294
2727
|
// `stored.data.chainId` is a plain string in the cache (kept AppKit-free);
|
|
2295
2728
|
// it always holds a valid CaipNetworkId here, so narrow at the boundary.
|