@owney/sdk 0.6.0-beta.0 → 0.6.0-beta.2
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 +20 -67
- package/dist/index.cjs +480 -67
- package/dist/index.d.cts +145 -104
- package/dist/index.d.ts +145 -104
- package/dist/index.js +480 -63
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,13 +32,15 @@ module.exports = __toCommonJS(src_exports);
|
|
|
32
32
|
|
|
33
33
|
// src/agents/zyfai/zyfai.agent.ts
|
|
34
34
|
var import_sdk = require("@zyfai/sdk");
|
|
35
|
+
var import_viem = require("viem");
|
|
36
|
+
var import_chains = require("viem/chains");
|
|
35
37
|
|
|
36
38
|
// src/types/config.ts
|
|
37
39
|
var P_SUPPORTED_CHAINS = {
|
|
38
40
|
chainIds: [8453, 42161],
|
|
39
41
|
chains: ["BASE", "ARBITRUM"]
|
|
40
42
|
};
|
|
41
|
-
var SUPPORTED_TOKENS = ["USDC", "USDT"];
|
|
43
|
+
var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
|
|
42
44
|
var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
|
|
43
45
|
var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
|
|
44
46
|
var SupportedAssets = [
|
|
@@ -59,6 +61,18 @@ var SupportedAssets = [
|
|
|
59
61
|
symbol: "USDT",
|
|
60
62
|
chainId: 42161,
|
|
61
63
|
decimals: 6
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
address: `0x4200000000000000000000000000000000000006`,
|
|
67
|
+
symbol: "WETH",
|
|
68
|
+
chainId: 8453,
|
|
69
|
+
decimals: 18
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`,
|
|
73
|
+
symbol: "WETH",
|
|
74
|
+
chainId: 42161,
|
|
75
|
+
decimals: 18
|
|
62
76
|
}
|
|
63
77
|
];
|
|
64
78
|
|
|
@@ -157,6 +171,18 @@ var CHAIN_ID_TO_NAME = {
|
|
|
157
171
|
8453: "BASE",
|
|
158
172
|
42161: "ARBITRUM"
|
|
159
173
|
};
|
|
174
|
+
var NAME_TO_CHAIN_ID = Object.entries(
|
|
175
|
+
CHAIN_ID_TO_NAME
|
|
176
|
+
).reduce((acc, [id, name]) => {
|
|
177
|
+
acc[name.toLowerCase()] = Number(id);
|
|
178
|
+
return acc;
|
|
179
|
+
}, {});
|
|
180
|
+
function resolveChainId(chain) {
|
|
181
|
+
const asNum = Number(chain);
|
|
182
|
+
if (Number.isFinite(asNum) && asNum > 0)
|
|
183
|
+
return asNum;
|
|
184
|
+
return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
|
|
185
|
+
}
|
|
160
186
|
function mapBalances(raw, _chainId, smartWallet) {
|
|
161
187
|
const portfolio = raw.portfolio;
|
|
162
188
|
const portfolioByChain = portfolio.portfolioByChain ?? {};
|
|
@@ -177,10 +203,27 @@ function mapBalances(raw, _chainId, smartWallet) {
|
|
|
177
203
|
});
|
|
178
204
|
}
|
|
179
205
|
}
|
|
206
|
+
const positions = (portfolio.positions ?? []).map((p) => ({
|
|
207
|
+
chain: p.chain ?? "",
|
|
208
|
+
protocol: p.protocol_name ?? p.protocol_id ?? "",
|
|
209
|
+
protocolId: p.protocol_id,
|
|
210
|
+
pool: p.pool,
|
|
211
|
+
asset: p.token_symbol ?? "",
|
|
212
|
+
// Position amount is hex-encoded base units (like the token balances
|
|
213
|
+
// above). Supported assets (USDC/USDT) are 6-decimal — hexToDecimal's
|
|
214
|
+
// default — so no per-token decimals lookup is needed.
|
|
215
|
+
amount: String(hexToDecimal(p.amount || "0")),
|
|
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
|
+
}));
|
|
180
222
|
return {
|
|
181
223
|
smartWallet,
|
|
182
224
|
totalBalance: String(totalBalance),
|
|
183
|
-
tokens
|
|
225
|
+
tokens,
|
|
226
|
+
positions
|
|
184
227
|
};
|
|
185
228
|
}
|
|
186
229
|
function sumTokenValues(tokens) {
|
|
@@ -239,6 +282,14 @@ function mapApyHistory(raw) {
|
|
|
239
282
|
apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain)
|
|
240
283
|
};
|
|
241
284
|
}
|
|
285
|
+
function splitOpportunity(opportunity) {
|
|
286
|
+
const match = opportunity.match(/^(.*?)\s*\(([^)]*)\)\s*$/);
|
|
287
|
+
if (match) {
|
|
288
|
+
const pool = match[2].trim();
|
|
289
|
+
return { protocol: match[1].trim(), pool: pool || void 0 };
|
|
290
|
+
}
|
|
291
|
+
return { protocol: opportunity.trim() };
|
|
292
|
+
}
|
|
242
293
|
function mapEntries(rawEntries, chainId) {
|
|
243
294
|
const actionMap = {
|
|
244
295
|
"Top up wallet": "Top up",
|
|
@@ -258,21 +309,26 @@ function mapEntries(rawEntries, chainId) {
|
|
|
258
309
|
);
|
|
259
310
|
oldApy = Number(rawLog.oldApy);
|
|
260
311
|
newApy = Number(rawLog.newApy);
|
|
312
|
+
const from = splitOpportunity(rawLog.oldOpportunity);
|
|
313
|
+
const to = splitOpportunity(rawLog.newOpportunity);
|
|
261
314
|
rebalanceLog = [
|
|
262
315
|
{
|
|
263
|
-
fromProtocol:
|
|
264
|
-
toProtocol:
|
|
316
|
+
fromProtocol: from.protocol,
|
|
317
|
+
toProtocol: to.protocol,
|
|
318
|
+
fromPool: from.pool,
|
|
319
|
+
toPool: to.pool,
|
|
265
320
|
tokenSymbol: matched?.token_symbol ?? "",
|
|
266
321
|
amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
|
|
267
322
|
status: "success"
|
|
268
323
|
}
|
|
269
324
|
];
|
|
270
325
|
}
|
|
326
|
+
const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
|
|
271
327
|
const rawAmount = entry?.positions?.[0]?.amount;
|
|
272
|
-
const hasAmount = typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
|
|
328
|
+
const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
|
|
273
329
|
return {
|
|
274
330
|
agent: "zyfai",
|
|
275
|
-
action:
|
|
331
|
+
action: mappedAction,
|
|
276
332
|
date: entry.date || "",
|
|
277
333
|
newApy: newApy === null ? null : String(newApy),
|
|
278
334
|
oldApy: oldApy === null ? null : String(oldApy),
|
|
@@ -288,13 +344,6 @@ function mapEntries(rawEntries, chainId) {
|
|
|
288
344
|
};
|
|
289
345
|
});
|
|
290
346
|
}
|
|
291
|
-
function mapAgentStatus(raw) {
|
|
292
|
-
return {
|
|
293
|
-
success: raw.success,
|
|
294
|
-
strategy: raw.strategy,
|
|
295
|
-
protocols: raw.protocols
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
347
|
function mapUserProfile(raw, userAddress) {
|
|
299
348
|
return {
|
|
300
349
|
address: userAddress,
|
|
@@ -330,6 +379,73 @@ function mapApyByStrategy(raw) {
|
|
|
330
379
|
detailedApys: hasDetails ? { apyPerAsset } : void 0
|
|
331
380
|
};
|
|
332
381
|
}
|
|
382
|
+
function computeAllocationApy(positions) {
|
|
383
|
+
let weightedSum = 0;
|
|
384
|
+
let totalValue = 0;
|
|
385
|
+
const buckets = {};
|
|
386
|
+
for (const p of positions) {
|
|
387
|
+
const apy = p.apy;
|
|
388
|
+
const value = Number(p.amount);
|
|
389
|
+
if (typeof apy !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
weightedSum += apy * value;
|
|
393
|
+
totalValue += value;
|
|
394
|
+
const chainId = resolveChainId(p.chain);
|
|
395
|
+
const token = p.asset;
|
|
396
|
+
if (!SUPPORTED_CHAIN_IDS.includes(chainId))
|
|
397
|
+
continue;
|
|
398
|
+
if (!SUPPORTED_TOKENS.includes(token))
|
|
399
|
+
continue;
|
|
400
|
+
const perAsset = buckets[chainId] ?? {};
|
|
401
|
+
const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
|
|
402
|
+
cell.weightedSum += apy * value;
|
|
403
|
+
cell.value += value;
|
|
404
|
+
perAsset[token] = cell;
|
|
405
|
+
buckets[chainId] = perAsset;
|
|
406
|
+
}
|
|
407
|
+
const apyByChainAndAsset = {};
|
|
408
|
+
for (const [chainKey, perAsset] of Object.entries(buckets)) {
|
|
409
|
+
const chainId = Number(chainKey);
|
|
410
|
+
const out = {};
|
|
411
|
+
for (const [token, cell] of Object.entries(perAsset ?? {})) {
|
|
412
|
+
if (cell && cell.value > 0) {
|
|
413
|
+
out[token] = cell.weightedSum / cell.value;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (Object.keys(out).length > 0)
|
|
417
|
+
apyByChainAndAsset[chainId] = out;
|
|
418
|
+
}
|
|
419
|
+
const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
|
|
420
|
+
return { totalApy, apyByChainAndAsset };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/agents/zyfai/zyfai.withdraw-amount.ts
|
|
424
|
+
var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
425
|
+
function topicToAddress(topic) {
|
|
426
|
+
return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
|
|
427
|
+
}
|
|
428
|
+
function extractWithdrawnAmount(logs, recipient, token, decimals) {
|
|
429
|
+
const wantRecipient = recipient.toLowerCase();
|
|
430
|
+
const wantToken = token.toLowerCase();
|
|
431
|
+
let total = 0n;
|
|
432
|
+
let matched = false;
|
|
433
|
+
for (const log of logs) {
|
|
434
|
+
if (log.address.toLowerCase() !== wantToken)
|
|
435
|
+
continue;
|
|
436
|
+
if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
|
|
437
|
+
continue;
|
|
438
|
+
if (topicToAddress(log.topics[2]) !== wantRecipient)
|
|
439
|
+
continue;
|
|
440
|
+
if (!log.data || log.data === "0x")
|
|
441
|
+
continue;
|
|
442
|
+
total += BigInt(log.data);
|
|
443
|
+
matched = true;
|
|
444
|
+
}
|
|
445
|
+
if (!matched)
|
|
446
|
+
return void 0;
|
|
447
|
+
return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
|
|
448
|
+
}
|
|
333
449
|
|
|
334
450
|
// src/agents/zyfai/zyfai.history-cursor.ts
|
|
335
451
|
var InvalidHistoryCursorError = class extends Error {
|
|
@@ -429,6 +545,11 @@ var DEFAULT_ZYFAI_RPC_URLS = {
|
|
|
429
545
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
430
546
|
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
431
547
|
};
|
|
548
|
+
var WETH_ADDRESS_BY_CHAIN = {
|
|
549
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
550
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
|
551
|
+
};
|
|
552
|
+
var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum };
|
|
432
553
|
var ZyfaiAgent = class {
|
|
433
554
|
id = "zyfai";
|
|
434
555
|
supportedChainIds = [8453, 42161];
|
|
@@ -436,12 +557,18 @@ var ZyfaiAgent = class {
|
|
|
436
557
|
{
|
|
437
558
|
chainId: 8453,
|
|
438
559
|
chain: "BASE",
|
|
439
|
-
assets: [
|
|
560
|
+
assets: [
|
|
561
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
562
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
563
|
+
]
|
|
440
564
|
},
|
|
441
565
|
{
|
|
442
566
|
chainId: 42161,
|
|
443
567
|
chain: "ARBITRUM",
|
|
444
|
-
assets: [
|
|
568
|
+
assets: [
|
|
569
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
570
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
571
|
+
]
|
|
445
572
|
}
|
|
446
573
|
];
|
|
447
574
|
sdk;
|
|
@@ -449,13 +576,83 @@ var ZyfaiAgent = class {
|
|
|
449
576
|
connectedChainId = null;
|
|
450
577
|
smartWalletCache = /* @__PURE__ */ new Map();
|
|
451
578
|
connectAuthPromise = null;
|
|
579
|
+
rpcUrls;
|
|
580
|
+
publicClients = /* @__PURE__ */ new Map();
|
|
581
|
+
// txHash -> resolved withdrawn amount. Receipts are immutable, so a
|
|
582
|
+
// successful decode is cached forever; failures are NOT cached so a transient
|
|
583
|
+
// RPC error retries on the next fetch.
|
|
584
|
+
withdrawAmountCache = /* @__PURE__ */ new Map();
|
|
452
585
|
constructor(apiKey, rpcUrls) {
|
|
586
|
+
this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
|
|
453
587
|
this.sdk = new import_sdk.ZyfaiSDK({
|
|
454
588
|
apiKey,
|
|
455
|
-
rpcUrls: rpcUrls
|
|
589
|
+
rpcUrls: this.rpcUrls
|
|
456
590
|
});
|
|
457
591
|
this.installAuthCache();
|
|
458
592
|
}
|
|
593
|
+
getPublicClient(chainId) {
|
|
594
|
+
const cached = this.publicClients.get(chainId);
|
|
595
|
+
if (cached)
|
|
596
|
+
return cached;
|
|
597
|
+
const client = (0, import_viem.createPublicClient)({
|
|
598
|
+
chain: VIEM_CHAIN[chainId],
|
|
599
|
+
transport: (0, import_viem.http)(this.rpcUrls[chainId])
|
|
600
|
+
});
|
|
601
|
+
this.publicClients.set(chainId, client);
|
|
602
|
+
return client;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Fills in the true withdrawn amount for `Withdraw` entries by decoding the
|
|
606
|
+
* USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
|
|
607
|
+
* payload only reports the *remaining* position for a partial withdraw (see
|
|
608
|
+
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
609
|
+
* balance instead of the amount withdrawn.
|
|
610
|
+
*
|
|
611
|
+
* Mutates `entries` in place. Runs all lookups concurrently and is tolerant
|
|
612
|
+
* of RPC failures: an entry whose amount can't be resolved keeps
|
|
613
|
+
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
614
|
+
* rather than a wrong number.
|
|
615
|
+
*/
|
|
616
|
+
async resolveWithdrawnAmounts(entries, chainId, recipient) {
|
|
617
|
+
const asset = SupportedAssets.find(
|
|
618
|
+
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
619
|
+
);
|
|
620
|
+
if (!asset)
|
|
621
|
+
return;
|
|
622
|
+
const withdrawals = entries.filter(
|
|
623
|
+
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
624
|
+
);
|
|
625
|
+
if (withdrawals.length === 0)
|
|
626
|
+
return;
|
|
627
|
+
const client = this.getPublicClient(chainId);
|
|
628
|
+
await Promise.all(
|
|
629
|
+
withdrawals.map(async (entry) => {
|
|
630
|
+
const tx = entry.transactions[0];
|
|
631
|
+
const txHash = tx.txHashes[0];
|
|
632
|
+
const cached = this.withdrawAmountCache.get(txHash);
|
|
633
|
+
if (cached !== void 0) {
|
|
634
|
+
tx.amount = cached;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
try {
|
|
638
|
+
const receipt = await client.getTransactionReceipt({
|
|
639
|
+
hash: txHash
|
|
640
|
+
});
|
|
641
|
+
const amount = extractWithdrawnAmount(
|
|
642
|
+
receipt.logs,
|
|
643
|
+
recipient,
|
|
644
|
+
asset.address,
|
|
645
|
+
asset.decimals
|
|
646
|
+
);
|
|
647
|
+
if (amount !== void 0) {
|
|
648
|
+
this.withdrawAmountCache.set(txHash, amount);
|
|
649
|
+
tx.amount = amount;
|
|
650
|
+
}
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
}
|
|
459
656
|
/**
|
|
460
657
|
* Patches `sdk.authenticateUser` so a fresh page can reuse the JWT from a
|
|
461
658
|
* prior session instead of triggering a new SIWE prompt. Runs inside
|
|
@@ -607,7 +804,7 @@ var ZyfaiAgent = class {
|
|
|
607
804
|
throw error;
|
|
608
805
|
}
|
|
609
806
|
}
|
|
610
|
-
async deposit(state, chainId, amount, depositCallback) {
|
|
807
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
611
808
|
try {
|
|
612
809
|
await this.connectFull(state, chainId);
|
|
613
810
|
const validChainId = isValidChainId(chainId);
|
|
@@ -621,13 +818,15 @@ var ZyfaiAgent = class {
|
|
|
621
818
|
validChainId,
|
|
622
819
|
amount
|
|
623
820
|
);
|
|
624
|
-
|
|
821
|
+
const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
|
|
822
|
+
await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
|
|
625
823
|
return { txHash, smartWallet: wallet.address, amount };
|
|
626
824
|
}
|
|
627
825
|
const raw = await this.sdk.depositFunds(
|
|
628
826
|
this.getAddress(),
|
|
629
827
|
validChainId,
|
|
630
|
-
amount
|
|
828
|
+
amount,
|
|
829
|
+
asset
|
|
631
830
|
);
|
|
632
831
|
return mapDeposit(raw);
|
|
633
832
|
} catch (error) {
|
|
@@ -706,23 +905,14 @@ var ZyfaiAgent = class {
|
|
|
706
905
|
toDate,
|
|
707
906
|
rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
|
|
708
907
|
}) : void 0;
|
|
908
|
+
const data = mapEntries(pageData, validChainId);
|
|
909
|
+
await this.resolveWithdrawnAmounts(data, validChainId, state.walletAddress);
|
|
709
910
|
return {
|
|
710
|
-
data
|
|
911
|
+
data,
|
|
711
912
|
hasMore,
|
|
712
913
|
nextCursor
|
|
713
914
|
};
|
|
714
915
|
}
|
|
715
|
-
// --- IAgent: Agent control ---
|
|
716
|
-
async pause(state, chainId) {
|
|
717
|
-
await this.connectAuth(state, chainId);
|
|
718
|
-
const raw = await this.sdk.pauseAgent();
|
|
719
|
-
return mapAgentStatus(raw);
|
|
720
|
-
}
|
|
721
|
-
async resume(state, chainId) {
|
|
722
|
-
await this.connectAuth(state, chainId);
|
|
723
|
-
const raw = await this.sdk.resumeAgent();
|
|
724
|
-
return mapAgentStatus(raw);
|
|
725
|
-
}
|
|
726
916
|
async getUserProfile(state, chainId) {
|
|
727
917
|
await this.connectAuth(state, chainId);
|
|
728
918
|
const validChainId = isValidChainId(chainId);
|
|
@@ -809,7 +999,7 @@ async function withFailureReporting(apiKey, agentType, fn) {
|
|
|
809
999
|
}
|
|
810
1000
|
|
|
811
1001
|
// src/lib/helpers/withdraw-helper.ts
|
|
812
|
-
var
|
|
1002
|
+
var import_viem2 = require("viem");
|
|
813
1003
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
814
1004
|
const target = asset.toUpperCase();
|
|
815
1005
|
return agents.map((agent) => {
|
|
@@ -819,7 +1009,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
819
1009
|
);
|
|
820
1010
|
if (!tokenBalance)
|
|
821
1011
|
return { agent, balance: 0n };
|
|
822
|
-
return { agent, balance: (0,
|
|
1012
|
+
return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
|
|
823
1013
|
});
|
|
824
1014
|
}
|
|
825
1015
|
function planProportionalShares(balances, requested, totalAvailable) {
|
|
@@ -934,6 +1124,130 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
934
1124
|
return out;
|
|
935
1125
|
}
|
|
936
1126
|
|
|
1127
|
+
// src/client.ts
|
|
1128
|
+
var import_viem4 = require("viem");
|
|
1129
|
+
var import_chains2 = require("viem/chains");
|
|
1130
|
+
|
|
1131
|
+
// src/lib/transfer-auth.ts
|
|
1132
|
+
var import_viem3 = require("viem");
|
|
1133
|
+
var ERC20_META_ABI = [
|
|
1134
|
+
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
1135
|
+
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
1136
|
+
];
|
|
1137
|
+
function buildTransferWithAuthorizationTypedData(input) {
|
|
1138
|
+
return {
|
|
1139
|
+
domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
|
|
1140
|
+
types: {
|
|
1141
|
+
TransferWithAuthorization: [
|
|
1142
|
+
{ name: "from", type: "address" },
|
|
1143
|
+
{ name: "to", type: "address" },
|
|
1144
|
+
{ name: "value", type: "uint256" },
|
|
1145
|
+
{ name: "validAfter", type: "uint256" },
|
|
1146
|
+
{ name: "validBefore", type: "uint256" },
|
|
1147
|
+
{ name: "nonce", type: "bytes32" }
|
|
1148
|
+
]
|
|
1149
|
+
},
|
|
1150
|
+
primaryType: "TransferWithAuthorization",
|
|
1151
|
+
message: input.message
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
async function readTokenMeta(publicClient, token) {
|
|
1155
|
+
const [tokenName, tokenVersion] = await Promise.all([
|
|
1156
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
|
|
1157
|
+
publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
|
|
1158
|
+
]);
|
|
1159
|
+
return { tokenName, tokenVersion };
|
|
1160
|
+
}
|
|
1161
|
+
function randomAuthNonce() {
|
|
1162
|
+
const bytes = new Uint8Array(32);
|
|
1163
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1164
|
+
return (0, import_viem3.bytesToHex)(bytes);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/lib/sponsor-client.ts
|
|
1168
|
+
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
1169
|
+
async function postSponsorTransferAuth(input) {
|
|
1170
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1171
|
+
let res;
|
|
1172
|
+
try {
|
|
1173
|
+
res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
1174
|
+
method: "POST",
|
|
1175
|
+
headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
|
|
1176
|
+
body: JSON.stringify(input.body)
|
|
1177
|
+
});
|
|
1178
|
+
} catch (networkError) {
|
|
1179
|
+
throw new OwneyError(
|
|
1180
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1181
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1182
|
+
{ cause: String(networkError) }
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
const text = await res.text();
|
|
1186
|
+
let parsed = null;
|
|
1187
|
+
try {
|
|
1188
|
+
parsed = JSON.parse(text);
|
|
1189
|
+
} catch {
|
|
1190
|
+
}
|
|
1191
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
1192
|
+
throw new OwneyError(
|
|
1193
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1194
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1195
|
+
{ statusCode: res.status, responseBody: text.slice(0, 500) }
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
return parsed.data;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/lib/sponsored-deposit.ts
|
|
1202
|
+
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
1203
|
+
function makeSponsoredDepositCallback(deps) {
|
|
1204
|
+
const post = deps.httpPost ?? postSponsorTransferAuth;
|
|
1205
|
+
return async (smartWallet, chainId, amount) => {
|
|
1206
|
+
const cid = chainId;
|
|
1207
|
+
const token = deps.tokenAddressByChain[cid];
|
|
1208
|
+
if (!token) {
|
|
1209
|
+
throw new OwneyError(
|
|
1210
|
+
"CHAIN_UNSUPPORTED",
|
|
1211
|
+
`No sponsored token configured for chain ${chainId}`
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
const pub = deps.getPublicClient(cid);
|
|
1215
|
+
const wallet = deps.getWalletClient(cid);
|
|
1216
|
+
const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
|
|
1217
|
+
const validAfter = 0n;
|
|
1218
|
+
const validBefore = BigInt(Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS);
|
|
1219
|
+
const nonce = randomAuthNonce();
|
|
1220
|
+
const typedData = buildTransferWithAuthorizationTypedData({
|
|
1221
|
+
token,
|
|
1222
|
+
chainId: cid,
|
|
1223
|
+
tokenName,
|
|
1224
|
+
tokenVersion,
|
|
1225
|
+
message: { from: deps.ownerAddress, to: smartWallet, value: BigInt(amount), validAfter, validBefore, nonce }
|
|
1226
|
+
});
|
|
1227
|
+
const authSignature = await wallet.signTypedData(
|
|
1228
|
+
{ account: deps.ownerAddress, ...typedData }
|
|
1229
|
+
);
|
|
1230
|
+
const result = await post({
|
|
1231
|
+
baseUrl: deps.baseUrl,
|
|
1232
|
+
apiKey: deps.apiKey,
|
|
1233
|
+
body: {
|
|
1234
|
+
chainId: cid,
|
|
1235
|
+
token,
|
|
1236
|
+
from: deps.ownerAddress,
|
|
1237
|
+
to: smartWallet,
|
|
1238
|
+
value: amount,
|
|
1239
|
+
validAfter: validAfter.toString(),
|
|
1240
|
+
validBefore: validBefore.toString(),
|
|
1241
|
+
nonce,
|
|
1242
|
+
authSignature,
|
|
1243
|
+
tokenName,
|
|
1244
|
+
tokenVersion
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
return result.txHash;
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
937
1251
|
// src/client.ts
|
|
938
1252
|
function encodeMultiAgentCursor(map) {
|
|
939
1253
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
@@ -955,6 +1269,14 @@ function decodeMultiAgentCursor(token) {
|
|
|
955
1269
|
}
|
|
956
1270
|
return parsed;
|
|
957
1271
|
}
|
|
1272
|
+
var SPONSORED_USDC_BY_CHAIN = {
|
|
1273
|
+
8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
|
|
1274
|
+
42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
|
|
1275
|
+
};
|
|
1276
|
+
var VIEM_CHAIN2 = {
|
|
1277
|
+
8453: import_chains2.base,
|
|
1278
|
+
42161: import_chains2.arbitrum
|
|
1279
|
+
};
|
|
958
1280
|
var OwneySDK = class {
|
|
959
1281
|
agents = /* @__PURE__ */ new Map();
|
|
960
1282
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -967,6 +1289,7 @@ var OwneySDK = class {
|
|
|
967
1289
|
state = null;
|
|
968
1290
|
apiKey;
|
|
969
1291
|
zyfaiRpcUrls;
|
|
1292
|
+
cachedSponsoredCallback = null;
|
|
970
1293
|
initializingAgentsPromise = null;
|
|
971
1294
|
constructor(config) {
|
|
972
1295
|
this.apiKey = config.apiKey;
|
|
@@ -990,6 +1313,7 @@ var OwneySDK = class {
|
|
|
990
1313
|
);
|
|
991
1314
|
}
|
|
992
1315
|
this.state = { provider, walletAddress, chainId: null };
|
|
1316
|
+
this.cachedSponsoredCallback = null;
|
|
993
1317
|
}
|
|
994
1318
|
/**
|
|
995
1319
|
* Disconnect from all agents and clear connection state.
|
|
@@ -1002,6 +1326,7 @@ var OwneySDK = class {
|
|
|
1002
1326
|
this.activeAgents.clear();
|
|
1003
1327
|
this.disabledAgents.clear();
|
|
1004
1328
|
this.state = null;
|
|
1329
|
+
this.cachedSponsoredCallback = null;
|
|
1005
1330
|
}
|
|
1006
1331
|
isAgentDisabled(agentId) {
|
|
1007
1332
|
return this.disabledAgents.has(agentId);
|
|
@@ -1035,6 +1360,46 @@ var OwneySDK = class {
|
|
|
1035
1360
|
}
|
|
1036
1361
|
return chainId;
|
|
1037
1362
|
}
|
|
1363
|
+
requireConnectedProvider() {
|
|
1364
|
+
if (!this.state?.provider) {
|
|
1365
|
+
throw new OwneyError(
|
|
1366
|
+
"NOT_CONNECTED",
|
|
1367
|
+
"connect(provider) must be called before deposit",
|
|
1368
|
+
{}
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
return this.state.provider;
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
|
|
1375
|
+
* used when the caller omits `depositCallback`. Wraps the connected EIP-1193
|
|
1376
|
+
* provider with viem `custom(provider)` to read token meta and sign the
|
|
1377
|
+
* `TransferWithAuthorization`, then POSTs to the sponsor API.
|
|
1378
|
+
*/
|
|
1379
|
+
getDefaultSponsoredCallback() {
|
|
1380
|
+
if (this.cachedSponsoredCallback)
|
|
1381
|
+
return this.cachedSponsoredCallback;
|
|
1382
|
+
const provider = this.requireConnectedProvider();
|
|
1383
|
+
const owner = this.state.walletAddress;
|
|
1384
|
+
this.cachedSponsoredCallback = makeSponsoredDepositCallback({
|
|
1385
|
+
apiKey: this.apiKey,
|
|
1386
|
+
ownerAddress: owner,
|
|
1387
|
+
tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
|
|
1388
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
1389
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
1390
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
1391
|
+
getPublicClient: (cid) => (0, import_viem4.createPublicClient)({
|
|
1392
|
+
chain: VIEM_CHAIN2[cid],
|
|
1393
|
+
transport: (0, import_viem4.custom)(provider)
|
|
1394
|
+
}),
|
|
1395
|
+
getWalletClient: (cid) => (0, import_viem4.createWalletClient)({
|
|
1396
|
+
account: owner,
|
|
1397
|
+
chain: VIEM_CHAIN2[cid],
|
|
1398
|
+
transport: (0, import_viem4.custom)(provider)
|
|
1399
|
+
})
|
|
1400
|
+
});
|
|
1401
|
+
return this.cachedSponsoredCallback;
|
|
1402
|
+
}
|
|
1038
1403
|
getAgent(agentId) {
|
|
1039
1404
|
const agent = this.agents.get(agentId);
|
|
1040
1405
|
if (!agent) {
|
|
@@ -1188,6 +1553,7 @@ var OwneySDK = class {
|
|
|
1188
1553
|
}
|
|
1189
1554
|
const state = this.requireState();
|
|
1190
1555
|
const chainId = this.requireChainId();
|
|
1556
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? void 0 : this.getDefaultSponsoredCallback());
|
|
1191
1557
|
if (agentId) {
|
|
1192
1558
|
if (this.isAgentDisabled(agentId)) {
|
|
1193
1559
|
throw new OwneyError(
|
|
@@ -1202,7 +1568,13 @@ var OwneySDK = class {
|
|
|
1202
1568
|
return withFailureReporting(
|
|
1203
1569
|
this.apiKey,
|
|
1204
1570
|
agent.id,
|
|
1205
|
-
() => agent.deposit(
|
|
1571
|
+
() => agent.deposit(
|
|
1572
|
+
state,
|
|
1573
|
+
chainId,
|
|
1574
|
+
amount,
|
|
1575
|
+
asset,
|
|
1576
|
+
effectiveCallback
|
|
1577
|
+
)
|
|
1206
1578
|
);
|
|
1207
1579
|
}
|
|
1208
1580
|
const totalAmount = BigInt(amount);
|
|
@@ -1236,7 +1608,13 @@ var OwneySDK = class {
|
|
|
1236
1608
|
agentResults[agent.id] = await withFailureReporting(
|
|
1237
1609
|
this.apiKey,
|
|
1238
1610
|
agent.id,
|
|
1239
|
-
() => agent.deposit(
|
|
1611
|
+
() => agent.deposit(
|
|
1612
|
+
state,
|
|
1613
|
+
chainId,
|
|
1614
|
+
agentAmount,
|
|
1615
|
+
asset,
|
|
1616
|
+
effectiveCallback
|
|
1617
|
+
)
|
|
1240
1618
|
);
|
|
1241
1619
|
}
|
|
1242
1620
|
return { agentResults };
|
|
@@ -1430,10 +1808,7 @@ var OwneySDK = class {
|
|
|
1430
1808
|
disabledBalances,
|
|
1431
1809
|
requested
|
|
1432
1810
|
);
|
|
1433
|
-
const enabledTotal = enabledBalances.reduce(
|
|
1434
|
-
(s, b) => s + b.balance,
|
|
1435
|
-
0n
|
|
1436
|
-
);
|
|
1811
|
+
const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
|
|
1437
1812
|
const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
|
|
1438
1813
|
agent,
|
|
1439
1814
|
balance,
|
|
@@ -1704,27 +2079,6 @@ var OwneySDK = class {
|
|
|
1704
2079
|
nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
|
|
1705
2080
|
};
|
|
1706
2081
|
}
|
|
1707
|
-
// --- Agent control ---
|
|
1708
|
-
/**
|
|
1709
|
-
* Pause an agent's automated operations. The agent will stop rebalancing until resumed.
|
|
1710
|
-
* @param agentId - The agent to pause (e.g. "zyfai")
|
|
1711
|
-
* @returns OwneyAgentStatus with success flag
|
|
1712
|
-
*/
|
|
1713
|
-
async pauseAgent(agentId) {
|
|
1714
|
-
const state = this.requireState();
|
|
1715
|
-
const chainId = this.requireChainId();
|
|
1716
|
-
return this.getAgent(agentId).pause(state, chainId);
|
|
1717
|
-
}
|
|
1718
|
-
/**
|
|
1719
|
-
* Resume an agent's automated operations.
|
|
1720
|
-
* @param agentId - The agent to resume (e.g. "zyfai")
|
|
1721
|
-
* @returns OwneyAgentStatus with success flag
|
|
1722
|
-
*/
|
|
1723
|
-
async resumeAgent(agentId) {
|
|
1724
|
-
const state = this.requireState();
|
|
1725
|
-
const chainId = this.requireChainId();
|
|
1726
|
-
return this.getAgent(agentId).resume(state, chainId);
|
|
1727
|
-
}
|
|
1728
2082
|
/**
|
|
1729
2083
|
* Get the user's profile for a specific agent, or all agents.
|
|
1730
2084
|
* @param agentId - Optional. Agent to query. Omit for all agents.
|
|
@@ -1799,12 +2153,13 @@ var OwneySDK = class {
|
|
|
1799
2153
|
const chainId = this.requireChainId();
|
|
1800
2154
|
const activeAgents = this.getActiveAgents();
|
|
1801
2155
|
const balances = {};
|
|
2156
|
+
const positionsByAgent = {};
|
|
1802
2157
|
let totalBalance = 0;
|
|
1803
2158
|
const entries = [...activeAgents.entries()];
|
|
1804
2159
|
const balanceResults = await Promise.allSettled(
|
|
1805
2160
|
entries.map(async ([id, agent]) => {
|
|
1806
2161
|
const b = await agent.getBalances(state, chainId);
|
|
1807
|
-
return [id, Number(b.totalBalance)];
|
|
2162
|
+
return [id, Number(b.totalBalance), b.positions ?? []];
|
|
1808
2163
|
})
|
|
1809
2164
|
);
|
|
1810
2165
|
let successCount = 0;
|
|
@@ -1813,8 +2168,9 @@ var OwneySDK = class {
|
|
|
1813
2168
|
const settledResult = balanceResults[i];
|
|
1814
2169
|
const [agentId] = entries[i];
|
|
1815
2170
|
if (settledResult.status === "fulfilled") {
|
|
1816
|
-
const [id, bal] = settledResult.value;
|
|
2171
|
+
const [id, bal, agentPositions] = settledResult.value;
|
|
1817
2172
|
balances[id] = bal;
|
|
2173
|
+
positionsByAgent[id] = agentPositions;
|
|
1818
2174
|
totalBalance += bal;
|
|
1819
2175
|
successCount += 1;
|
|
1820
2176
|
} else {
|
|
@@ -1848,13 +2204,70 @@ var OwneySDK = class {
|
|
|
1848
2204
|
}
|
|
1849
2205
|
}
|
|
1850
2206
|
return {
|
|
1851
|
-
agentAllocation: allocation
|
|
2207
|
+
agentAllocation: allocation,
|
|
2208
|
+
agentPositions: positionsByAgent
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Allocation-weighted APY from the current portfolio: each pool's APY
|
|
2213
|
+
* weighted by the user's position value in it. More accurate per-user than
|
|
2214
|
+
* the historical 30D APY. (ROUT-228)
|
|
2215
|
+
* @param options.agentId - Optional. Scope to one agent. Omit to aggregate
|
|
2216
|
+
* positions across all active agents.
|
|
2217
|
+
*/
|
|
2218
|
+
async getAllocationApy({
|
|
2219
|
+
agentId
|
|
2220
|
+
} = {}) {
|
|
2221
|
+
const state = this.requireState();
|
|
2222
|
+
const chainId = this.requireChainId();
|
|
2223
|
+
const activeAgents = agentId ? /* @__PURE__ */ new Map([[agentId, this.getAgent(agentId)]]) : this.getActiveAgents();
|
|
2224
|
+
const entries = [...activeAgents.entries()];
|
|
2225
|
+
const balanceResults = await Promise.allSettled(
|
|
2226
|
+
entries.map(async ([id, agent]) => {
|
|
2227
|
+
const b = await agent.getBalances(state, chainId);
|
|
2228
|
+
return [id, b.positions ?? []];
|
|
2229
|
+
})
|
|
2230
|
+
);
|
|
2231
|
+
const agentApy = {};
|
|
2232
|
+
const allPositions = [];
|
|
2233
|
+
let successCount = 0;
|
|
2234
|
+
const agentErrors = {};
|
|
2235
|
+
for (let i = 0; i < balanceResults.length; i++) {
|
|
2236
|
+
const settled = balanceResults[i];
|
|
2237
|
+
const [aid] = entries[i];
|
|
2238
|
+
if (settled.status === "fulfilled") {
|
|
2239
|
+
const [id, positions] = settled.value;
|
|
2240
|
+
const per = computeAllocationApy(positions);
|
|
2241
|
+
agentApy[id] = {
|
|
2242
|
+
totalApy: per.totalApy,
|
|
2243
|
+
apyByChainAndAsset: per.apyByChainAndAsset
|
|
2244
|
+
};
|
|
2245
|
+
allPositions.push(...positions);
|
|
2246
|
+
successCount += 1;
|
|
2247
|
+
} else {
|
|
2248
|
+
const reason = settled.reason;
|
|
2249
|
+
agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
|
|
2250
|
+
console.error(`getAllocationApy agent "${aid}" failed:`, reason);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
if (successCount === 0) {
|
|
2254
|
+
throw new OwneyError(
|
|
2255
|
+
"ALLOCATION_ALL_FAILED",
|
|
2256
|
+
"Failed to fetch allocation APY: all agents failed.",
|
|
2257
|
+
{ agentErrors }
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
const overall = computeAllocationApy(allPositions);
|
|
2261
|
+
return {
|
|
2262
|
+
totalApy: overall.totalApy,
|
|
2263
|
+
apyByChainAndAsset: overall.apyByChainAndAsset,
|
|
2264
|
+
agentApy
|
|
1852
2265
|
};
|
|
1853
2266
|
}
|
|
1854
2267
|
};
|
|
1855
2268
|
|
|
1856
2269
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
1857
|
-
var
|
|
2270
|
+
var import_viem5 = require("viem");
|
|
1858
2271
|
var import_siwe = require("siwe");
|
|
1859
2272
|
var import_sdk2 = require("@zyfai/sdk");
|
|
1860
2273
|
|
|
@@ -1936,7 +2349,7 @@ function buildSIWXConfig(deps) {
|
|
|
1936
2349
|
issuedAt,
|
|
1937
2350
|
toString() {
|
|
1938
2351
|
return new import_siwe.SiweMessage({
|
|
1939
|
-
address: (0,
|
|
2352
|
+
address: (0, import_viem5.getAddress)(accountAddress),
|
|
1940
2353
|
chainId: numericChainId(chainId),
|
|
1941
2354
|
domain,
|
|
1942
2355
|
uri,
|
|
@@ -2012,9 +2425,9 @@ function buildSIWXConfig(deps) {
|
|
|
2012
2425
|
}
|
|
2013
2426
|
function createOwneySIWX(config) {
|
|
2014
2427
|
const zyfai = new import_sdk2.ZyfaiSDK({ apiKey: config.apiKey });
|
|
2015
|
-
const
|
|
2428
|
+
const http2 = zyfai.httpClient;
|
|
2016
2429
|
return buildSIWXConfig({
|
|
2017
|
-
post: (url, data) =>
|
|
2430
|
+
post: (url, data) => http2.post(url, data),
|
|
2018
2431
|
referralSource: config.referralSource
|
|
2019
2432
|
});
|
|
2020
2433
|
}
|