@owney/sdk 0.6.4-beta.0 → 0.6.5-beta.0

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 CHANGED
@@ -40,7 +40,7 @@ var P_SUPPORTED_CHAINS = {
40
40
  chainIds: [8453, 42161],
41
41
  chains: ["BASE", "ARBITRUM"]
42
42
  };
43
- var SUPPORTED_TOKENS = ["USDC", "USDT"];
43
+ var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
44
44
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
45
45
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
46
46
  var SupportedAssets = [
@@ -61,6 +61,18 @@ var SupportedAssets = [
61
61
  symbol: "USDT",
62
62
  chainId: 42161,
63
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
64
76
  }
65
77
  ];
66
78
 
@@ -545,11 +557,26 @@ var clearSession = (address, chainId) => {
545
557
  }
546
558
  };
547
559
 
560
+ // src/agents/zyfai/autoSelect.ts
561
+ function buildAutoSelectUpdate(current, asset) {
562
+ if (current.autoSelectProtocols === true)
563
+ return null;
564
+ return {
565
+ asset,
566
+ autoSelectProtocols: true,
567
+ ...current.chains ? { chains: current.chains } : {}
568
+ };
569
+ }
570
+
548
571
  // src/agents/zyfai/zyfai.agent.ts
549
572
  var DEFAULT_ZYFAI_RPC_URLS = {
550
573
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
551
574
  42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
552
575
  };
576
+ var WETH_ADDRESS_BY_CHAIN = {
577
+ 8453: "0x4200000000000000000000000000000000000006",
578
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
579
+ };
553
580
  var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum };
554
581
  var ZyfaiAgent = class {
555
582
  id = "zyfai";
@@ -558,12 +585,18 @@ var ZyfaiAgent = class {
558
585
  {
559
586
  chainId: 8453,
560
587
  chain: "BASE",
561
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
588
+ assets: [
589
+ { symbol: "USDC", minDepositAmount: "1500000" },
590
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
591
+ ]
562
592
  },
563
593
  {
564
594
  chainId: 42161,
565
595
  chain: "ARBITRUM",
566
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
596
+ assets: [
597
+ { symbol: "USDC", minDepositAmount: "1500000" },
598
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
599
+ ]
567
600
  }
568
601
  ];
569
602
  sdk;
@@ -758,6 +791,22 @@ var ZyfaiAgent = class {
758
791
  this.smartWalletCache.set(`${address}_${validChainId}`, wallet.address);
759
792
  if (!wallet.isDeployed) {
760
793
  await this.sdk.deploySafe(address, validChainId, "aggressive", true);
794
+ try {
795
+ if (validChainId === 42161) {
796
+ await this.sdk.updateUserProfile({
797
+ asset: "WETH",
798
+ chains: [validChainId],
799
+ protocols: ["c24a33fa-4a87-4376-b756-93bd3957d3ea"],
800
+ // AAVE protocol ID
801
+ autoSelectProtocols: false
802
+ });
803
+ }
804
+ } catch (err) {
805
+ console.warn(
806
+ "Failed to configure AAVE protocol after Safe deployment:",
807
+ err instanceof Error ? err.message : String(err)
808
+ );
809
+ }
761
810
  }
762
811
  const user = await this.sdk.getUserDetails();
763
812
  if (!user.hasActiveSessionKey) {
@@ -799,7 +848,7 @@ var ZyfaiAgent = class {
799
848
  throw error;
800
849
  }
801
850
  }
802
- async deposit(state, chainId, amount, depositCallback) {
851
+ async deposit(state, chainId, amount, asset, depositCallback) {
803
852
  try {
804
853
  await this.connectFull(state, chainId);
805
854
  const validChainId = isValidChainId(chainId);
@@ -813,13 +862,15 @@ var ZyfaiAgent = class {
813
862
  validChainId,
814
863
  amount
815
864
  );
816
- await this.sdk.logDeposit(validChainId, txHash, amount);
865
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
866
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
817
867
  return { txHash, smartWallet: wallet.address, amount };
818
868
  }
819
869
  const raw = await this.sdk.depositFunds(
820
870
  this.getAddress(),
821
871
  validChainId,
822
- amount
872
+ amount,
873
+ asset
823
874
  );
824
875
  return mapDeposit(raw);
825
876
  } catch (error) {
@@ -915,6 +966,21 @@ var ZyfaiAgent = class {
915
966
  }
916
967
  return mapUserProfile(raw, this.connectedAddress);
917
968
  }
969
+ async ensureAutoSelectProtocols(state, chainId, asset) {
970
+ await this.connectAuth(state, chainId);
971
+ const current = await this.sdk.getUserDetails(asset);
972
+ const update = buildAutoSelectUpdate(
973
+ {
974
+ autoSelectProtocols: current.autoSelectProtocols,
975
+ chains: current.chains
976
+ },
977
+ asset
978
+ );
979
+ if (!update)
980
+ return false;
981
+ await this.sdk.updateUserProfile(update);
982
+ return true;
983
+ }
918
984
  // --- IAgent: Discovery (no wallet required) ---
919
985
  async getAgentApy(days, options) {
920
986
  const raw = await this.sdk.getAPYPerStrategy(
@@ -1118,7 +1184,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1118
1184
  }
1119
1185
 
1120
1186
  // src/client.ts
1121
- var import_viem4 = require("viem");
1187
+ var import_viem5 = require("viem");
1122
1188
  var import_chains2 = require("viem/chains");
1123
1189
 
1124
1190
  // src/lib/transfer-auth.ts
@@ -1190,6 +1256,70 @@ async function postSponsorTransferAuth(input) {
1190
1256
  }
1191
1257
  return parsed.data;
1192
1258
  }
1259
+ async function postSponsorPermit2Transfer(input) {
1260
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1261
+ let res;
1262
+ try {
1263
+ res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
1264
+ method: "POST",
1265
+ headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1266
+ body: JSON.stringify(input.body)
1267
+ });
1268
+ } catch (networkError) {
1269
+ throw new OwneyError(
1270
+ "SPONSOR_REQUEST_FAILED",
1271
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1272
+ { cause: String(networkError), safeToFallback: false }
1273
+ );
1274
+ }
1275
+ const text = await res.text();
1276
+ let parsed = null;
1277
+ try {
1278
+ parsed = JSON.parse(text);
1279
+ } catch {
1280
+ }
1281
+ if (!res.ok || !parsed?.success || !parsed.data) {
1282
+ throw new OwneyError(
1283
+ "SPONSOR_REQUEST_FAILED",
1284
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1285
+ {
1286
+ statusCode: res.status,
1287
+ responseBody: text.slice(0, 500),
1288
+ safeToFallback: res.status >= 400 && res.status < 500
1289
+ }
1290
+ );
1291
+ }
1292
+ return parsed.data;
1293
+ }
1294
+ async function getSponsorRelayerAddress(input) {
1295
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1296
+ let res;
1297
+ try {
1298
+ res = await fetch(`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`, {
1299
+ headers: { "x-owney-api-key": input.apiKey }
1300
+ });
1301
+ } catch (networkError) {
1302
+ throw new OwneyError(
1303
+ "SPONSOR_REQUEST_FAILED",
1304
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1305
+ { cause: String(networkError), safeToFallback: true }
1306
+ );
1307
+ }
1308
+ const text = await res.text();
1309
+ let parsed = null;
1310
+ try {
1311
+ parsed = JSON.parse(text);
1312
+ } catch {
1313
+ }
1314
+ if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
1315
+ throw new OwneyError(
1316
+ "SPONSOR_REQUEST_FAILED",
1317
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1318
+ { statusCode: res.status, responseBody: text.slice(0, 500), safeToFallback: true }
1319
+ );
1320
+ }
1321
+ return parsed.data.relayer;
1322
+ }
1193
1323
 
1194
1324
  // src/lib/sponsored-deposit.ts
1195
1325
  var AUTH_WINDOW_SECONDS = 15 * 60;
@@ -1241,6 +1371,114 @@ function makeSponsoredDepositCallback(deps) {
1241
1371
  };
1242
1372
  }
1243
1373
 
1374
+ // src/lib/permit2.ts
1375
+ var import_viem4 = require("viem");
1376
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
1377
+ var MAX_UINT256 = 2n ** 256n - 1n;
1378
+ var ERC20_ALLOWANCE_ABI = [
1379
+ {
1380
+ type: "function",
1381
+ name: "allowance",
1382
+ stateMutability: "view",
1383
+ inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }],
1384
+ outputs: [{ name: "", type: "uint256" }]
1385
+ },
1386
+ {
1387
+ type: "function",
1388
+ name: "approve",
1389
+ stateMutability: "nonpayable",
1390
+ inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
1391
+ outputs: [{ name: "", type: "bool" }]
1392
+ }
1393
+ ];
1394
+ function buildPermitTransferFromTypedData(input) {
1395
+ return {
1396
+ domain: { name: "Permit2", chainId: input.chainId, verifyingContract: PERMIT2_ADDRESS },
1397
+ types: {
1398
+ PermitTransferFrom: [
1399
+ { name: "permitted", type: "TokenPermissions" },
1400
+ { name: "spender", type: "address" },
1401
+ { name: "nonce", type: "uint256" },
1402
+ { name: "deadline", type: "uint256" }
1403
+ ],
1404
+ TokenPermissions: [
1405
+ { name: "token", type: "address" },
1406
+ { name: "amount", type: "uint256" }
1407
+ ]
1408
+ },
1409
+ primaryType: "PermitTransferFrom",
1410
+ message: input.message
1411
+ };
1412
+ }
1413
+ function randomPermit2Nonce() {
1414
+ const bytes = new Uint8Array(32);
1415
+ globalThis.crypto.getRandomValues(bytes);
1416
+ return BigInt((0, import_viem4.bytesToHex)(bytes));
1417
+ }
1418
+ async function readPermit2Allowance(publicClient, token, owner) {
1419
+ return publicClient.readContract({
1420
+ address: token,
1421
+ abi: ERC20_ALLOWANCE_ABI,
1422
+ functionName: "allowance",
1423
+ args: [owner, PERMIT2_ADDRESS]
1424
+ });
1425
+ }
1426
+
1427
+ // src/lib/sponsored-weth-deposit.ts
1428
+ var PERMIT_WINDOW_SECONDS = 15 * 60;
1429
+ function makeSponsoredWethCallback(deps) {
1430
+ const get = deps.httpGet ?? getSponsorRelayerAddress;
1431
+ const post = deps.httpPost ?? postSponsorPermit2Transfer;
1432
+ return async (smartWallet, chainId, amount) => {
1433
+ const cid = chainId;
1434
+ const token = deps.tokenAddressByChain[cid];
1435
+ if (!token) {
1436
+ throw new OwneyError("CHAIN_UNSUPPORTED", `No sponsored WETH configured for chain ${chainId}`);
1437
+ }
1438
+ const amountWei = BigInt(amount);
1439
+ const pub = deps.getPublicClient(cid);
1440
+ const allowance = await readPermit2Allowance(pub, token, deps.ownerAddress);
1441
+ if (allowance < amountWei) {
1442
+ throw new OwneyError(
1443
+ "PERMIT2_APPROVAL_REQUIRED",
1444
+ "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
1445
+ { token, chainId: cid, allowance: allowance.toString(), amount }
1446
+ );
1447
+ }
1448
+ const relayer = await get({ baseUrl: deps.baseUrl, apiKey: deps.apiKey, chainId: cid });
1449
+ const nonce = randomPermit2Nonce();
1450
+ const deadline = BigInt(Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS);
1451
+ const typedData = buildPermitTransferFromTypedData({
1452
+ chainId: cid,
1453
+ message: {
1454
+ permitted: { token, amount: amountWei },
1455
+ spender: relayer,
1456
+ nonce,
1457
+ deadline
1458
+ }
1459
+ });
1460
+ const wallet = deps.getWalletClient(cid);
1461
+ const signature = await wallet.signTypedData(
1462
+ { account: deps.ownerAddress, ...typedData }
1463
+ );
1464
+ const result = await post({
1465
+ baseUrl: deps.baseUrl,
1466
+ apiKey: deps.apiKey,
1467
+ body: {
1468
+ chainId: cid,
1469
+ token,
1470
+ from: deps.ownerAddress,
1471
+ to: smartWallet,
1472
+ amount,
1473
+ nonce: nonce.toString(),
1474
+ deadline: deadline.toString(),
1475
+ signature
1476
+ }
1477
+ });
1478
+ return result.txHash;
1479
+ };
1480
+ }
1481
+
1244
1482
  // src/client.ts
1245
1483
  function encodeMultiAgentCursor(map) {
1246
1484
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -1270,6 +1508,13 @@ var VIEM_CHAIN2 = {
1270
1508
  8453: import_chains2.base,
1271
1509
  42161: import_chains2.arbitrum
1272
1510
  };
1511
+ var SPONSORED_WETH_BY_CHAIN = {
1512
+ 8453: "0x4200000000000000000000000000000000000006",
1513
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
1514
+ };
1515
+ function shouldFallbackToUserPaid(error, asset, appCallback) {
1516
+ return asset === "WETH" && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
1517
+ }
1273
1518
  var OwneySDK = class {
1274
1519
  agents = /* @__PURE__ */ new Map();
1275
1520
  activeAgents = /* @__PURE__ */ new Set();
@@ -1283,6 +1528,7 @@ var OwneySDK = class {
1283
1528
  apiKey;
1284
1529
  zyfaiRpcUrls;
1285
1530
  cachedSponsoredCallback = null;
1531
+ cachedWethSponsoredCallback = null;
1286
1532
  initializingAgentsPromise = null;
1287
1533
  constructor(config) {
1288
1534
  this.apiKey = config.apiKey;
@@ -1307,6 +1553,7 @@ var OwneySDK = class {
1307
1553
  }
1308
1554
  this.state = { provider, walletAddress, chainId: null };
1309
1555
  this.cachedSponsoredCallback = null;
1556
+ this.cachedWethSponsoredCallback = null;
1310
1557
  }
1311
1558
  /**
1312
1559
  * Disconnect from all agents and clear connection state.
@@ -1320,6 +1567,7 @@ var OwneySDK = class {
1320
1567
  this.disabledAgents.clear();
1321
1568
  this.state = null;
1322
1569
  this.cachedSponsoredCallback = null;
1570
+ this.cachedWethSponsoredCallback = null;
1323
1571
  }
1324
1572
  isAgentDisabled(agentId) {
1325
1573
  return this.disabledAgents.has(agentId);
@@ -1381,18 +1629,48 @@ var OwneySDK = class {
1381
1629
  // Casts work around viem's chain-narrowed Client vs the generic
1382
1630
  // PublicClient/WalletClient param types — structurally identical at
1383
1631
  // runtime, but the two share a name TS treats as unrelated.
1384
- getPublicClient: (cid) => (0, import_viem4.createPublicClient)({
1632
+ getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
1385
1633
  chain: VIEM_CHAIN2[cid],
1386
- transport: (0, import_viem4.custom)(provider)
1634
+ transport: (0, import_viem5.custom)(provider)
1387
1635
  }),
1388
- getWalletClient: (cid) => (0, import_viem4.createWalletClient)({
1636
+ getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
1389
1637
  account: owner,
1390
1638
  chain: VIEM_CHAIN2[cid],
1391
- transport: (0, import_viem4.custom)(provider)
1639
+ transport: (0, import_viem5.custom)(provider)
1392
1640
  })
1393
1641
  });
1394
1642
  return this.cachedSponsoredCallback;
1395
1643
  }
1644
+ /**
1645
+ * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
1646
+ * callback used when the caller omits `depositCallback` for a WETH
1647
+ * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
1648
+ * `PermitTransferFrom` instead of an EIP-3009 authorization.
1649
+ */
1650
+ getDefaultWethSponsoredCallback() {
1651
+ if (this.cachedWethSponsoredCallback)
1652
+ return this.cachedWethSponsoredCallback;
1653
+ const provider = this.requireConnectedProvider();
1654
+ const owner = this.state.walletAddress;
1655
+ this.cachedWethSponsoredCallback = makeSponsoredWethCallback({
1656
+ apiKey: this.apiKey,
1657
+ ownerAddress: owner,
1658
+ tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
1659
+ // Casts work around viem's chain-narrowed Client vs the generic
1660
+ // PublicClient/WalletClient param types — structurally identical at
1661
+ // runtime, but the two share a name TS treats as unrelated.
1662
+ getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
1663
+ chain: VIEM_CHAIN2[cid],
1664
+ transport: (0, import_viem5.custom)(provider)
1665
+ }),
1666
+ getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
1667
+ account: owner,
1668
+ chain: VIEM_CHAIN2[cid],
1669
+ transport: (0, import_viem5.custom)(provider)
1670
+ })
1671
+ });
1672
+ return this.cachedWethSponsoredCallback;
1673
+ }
1396
1674
  getAgent(agentId) {
1397
1675
  const agent = this.agents.get(agentId);
1398
1676
  if (!agent) {
@@ -1546,7 +1824,7 @@ var OwneySDK = class {
1546
1824
  }
1547
1825
  const state = this.requireState();
1548
1826
  const chainId = this.requireChainId();
1549
- const effectiveCallback = depositCallback ?? this.getDefaultSponsoredCallback();
1827
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback() : this.getDefaultSponsoredCallback());
1550
1828
  if (agentId) {
1551
1829
  if (this.isAgentDisabled(agentId)) {
1552
1830
  throw new OwneyError(
@@ -1558,10 +1836,14 @@ var OwneySDK = class {
1558
1836
  const agent = this.getAgent(agentId);
1559
1837
  this.validateAssetSupport(agent, chainId, asset);
1560
1838
  await this.validateMinDepositAmount(agent, state, chainId, asset, amount);
1561
- return withFailureReporting(
1562
- this.apiKey,
1563
- agent.id,
1564
- () => agent.deposit(state, chainId, amount, effectiveCallback)
1839
+ return this.depositWithFallback(
1840
+ agent,
1841
+ state,
1842
+ chainId,
1843
+ amount,
1844
+ asset,
1845
+ effectiveCallback,
1846
+ depositCallback
1565
1847
  );
1566
1848
  }
1567
1849
  const totalAmount = BigInt(amount);
@@ -1592,14 +1874,78 @@ var OwneySDK = class {
1592
1874
  }
1593
1875
  const agentResults = {};
1594
1876
  for (const { agent, amount: agentAmount } of agentAmounts) {
1595
- agentResults[agent.id] = await withFailureReporting(
1596
- this.apiKey,
1597
- agent.id,
1598
- () => agent.deposit(state, chainId, agentAmount, effectiveCallback)
1877
+ agentResults[agent.id] = await this.depositWithFallback(
1878
+ agent,
1879
+ state,
1880
+ chainId,
1881
+ agentAmount,
1882
+ asset,
1883
+ effectiveCallback,
1884
+ depositCallback
1599
1885
  );
1600
1886
  }
1601
1887
  return { agentResults };
1602
1888
  }
1889
+ /**
1890
+ * Invokes `agent.deposit` with the resolved sponsored callback, composing
1891
+ * two independent auto-recovery mechanisms:
1892
+ *
1893
+ * 1. Missing Permit2 allowance: when the app did not supply its own
1894
+ * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
1895
+ * WETH deposit, this is the wallet's first gasless WETH deposit. We send
1896
+ * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
1897
+ * retry the SAME sponsored attempt once. Bounded to one approval attempt
1898
+ * per call so a wallet/agent that keeps reporting the allowance as
1899
+ * missing can't loop forever. If `approvePermit2()` itself throws (e.g.
1900
+ * the user rejects the wallet prompt), that error propagates as-is —
1901
+ * the user said no to a transaction, so we must not turn around and ask
1902
+ * them to pay for a different one via the fallback below.
1903
+ * 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
1904
+ * the infrastructure layer in a provably safe-to-retry way, so we warn
1905
+ * and retry once with `undefined`, falling through to a user-paid
1906
+ * native deposit. This still applies after a successful Permit2
1907
+ * approval retry (the retried sponsored attempt can itself hit a safe
1908
+ * sponsor failure).
1909
+ *
1910
+ * Any other error — an app-supplied callback's own failure in particular —
1911
+ * propagates unchanged. Scoped per agent invocation so one agent's failure
1912
+ * doesn't force a retry of sibling agents in a multi-agent split.
1913
+ */
1914
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
1915
+ const attempt = (callback) => withFailureReporting(
1916
+ this.apiKey,
1917
+ agent.id,
1918
+ () => agent.deposit(
1919
+ state,
1920
+ chainId,
1921
+ amount,
1922
+ asset,
1923
+ callback
1924
+ )
1925
+ );
1926
+ let approvalAttempted = false;
1927
+ for (; ; ) {
1928
+ try {
1929
+ return await attempt(effectiveCallback);
1930
+ } catch (error) {
1931
+ if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
1932
+ approvalAttempted = true;
1933
+ console.warn(
1934
+ "[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
1935
+ );
1936
+ await this.approvePermit2();
1937
+ continue;
1938
+ }
1939
+ if (!shouldFallbackToUserPaid(error, asset, appCallback))
1940
+ throw error;
1941
+ console.warn(
1942
+ "[owney-sdk] WETH sponsorship unavailable, falling back to user-paid deposit:",
1943
+ error
1944
+ );
1945
+ return attempt(void 0);
1946
+ }
1947
+ }
1948
+ }
1603
1949
  getMinDepositAmount(agent, chainId, asset) {
1604
1950
  const chainAssets = agent.supportedAssets.find(
1605
1951
  (sa) => sa.chainId === chainId
@@ -2087,6 +2433,66 @@ var OwneySDK = class {
2087
2433
  agentUserProfile: results
2088
2434
  };
2089
2435
  }
2436
+ /**
2437
+ * Ensure the given asset uses backend protocol auto-selection for the active
2438
+ * chain's account. No-op (returns false) for agents that don't support it.
2439
+ * @param asset - "USDC" or "WETH".
2440
+ * @param agentId - Agent to target. Defaults to "zyfai".
2441
+ * @returns whether a profile write occurred.
2442
+ */
2443
+ async ensureAutoSelectProtocols(asset, agentId = "zyfai") {
2444
+ const state = this.requireState();
2445
+ const chainId = this.requireChainId();
2446
+ const agent = this.getAgent(agentId);
2447
+ if (typeof agent.ensureAutoSelectProtocols !== "function")
2448
+ return false;
2449
+ return agent.ensureAutoSelectProtocols(state, chainId, asset);
2450
+ }
2451
+ /**
2452
+ * One-time, user-paid approval of Permit2 on the sponsored WETH token for
2453
+ * the active chain. Required once per wallet per chain before gasless WETH
2454
+ * deposits; afterwards deposit() is signature-only. Resolves only after the
2455
+ * approval transaction is mined (1 confirmation), so a subsequent deposit()
2456
+ * will see the new allowance; throws if the transaction reverted.
2457
+ * @returns the approval transaction hash.
2458
+ */
2459
+ async approvePermit2(asset = "WETH") {
2460
+ const state = this.requireState();
2461
+ const chainId = this.requireChainId();
2462
+ const token = SPONSORED_WETH_BY_CHAIN[chainId];
2463
+ if (!token) {
2464
+ throw new OwneyError(
2465
+ "CHAIN_UNSUPPORTED",
2466
+ `No sponsored WETH on chain ${chainId}`
2467
+ );
2468
+ }
2469
+ const provider = this.requireConnectedProvider();
2470
+ const wallet = (0, import_viem5.createWalletClient)({
2471
+ account: state.walletAddress,
2472
+ chain: VIEM_CHAIN2[chainId],
2473
+ transport: (0, import_viem5.custom)(provider)
2474
+ });
2475
+ const hash = await wallet.writeContract({
2476
+ address: token,
2477
+ abi: ERC20_ALLOWANCE_ABI,
2478
+ functionName: "approve",
2479
+ args: [PERMIT2_ADDRESS, MAX_UINT256],
2480
+ account: state.walletAddress,
2481
+ chain: VIEM_CHAIN2[chainId]
2482
+ });
2483
+ const publicClient = (0, import_viem5.createPublicClient)({
2484
+ chain: VIEM_CHAIN2[chainId],
2485
+ transport: (0, import_viem5.custom)(provider)
2486
+ });
2487
+ const receipt = await publicClient.waitForTransactionReceipt({
2488
+ hash,
2489
+ confirmations: 1
2490
+ });
2491
+ if (receipt.status !== "success") {
2492
+ throw new Error(`Permit2 approval reverted (tx ${hash})`);
2493
+ }
2494
+ return hash;
2495
+ }
2090
2496
  // --- Discovery (no wallet required) ---
2091
2497
  /**
2092
2498
  * Get the agent's average APY performance over a time period. Does not require a wallet connection.
@@ -2185,7 +2591,7 @@ var OwneySDK = class {
2185
2591
  };
2186
2592
 
2187
2593
  // src/agents/zyfai/zyfai.siwx.ts
2188
- var import_viem5 = require("viem");
2594
+ var import_viem6 = require("viem");
2189
2595
  var import_siwe = require("siwe");
2190
2596
  var import_sdk2 = require("@zyfai/sdk");
2191
2597
 
@@ -2267,7 +2673,7 @@ function buildSIWXConfig(deps) {
2267
2673
  issuedAt,
2268
2674
  toString() {
2269
2675
  return new import_siwe.SiweMessage({
2270
- address: (0, import_viem5.getAddress)(accountAddress),
2676
+ address: (0, import_viem6.getAddress)(accountAddress),
2271
2677
  chainId: numericChainId(chainId),
2272
2678
  domain,
2273
2679
  uri,