@owney/sdk 0.6.4 → 0.6.5-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +487 -35
- package/dist/index.d.cts +119 -61
- package/dist/index.d.ts +119 -61
- package/dist/index.js +480 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ var P_SUPPORTED_CHAINS = {
|
|
|
8
8
|
chainIds: [8453, 42161],
|
|
9
9
|
chains: ["BASE", "ARBITRUM"]
|
|
10
10
|
};
|
|
11
|
-
var SUPPORTED_TOKENS = ["USDC", "USDT"];
|
|
11
|
+
var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
|
|
12
12
|
var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
|
|
13
13
|
var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
|
|
14
14
|
var SupportedAssets = [
|
|
@@ -29,6 +29,18 @@ var SupportedAssets = [
|
|
|
29
29
|
symbol: "USDT",
|
|
30
30
|
chainId: 42161,
|
|
31
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
|
|
32
44
|
}
|
|
33
45
|
];
|
|
34
46
|
|
|
@@ -109,6 +121,20 @@ var DayFilterMapping = {
|
|
|
109
121
|
};
|
|
110
122
|
|
|
111
123
|
// src/agents/zyfai/zyfai.mapper.ts
|
|
124
|
+
var DECIMALS_BY_SYMBOL = {
|
|
125
|
+
usdc: 6,
|
|
126
|
+
usdt: 6,
|
|
127
|
+
weth: 18,
|
|
128
|
+
eth: 18,
|
|
129
|
+
cbeth: 18,
|
|
130
|
+
wbtc: 8,
|
|
131
|
+
cbbtc: 8
|
|
132
|
+
};
|
|
133
|
+
function tokenDecimals(symbol, explicit) {
|
|
134
|
+
if (typeof explicit === "number" && Number.isFinite(explicit))
|
|
135
|
+
return explicit;
|
|
136
|
+
return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
|
|
137
|
+
}
|
|
112
138
|
function mapDeposit(raw) {
|
|
113
139
|
return {
|
|
114
140
|
txHash: raw.txHash,
|
|
@@ -166,9 +192,16 @@ function mapBalances(raw, _chainId, smartWallet) {
|
|
|
166
192
|
pool: p.pool,
|
|
167
193
|
asset: p.token_symbol ?? "",
|
|
168
194
|
// Position amount is hex-encoded base units (like the token balances
|
|
169
|
-
// above).
|
|
170
|
-
//
|
|
171
|
-
|
|
195
|
+
// above). `PositionSlot` doesn't type a `decimals` field, but runtime
|
|
196
|
+
// getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
|
|
197
|
+
// when present, otherwise fall back to the per-symbol lookup so
|
|
198
|
+
// non-6-decimal assets like WETH don't render 1e12 too large.
|
|
199
|
+
amount: String(
|
|
200
|
+
hexToDecimal(
|
|
201
|
+
p.amount || "0",
|
|
202
|
+
tokenDecimals(p.token_symbol, p.decimals)
|
|
203
|
+
)
|
|
204
|
+
),
|
|
172
205
|
apy: p.pool_apy,
|
|
173
206
|
tvl: p.pool_tvl,
|
|
174
207
|
// PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
|
|
@@ -291,7 +324,15 @@ function mapEntries(rawEntries, chainId) {
|
|
|
291
324
|
fromPool: from.pool,
|
|
292
325
|
toPool: to.pool,
|
|
293
326
|
tokenSymbol: matched?.token_symbol ?? "",
|
|
294
|
-
amount: matched ? String(
|
|
327
|
+
amount: matched ? String(
|
|
328
|
+
hexToDecimal(
|
|
329
|
+
matched.amount || "0x",
|
|
330
|
+
tokenDecimals(
|
|
331
|
+
matched.token_symbol,
|
|
332
|
+
matched.decimals
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
) : "",
|
|
295
336
|
status: "success"
|
|
296
337
|
}
|
|
297
338
|
];
|
|
@@ -310,7 +351,12 @@ function mapEntries(rawEntries, chainId) {
|
|
|
310
351
|
txHashes: entry.transactionHash ? [entry.transactionHash] : [],
|
|
311
352
|
chainId: entry.chainId,
|
|
312
353
|
tokenSymbol: entry?.positions?.[0]?.token_symbol,
|
|
313
|
-
amount: hasAmount ? String(
|
|
354
|
+
amount: hasAmount ? String(
|
|
355
|
+
hexToDecimal(
|
|
356
|
+
rawAmount,
|
|
357
|
+
tokenDecimals(entry?.positions?.[0]?.token_symbol)
|
|
358
|
+
)
|
|
359
|
+
) : void 0
|
|
314
360
|
}
|
|
315
361
|
],
|
|
316
362
|
rebalanceLog
|
|
@@ -513,11 +559,26 @@ var clearSession = (address, chainId) => {
|
|
|
513
559
|
}
|
|
514
560
|
};
|
|
515
561
|
|
|
562
|
+
// src/agents/zyfai/autoSelect.ts
|
|
563
|
+
function buildAutoSelectUpdate(current, asset) {
|
|
564
|
+
if (current.autoSelectProtocols === true)
|
|
565
|
+
return null;
|
|
566
|
+
return {
|
|
567
|
+
asset,
|
|
568
|
+
autoSelectProtocols: true,
|
|
569
|
+
...current.chains ? { chains: current.chains } : {}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
516
573
|
// src/agents/zyfai/zyfai.agent.ts
|
|
517
574
|
var DEFAULT_ZYFAI_RPC_URLS = {
|
|
518
575
|
8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
|
|
519
576
|
42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
|
|
520
577
|
};
|
|
578
|
+
var WETH_ADDRESS_BY_CHAIN = {
|
|
579
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
580
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
|
581
|
+
};
|
|
521
582
|
var VIEM_CHAIN = { 8453: base, 42161: arbitrum };
|
|
522
583
|
var ZyfaiAgent = class {
|
|
523
584
|
id = "zyfai";
|
|
@@ -526,12 +587,18 @@ var ZyfaiAgent = class {
|
|
|
526
587
|
{
|
|
527
588
|
chainId: 8453,
|
|
528
589
|
chain: "BASE",
|
|
529
|
-
assets: [
|
|
590
|
+
assets: [
|
|
591
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
592
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
593
|
+
]
|
|
530
594
|
},
|
|
531
595
|
{
|
|
532
596
|
chainId: 42161,
|
|
533
597
|
chain: "ARBITRUM",
|
|
534
|
-
assets: [
|
|
598
|
+
assets: [
|
|
599
|
+
{ symbol: "USDC", minDepositAmount: "1500000" },
|
|
600
|
+
{ symbol: "WETH", minDepositAmount: "500000000000000" }
|
|
601
|
+
]
|
|
535
602
|
}
|
|
536
603
|
];
|
|
537
604
|
sdk;
|
|
@@ -566,22 +633,24 @@ var ZyfaiAgent = class {
|
|
|
566
633
|
}
|
|
567
634
|
/**
|
|
568
635
|
* Fills in the true withdrawn amount for `Withdraw` entries by decoding the
|
|
569
|
-
*
|
|
636
|
+
* on-chain `Transfer` that lands in the user's wallet. The Zyfai history
|
|
570
637
|
* payload only reports the *remaining* position for a partial withdraw (see
|
|
571
638
|
* the note in `mapEntries`), so without this the UI shows the leftover
|
|
572
639
|
* balance instead of the amount withdrawn.
|
|
573
640
|
*
|
|
641
|
+
* The withdrawn asset varies per entry (USDC or WETH), so it's resolved
|
|
642
|
+
* from `tx.tokenSymbol` (set by `mapEntries` from the entry's own
|
|
643
|
+
* position) rather than assumed to always be USDC. Zyfai sometimes
|
|
644
|
+
* denominates WETH as lowercase "eth" — that's normalized to WETH.
|
|
645
|
+
* Entries with no symbol (older payload shapes) fall back to USDC to
|
|
646
|
+
* preserve prior behavior.
|
|
647
|
+
*
|
|
574
648
|
* Mutates `entries` in place. Runs all lookups concurrently and is tolerant
|
|
575
649
|
* of RPC failures: an entry whose amount can't be resolved keeps
|
|
576
650
|
* `amount: undefined`, which consumers render as a hidden/retryable row
|
|
577
651
|
* rather than a wrong number.
|
|
578
652
|
*/
|
|
579
653
|
async resolveWithdrawnAmounts(entries, chainId, recipient) {
|
|
580
|
-
const asset = SupportedAssets.find(
|
|
581
|
-
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
582
|
-
);
|
|
583
|
-
if (!asset)
|
|
584
|
-
return;
|
|
585
654
|
const withdrawals = entries.filter(
|
|
586
655
|
(e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
|
|
587
656
|
);
|
|
@@ -592,7 +661,17 @@ var ZyfaiAgent = class {
|
|
|
592
661
|
withdrawals.map(async (entry) => {
|
|
593
662
|
const tx = entry.transactions[0];
|
|
594
663
|
const txHash = tx.txHashes[0];
|
|
595
|
-
const
|
|
664
|
+
const rawSymbol = tx.tokenSymbol;
|
|
665
|
+
const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
|
|
666
|
+
const asset = SupportedAssets.find(
|
|
667
|
+
(a) => a.chainId === chainId && a.symbol === normalizedSymbol
|
|
668
|
+
) ?? SupportedAssets.find(
|
|
669
|
+
(a) => a.chainId === chainId && a.symbol === "USDC"
|
|
670
|
+
);
|
|
671
|
+
if (!asset)
|
|
672
|
+
return;
|
|
673
|
+
const cacheKey = `${txHash}:${asset.address}`;
|
|
674
|
+
const cached = this.withdrawAmountCache.get(cacheKey);
|
|
596
675
|
if (cached !== void 0) {
|
|
597
676
|
tx.amount = cached;
|
|
598
677
|
return;
|
|
@@ -608,7 +687,7 @@ var ZyfaiAgent = class {
|
|
|
608
687
|
asset.decimals
|
|
609
688
|
);
|
|
610
689
|
if (amount !== void 0) {
|
|
611
|
-
this.withdrawAmountCache.set(
|
|
690
|
+
this.withdrawAmountCache.set(cacheKey, amount);
|
|
612
691
|
tx.amount = amount;
|
|
613
692
|
}
|
|
614
693
|
} catch {
|
|
@@ -726,6 +805,22 @@ var ZyfaiAgent = class {
|
|
|
726
805
|
this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
|
|
727
806
|
if (!wallet.isDeployed) {
|
|
728
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
|
+
}
|
|
729
824
|
}
|
|
730
825
|
const user = await this.sdk.getUserDetails();
|
|
731
826
|
if (!user.hasActiveSessionKey) {
|
|
@@ -767,7 +862,7 @@ var ZyfaiAgent = class {
|
|
|
767
862
|
throw error;
|
|
768
863
|
}
|
|
769
864
|
}
|
|
770
|
-
async deposit(state, chainId, amount, depositCallback) {
|
|
865
|
+
async deposit(state, chainId, amount, asset, depositCallback) {
|
|
771
866
|
try {
|
|
772
867
|
await this.connectFull(state, chainId);
|
|
773
868
|
const validChainId = isValidChainId(chainId);
|
|
@@ -781,13 +876,15 @@ var ZyfaiAgent = class {
|
|
|
781
876
|
validChainId,
|
|
782
877
|
amount
|
|
783
878
|
);
|
|
784
|
-
|
|
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));
|
|
785
881
|
return { txHash, smartWallet: wallet.address, amount };
|
|
786
882
|
}
|
|
787
883
|
const raw = await this.sdk.depositFunds(
|
|
788
884
|
this.getAddress(),
|
|
789
885
|
validChainId,
|
|
790
|
-
amount
|
|
886
|
+
amount,
|
|
887
|
+
asset
|
|
791
888
|
);
|
|
792
889
|
return mapDeposit(raw);
|
|
793
890
|
} catch (error) {
|
|
@@ -883,6 +980,21 @@ var ZyfaiAgent = class {
|
|
|
883
980
|
}
|
|
884
981
|
return mapUserProfile(raw, this.connectedAddress);
|
|
885
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)
|
|
994
|
+
return false;
|
|
995
|
+
await this.sdk.updateUserProfile(update);
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
886
998
|
// --- IAgent: Discovery (no wallet required) ---
|
|
887
999
|
async getAgentApy(days, options) {
|
|
888
1000
|
const raw = await this.sdk.getAPYPerStrategy(
|
|
@@ -1162,6 +1274,70 @@ async function postSponsorTransferAuth(input) {
|
|
|
1162
1274
|
}
|
|
1163
1275
|
return parsed.data;
|
|
1164
1276
|
}
|
|
1277
|
+
async function postSponsorPermit2Transfer(input) {
|
|
1278
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1279
|
+
let res;
|
|
1280
|
+
try {
|
|
1281
|
+
res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
|
|
1282
|
+
method: "POST",
|
|
1283
|
+
headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
|
|
1284
|
+
body: JSON.stringify(input.body)
|
|
1285
|
+
});
|
|
1286
|
+
} catch (networkError) {
|
|
1287
|
+
throw new OwneyError(
|
|
1288
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1289
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1290
|
+
{ cause: String(networkError), safeToFallback: false }
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
const text = await res.text();
|
|
1294
|
+
let parsed = null;
|
|
1295
|
+
try {
|
|
1296
|
+
parsed = JSON.parse(text);
|
|
1297
|
+
} catch {
|
|
1298
|
+
}
|
|
1299
|
+
if (!res.ok || !parsed?.success || !parsed.data) {
|
|
1300
|
+
throw new OwneyError(
|
|
1301
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1302
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1303
|
+
{
|
|
1304
|
+
statusCode: res.status,
|
|
1305
|
+
responseBody: text.slice(0, 500),
|
|
1306
|
+
safeToFallback: res.status >= 400 && res.status < 500
|
|
1307
|
+
}
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
return parsed.data;
|
|
1311
|
+
}
|
|
1312
|
+
async function getSponsorRelayerAddress(input) {
|
|
1313
|
+
const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
1314
|
+
let res;
|
|
1315
|
+
try {
|
|
1316
|
+
res = await fetch(`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`, {
|
|
1317
|
+
headers: { "x-owney-api-key": input.apiKey }
|
|
1318
|
+
});
|
|
1319
|
+
} catch (networkError) {
|
|
1320
|
+
throw new OwneyError(
|
|
1321
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1322
|
+
`Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
|
|
1323
|
+
{ cause: String(networkError), safeToFallback: true }
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
const text = await res.text();
|
|
1327
|
+
let parsed = null;
|
|
1328
|
+
try {
|
|
1329
|
+
parsed = JSON.parse(text);
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
|
|
1333
|
+
throw new OwneyError(
|
|
1334
|
+
"SPONSOR_REQUEST_FAILED",
|
|
1335
|
+
`Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
|
|
1336
|
+
{ statusCode: res.status, responseBody: text.slice(0, 500), safeToFallback: true }
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
return parsed.data.relayer;
|
|
1340
|
+
}
|
|
1165
1341
|
|
|
1166
1342
|
// src/lib/sponsored-deposit.ts
|
|
1167
1343
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
@@ -1213,6 +1389,114 @@ function makeSponsoredDepositCallback(deps) {
|
|
|
1213
1389
|
};
|
|
1214
1390
|
}
|
|
1215
1391
|
|
|
1392
|
+
// src/lib/permit2.ts
|
|
1393
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
1394
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
1395
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
1396
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
1397
|
+
{
|
|
1398
|
+
type: "function",
|
|
1399
|
+
name: "allowance",
|
|
1400
|
+
stateMutability: "view",
|
|
1401
|
+
inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }],
|
|
1402
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
type: "function",
|
|
1406
|
+
name: "approve",
|
|
1407
|
+
stateMutability: "nonpayable",
|
|
1408
|
+
inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
|
|
1409
|
+
outputs: [{ name: "", type: "bool" }]
|
|
1410
|
+
}
|
|
1411
|
+
];
|
|
1412
|
+
function buildPermitTransferFromTypedData(input) {
|
|
1413
|
+
return {
|
|
1414
|
+
domain: { name: "Permit2", chainId: input.chainId, verifyingContract: PERMIT2_ADDRESS },
|
|
1415
|
+
types: {
|
|
1416
|
+
PermitTransferFrom: [
|
|
1417
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
1418
|
+
{ name: "spender", type: "address" },
|
|
1419
|
+
{ name: "nonce", type: "uint256" },
|
|
1420
|
+
{ name: "deadline", type: "uint256" }
|
|
1421
|
+
],
|
|
1422
|
+
TokenPermissions: [
|
|
1423
|
+
{ name: "token", type: "address" },
|
|
1424
|
+
{ name: "amount", type: "uint256" }
|
|
1425
|
+
]
|
|
1426
|
+
},
|
|
1427
|
+
primaryType: "PermitTransferFrom",
|
|
1428
|
+
message: input.message
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
function randomPermit2Nonce() {
|
|
1432
|
+
const bytes = new Uint8Array(32);
|
|
1433
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
1434
|
+
return BigInt(bytesToHex2(bytes));
|
|
1435
|
+
}
|
|
1436
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
1437
|
+
return publicClient.readContract({
|
|
1438
|
+
address: token,
|
|
1439
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
1440
|
+
functionName: "allowance",
|
|
1441
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/lib/sponsored-weth-deposit.ts
|
|
1446
|
+
var PERMIT_WINDOW_SECONDS = 15 * 60;
|
|
1447
|
+
function makeSponsoredWethCallback(deps) {
|
|
1448
|
+
const get = deps.httpGet ?? getSponsorRelayerAddress;
|
|
1449
|
+
const post = deps.httpPost ?? postSponsorPermit2Transfer;
|
|
1450
|
+
return async (smartWallet, chainId, amount) => {
|
|
1451
|
+
const cid = chainId;
|
|
1452
|
+
const token = deps.tokenAddressByChain[cid];
|
|
1453
|
+
if (!token) {
|
|
1454
|
+
throw new OwneyError("CHAIN_UNSUPPORTED", `No sponsored WETH configured for chain ${chainId}`);
|
|
1455
|
+
}
|
|
1456
|
+
const amountWei = BigInt(amount);
|
|
1457
|
+
const pub = deps.getPublicClient(cid);
|
|
1458
|
+
const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
|
|
1459
|
+
if (allowance < amountWei) {
|
|
1460
|
+
throw new OwneyError(
|
|
1461
|
+
"PERMIT2_APPROVAL_REQUIRED",
|
|
1462
|
+
"WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
|
|
1463
|
+
{ token, chainId: cid, allowance: allowance.toString(), amount }
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
const relayer = await get({ baseUrl: deps.baseUrl, apiKey: deps.apiKey, chainId: cid });
|
|
1467
|
+
const nonce = randomPermit2Nonce();
|
|
1468
|
+
const deadline = BigInt(Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS);
|
|
1469
|
+
const typedData = buildPermitTransferFromTypedData({
|
|
1470
|
+
chainId: cid,
|
|
1471
|
+
message: {
|
|
1472
|
+
permitted: { token, amount: amountWei },
|
|
1473
|
+
spender: relayer,
|
|
1474
|
+
nonce,
|
|
1475
|
+
deadline
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
const wallet = deps.getWalletClient(cid);
|
|
1479
|
+
const signature = await wallet.signTypedData(
|
|
1480
|
+
{ account: deps.ownerAddress, ...typedData }
|
|
1481
|
+
);
|
|
1482
|
+
const result = await post({
|
|
1483
|
+
baseUrl: deps.baseUrl,
|
|
1484
|
+
apiKey: deps.apiKey,
|
|
1485
|
+
body: {
|
|
1486
|
+
chainId: cid,
|
|
1487
|
+
token,
|
|
1488
|
+
from: deps.ownerAddress,
|
|
1489
|
+
to: smartWallet,
|
|
1490
|
+
amount,
|
|
1491
|
+
nonce: nonce.toString(),
|
|
1492
|
+
deadline: deadline.toString(),
|
|
1493
|
+
signature
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
return result.txHash;
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1216
1500
|
// src/client.ts
|
|
1217
1501
|
function encodeMultiAgentCursor(map) {
|
|
1218
1502
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
@@ -1242,6 +1526,13 @@ var VIEM_CHAIN2 = {
|
|
|
1242
1526
|
8453: base2,
|
|
1243
1527
|
42161: arbitrum2
|
|
1244
1528
|
};
|
|
1529
|
+
var SPONSORED_WETH_BY_CHAIN = {
|
|
1530
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
1531
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
|
1532
|
+
};
|
|
1533
|
+
function shouldFallbackToUserPaid(error, asset, appCallback) {
|
|
1534
|
+
return asset === "WETH" && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
|
|
1535
|
+
}
|
|
1245
1536
|
var OwneySDK = class {
|
|
1246
1537
|
agents = /* @__PURE__ */ new Map();
|
|
1247
1538
|
activeAgents = /* @__PURE__ */ new Set();
|
|
@@ -1255,6 +1546,7 @@ var OwneySDK = class {
|
|
|
1255
1546
|
apiKey;
|
|
1256
1547
|
zyfaiRpcUrls;
|
|
1257
1548
|
cachedSponsoredCallback = null;
|
|
1549
|
+
cachedWethSponsoredCallback = null;
|
|
1258
1550
|
initializingAgentsPromise = null;
|
|
1259
1551
|
constructor(config) {
|
|
1260
1552
|
this.apiKey = config.apiKey;
|
|
@@ -1279,6 +1571,7 @@ var OwneySDK = class {
|
|
|
1279
1571
|
}
|
|
1280
1572
|
this.state = { provider, walletAddress, chainId: null };
|
|
1281
1573
|
this.cachedSponsoredCallback = null;
|
|
1574
|
+
this.cachedWethSponsoredCallback = null;
|
|
1282
1575
|
}
|
|
1283
1576
|
/**
|
|
1284
1577
|
* Disconnect from all agents and clear connection state.
|
|
@@ -1292,6 +1585,7 @@ var OwneySDK = class {
|
|
|
1292
1585
|
this.disabledAgents.clear();
|
|
1293
1586
|
this.state = null;
|
|
1294
1587
|
this.cachedSponsoredCallback = null;
|
|
1588
|
+
this.cachedWethSponsoredCallback = null;
|
|
1295
1589
|
}
|
|
1296
1590
|
isAgentDisabled(agentId) {
|
|
1297
1591
|
return this.disabledAgents.has(agentId);
|
|
@@ -1365,6 +1659,36 @@ var OwneySDK = class {
|
|
|
1365
1659
|
});
|
|
1366
1660
|
return this.cachedSponsoredCallback;
|
|
1367
1661
|
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Lazily builds (and caches) the default Permit2 sponsored WETH deposit
|
|
1664
|
+
* callback used when the caller omits `depositCallback` for a WETH
|
|
1665
|
+
* deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
|
|
1666
|
+
* `PermitTransferFrom` instead of an EIP-3009 authorization.
|
|
1667
|
+
*/
|
|
1668
|
+
getDefaultWethSponsoredCallback() {
|
|
1669
|
+
if (this.cachedWethSponsoredCallback)
|
|
1670
|
+
return this.cachedWethSponsoredCallback;
|
|
1671
|
+
const provider = this.requireConnectedProvider();
|
|
1672
|
+
const owner = this.state.walletAddress;
|
|
1673
|
+
this.cachedWethSponsoredCallback = makeSponsoredWethCallback({
|
|
1674
|
+
apiKey: this.apiKey,
|
|
1675
|
+
ownerAddress: owner,
|
|
1676
|
+
tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
|
|
1677
|
+
// Casts work around viem's chain-narrowed Client vs the generic
|
|
1678
|
+
// PublicClient/WalletClient param types — structurally identical at
|
|
1679
|
+
// runtime, but the two share a name TS treats as unrelated.
|
|
1680
|
+
getPublicClient: (cid) => createPublicClient2({
|
|
1681
|
+
chain: VIEM_CHAIN2[cid],
|
|
1682
|
+
transport: custom(provider)
|
|
1683
|
+
}),
|
|
1684
|
+
getWalletClient: (cid) => createWalletClient({
|
|
1685
|
+
account: owner,
|
|
1686
|
+
chain: VIEM_CHAIN2[cid],
|
|
1687
|
+
transport: custom(provider)
|
|
1688
|
+
})
|
|
1689
|
+
});
|
|
1690
|
+
return this.cachedWethSponsoredCallback;
|
|
1691
|
+
}
|
|
1368
1692
|
getAgent(agentId) {
|
|
1369
1693
|
const agent = this.agents.get(agentId);
|
|
1370
1694
|
if (!agent) {
|
|
@@ -1518,7 +1842,7 @@ var OwneySDK = class {
|
|
|
1518
1842
|
}
|
|
1519
1843
|
const state = this.requireState();
|
|
1520
1844
|
const chainId = this.requireChainId();
|
|
1521
|
-
const effectiveCallback = depositCallback ?? this.getDefaultSponsoredCallback();
|
|
1845
|
+
const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback() : this.getDefaultSponsoredCallback());
|
|
1522
1846
|
if (agentId) {
|
|
1523
1847
|
if (this.isAgentDisabled(agentId)) {
|
|
1524
1848
|
throw new OwneyError(
|
|
@@ -1530,10 +1854,14 @@ var OwneySDK = class {
|
|
|
1530
1854
|
const agent = this.getAgent(agentId);
|
|
1531
1855
|
this.validateAssetSupport(agent, chainId, asset);
|
|
1532
1856
|
await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
|
|
1533
|
-
return
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1857
|
+
return this.depositWithFallback(
|
|
1858
|
+
agent,
|
|
1859
|
+
state,
|
|
1860
|
+
chainId,
|
|
1861
|
+
amount,
|
|
1862
|
+
asset,
|
|
1863
|
+
effectiveCallback,
|
|
1864
|
+
depositCallback
|
|
1537
1865
|
);
|
|
1538
1866
|
}
|
|
1539
1867
|
const totalAmount = BigInt(amount);
|
|
@@ -1564,14 +1892,78 @@ var OwneySDK = class {
|
|
|
1564
1892
|
}
|
|
1565
1893
|
const agentResults = {};
|
|
1566
1894
|
for (const { agent, amount: agentAmount } of agentAmounts) {
|
|
1567
|
-
agentResults[agent.id] = await
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1895
|
+
agentResults[agent.id] = await this.depositWithFallback(
|
|
1896
|
+
agent,
|
|
1897
|
+
state,
|
|
1898
|
+
chainId,
|
|
1899
|
+
agentAmount,
|
|
1900
|
+
asset,
|
|
1901
|
+
effectiveCallback,
|
|
1902
|
+
depositCallback
|
|
1571
1903
|
);
|
|
1572
1904
|
}
|
|
1573
1905
|
return { agentResults };
|
|
1574
1906
|
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Invokes `agent.deposit` with the resolved sponsored callback, composing
|
|
1909
|
+
* two independent auto-recovery mechanisms:
|
|
1910
|
+
*
|
|
1911
|
+
* 1. Missing Permit2 allowance: when the app did not supply its own
|
|
1912
|
+
* callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
|
|
1913
|
+
* WETH deposit, this is the wallet's first gasless WETH deposit. We send
|
|
1914
|
+
* the one-time (user-paid) Permit2 approval via `approvePermit2()` and
|
|
1915
|
+
* retry the SAME sponsored attempt once. Bounded to one approval attempt
|
|
1916
|
+
* per call so a wallet/agent that keeps reporting the allowance as
|
|
1917
|
+
* missing can't loop forever. If `approvePermit2()` itself throws (e.g.
|
|
1918
|
+
* the user rejects the wallet prompt), that error propagates as-is —
|
|
1919
|
+
* the user said no to a transaction, so we must not turn around and ask
|
|
1920
|
+
* them to pay for a different one via the fallback below.
|
|
1921
|
+
* 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
|
|
1922
|
+
* the infrastructure layer in a provably safe-to-retry way, so we warn
|
|
1923
|
+
* and retry once with `undefined`, falling through to a user-paid
|
|
1924
|
+
* native deposit. This still applies after a successful Permit2
|
|
1925
|
+
* approval retry (the retried sponsored attempt can itself hit a safe
|
|
1926
|
+
* sponsor failure).
|
|
1927
|
+
*
|
|
1928
|
+
* Any other error — an app-supplied callback's own failure in particular —
|
|
1929
|
+
* propagates unchanged. Scoped per agent invocation so one agent's failure
|
|
1930
|
+
* doesn't force a retry of sibling agents in a multi-agent split.
|
|
1931
|
+
*/
|
|
1932
|
+
async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
|
|
1933
|
+
const attempt = (callback) => withFailureReporting(
|
|
1934
|
+
this.apiKey,
|
|
1935
|
+
agent.id,
|
|
1936
|
+
() => agent.deposit(
|
|
1937
|
+
state,
|
|
1938
|
+
chainId,
|
|
1939
|
+
amount,
|
|
1940
|
+
asset,
|
|
1941
|
+
callback
|
|
1942
|
+
)
|
|
1943
|
+
);
|
|
1944
|
+
let approvalAttempted = false;
|
|
1945
|
+
for (; ; ) {
|
|
1946
|
+
try {
|
|
1947
|
+
return await attempt(effectiveCallback);
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
|
|
1950
|
+
approvalAttempted = true;
|
|
1951
|
+
console.warn(
|
|
1952
|
+
"[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
|
|
1953
|
+
);
|
|
1954
|
+
await this.approvePermit2();
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
if (!shouldFallbackToUserPaid(error, asset, appCallback))
|
|
1958
|
+
throw error;
|
|
1959
|
+
console.warn(
|
|
1960
|
+
"[owney-sdk] WETH sponsorship unavailable, falling back to user-paid deposit:",
|
|
1961
|
+
error
|
|
1962
|
+
);
|
|
1963
|
+
return attempt(void 0);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1575
1967
|
getMinDepositAmount(agent, chainId, asset) {
|
|
1576
1968
|
const chainAssets = agent.supportedAssets.find(
|
|
1577
1969
|
(sa) => sa.chainId === chainId
|
|
@@ -2059,6 +2451,66 @@ var OwneySDK = class {
|
|
|
2059
2451
|
agentUserProfile: results
|
|
2060
2452
|
};
|
|
2061
2453
|
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Ensure the given asset uses backend protocol auto-selection for the active
|
|
2456
|
+
* chain's account. No-op (returns false) for agents that don't support it.
|
|
2457
|
+
* @param asset - "USDC" or "WETH".
|
|
2458
|
+
* @param agentId - Agent to target. Defaults to "zyfai".
|
|
2459
|
+
* @returns whether a profile write occurred.
|
|
2460
|
+
*/
|
|
2461
|
+
async ensureAutoSelectProtocols(asset, agentId = "zyfai") {
|
|
2462
|
+
const state = this.requireState();
|
|
2463
|
+
const chainId = this.requireChainId();
|
|
2464
|
+
const agent = this.getAgent(agentId);
|
|
2465
|
+
if (typeof agent.ensureAutoSelectProtocols !== "function")
|
|
2466
|
+
return false;
|
|
2467
|
+
return agent.ensureAutoSelectProtocols(state, chainId, asset);
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2470
|
+
* One-time, user-paid approval of Permit2 on the sponsored WETH token for
|
|
2471
|
+
* the active chain. Required once per wallet per chain before gasless WETH
|
|
2472
|
+
* deposits; afterwards deposit() is signature-only. Resolves only after the
|
|
2473
|
+
* approval transaction is mined (1 confirmation), so a subsequent deposit()
|
|
2474
|
+
* will see the new allowance; throws if the transaction reverted.
|
|
2475
|
+
* @returns the approval transaction hash.
|
|
2476
|
+
*/
|
|
2477
|
+
async approvePermit2(asset = "WETH") {
|
|
2478
|
+
const state = this.requireState();
|
|
2479
|
+
const chainId = this.requireChainId();
|
|
2480
|
+
const token = SPONSORED_WETH_BY_CHAIN[chainId];
|
|
2481
|
+
if (!token) {
|
|
2482
|
+
throw new OwneyError(
|
|
2483
|
+
"CHAIN_UNSUPPORTED",
|
|
2484
|
+
`No sponsored WETH on chain ${chainId}`
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
const provider = this.requireConnectedProvider();
|
|
2488
|
+
const wallet = createWalletClient({
|
|
2489
|
+
account: state.walletAddress,
|
|
2490
|
+
chain: VIEM_CHAIN2[chainId],
|
|
2491
|
+
transport: custom(provider)
|
|
2492
|
+
});
|
|
2493
|
+
const hash = await wallet.writeContract({
|
|
2494
|
+
address: token,
|
|
2495
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2496
|
+
functionName: "approve",
|
|
2497
|
+
args: [PERMIT2_ADDRESS, MAX_UINT256],
|
|
2498
|
+
account: state.walletAddress,
|
|
2499
|
+
chain: VIEM_CHAIN2[chainId]
|
|
2500
|
+
});
|
|
2501
|
+
const publicClient = createPublicClient2({
|
|
2502
|
+
chain: VIEM_CHAIN2[chainId],
|
|
2503
|
+
transport: custom(provider)
|
|
2504
|
+
});
|
|
2505
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
2506
|
+
hash,
|
|
2507
|
+
confirmations: 1
|
|
2508
|
+
});
|
|
2509
|
+
if (receipt.status !== "success") {
|
|
2510
|
+
throw new Error(`Permit2 approval reverted (tx ${hash})`);
|
|
2511
|
+
}
|
|
2512
|
+
return hash;
|
|
2513
|
+
}
|
|
2062
2514
|
// --- Discovery (no wallet required) ---
|
|
2063
2515
|
/**
|
|
2064
2516
|
* Get the agent's average APY performance over a time period. Does not require a wallet connection.
|