@owney/sdk 0.7.7-beta.2 → 0.7.7-beta.4

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.js CHANGED
@@ -184,6 +184,32 @@ function resolveChainId(chain) {
184
184
  if (Number.isFinite(asNum) && asNum > 0) return asNum;
185
185
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
186
186
  }
187
+ function mapPendingAllocations(raw) {
188
+ if (!Array.isArray(raw)) return void 0;
189
+ const pending = [];
190
+ for (const entry of raw) {
191
+ if (typeof entry !== "object" || entry === null) continue;
192
+ const e = entry;
193
+ if (e.isPending !== true) continue;
194
+ if (typeof e.chainId !== "number" || !Number.isFinite(e.chainId)) continue;
195
+ if (typeof e.tokenSymbol !== "string" || e.tokenSymbol === "") continue;
196
+ if (typeof e.balance !== "string") continue;
197
+ let amount;
198
+ try {
199
+ amount = hexToDecimal(e.balance, tokenDecimals(e.tokenSymbol));
200
+ } catch {
201
+ continue;
202
+ }
203
+ if (!Number.isFinite(amount) || amount <= 0) continue;
204
+ pending.push({
205
+ chainId: e.chainId,
206
+ asset: e.tokenSymbol,
207
+ amount: String(amount),
208
+ ...typeof e.updatedAt === "string" && e.updatedAt !== "" ? { since: e.updatedAt } : {}
209
+ });
210
+ }
211
+ return pending.length > 0 ? pending : void 0;
212
+ }
187
213
  function mapBalances(raw, _chainId, smartWallet) {
188
214
  const portfolio = raw.portfolio;
189
215
  const portfolioByChain = portfolio.portfolioByChain ?? {};
@@ -235,12 +261,16 @@ function mapBalances(raw, _chainId, smartWallet) {
235
261
  // structural read keeps it undefined today and auto-populates later.
236
262
  liquidity: p.liquidity
237
263
  }));
264
+ const pendingAllocations = mapPendingAllocations(
265
+ portfolio.staleBalances
266
+ );
238
267
  return {
239
268
  smartWallet,
240
269
  totalBalance: String(totalBalance),
241
270
  totalBalanceAsset: "usdc",
242
271
  tokens,
243
- positions
272
+ positions,
273
+ ...pendingAllocations ? { pendingAllocations } : {}
244
274
  };
245
275
  }
246
276
  function sumTokenValues(tokens) {
@@ -846,6 +876,12 @@ var ZyfaiAgent = class {
846
876
  connectedAddress = null;
847
877
  connectedChainId = null;
848
878
  smartWalletCache = /* @__PURE__ */ new Map();
879
+ /**
880
+ * `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
881
+ * against re-running it on every activation/deposit when Zyfai's
882
+ * `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
883
+ */
884
+ sessionKeyAttempts = /* @__PURE__ */ new Set();
849
885
  connectAuthPromise = null;
850
886
  rpcUrls;
851
887
  publicClients = /* @__PURE__ */ new Map();
@@ -1303,10 +1339,74 @@ var ZyfaiAgent = class {
1303
1339
  * recoverable (ensureWalletDeployed tries the other deploy call), but
1304
1340
  * failing the deposit outright is not.
1305
1341
  */
1342
+ /**
1343
+ * Zyfai's own answer to "what kind of wallet is this, and can the agent act on
1344
+ * it", read from the positions payload (`GET /data/position`, which
1345
+ * `getPositions` wraps).
1346
+ *
1347
+ * `predeployed` and `hasActiveSessionKey` are both returned at runtime but
1348
+ * declared on neither `Portfolio` nor their docs, hence the cast. Verified
1349
+ * live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
1350
+ * false}` while a pool wallet reports `{true, true}`.
1351
+ *
1352
+ * Preferred over the SDK's private `isPredeployed`, which is cached from the
1353
+ * login response and can go stale mid-session. Returns an empty object on any
1354
+ * failure so every caller degrades to its own fallback.
1355
+ */
1356
+ async readWalletState(ownerAddress) {
1357
+ try {
1358
+ const { portfolio } = await this.sdk.getPositions(ownerAddress);
1359
+ const raw = portfolio;
1360
+ debugLog("zyfai:onboard", "wallet state from getPositions", {
1361
+ predeployed: raw?.predeployed,
1362
+ hasActiveSessionKey: raw?.hasActiveSessionKey
1363
+ });
1364
+ return {
1365
+ predeployed: raw?.predeployed,
1366
+ hasActiveSessionKey: raw?.hasActiveSessionKey
1367
+ };
1368
+ } catch (error) {
1369
+ console.warn(
1370
+ "[zyfai] getPositions wallet-state read failed (non-fatal):",
1371
+ error instanceof Error ? error.message : String(error)
1372
+ );
1373
+ return {};
1374
+ }
1375
+ }
1306
1376
  async isPredeployedPoolWallet(ownerAddress, smartWallet) {
1377
+ const { predeployed } = await this.readWalletState(ownerAddress);
1378
+ if (typeof predeployed === "boolean") {
1379
+ debugLog(
1380
+ "zyfai:onboard",
1381
+ `wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1382
+ { source: "getPositions.predeployed", predeployed }
1383
+ );
1384
+ return predeployed;
1385
+ }
1386
+ const internals = this.sdk;
1387
+ if (typeof internals.isPredeployed === "boolean") {
1388
+ debugLog(
1389
+ "zyfai:onboard",
1390
+ `wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1391
+ {
1392
+ source: "login response (cached)",
1393
+ predeployed: internals.isPredeployed
1394
+ }
1395
+ );
1396
+ return internals.isPredeployed;
1397
+ }
1307
1398
  try {
1308
1399
  const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
1309
- return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
1400
+ const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
1401
+ debugLog(
1402
+ "zyfai:onboard",
1403
+ `wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1404
+ {
1405
+ source: "getSmartWalletByEOA fallback",
1406
+ poolRecord: byEoa?.smartWallet
1407
+ }
1408
+ );
1409
+ return isPool;
1310
1410
  } catch (error) {
1311
1411
  console.warn(
1312
1412
  "[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
@@ -1344,8 +1444,24 @@ var ZyfaiAgent = class {
1344
1444
  ownerAddress,
1345
1445
  chainId
1346
1446
  );
1347
- if (!wallet.isDeployed) return;
1348
- await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1447
+ debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
1448
+ smartWallet: wallet.address,
1449
+ isDeployed: wallet.isDeployed
1450
+ });
1451
+ if (!wallet.isDeployed) {
1452
+ debugLog(
1453
+ "zyfai:onboard",
1454
+ "Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
1455
+ );
1456
+ return;
1457
+ }
1458
+ const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1459
+ await this.ensureSessionKey(
1460
+ ownerAddress,
1461
+ chainId,
1462
+ wallet.address,
1463
+ hasActiveSessionKey
1464
+ );
1349
1465
  } catch (error) {
1350
1466
  console.warn(
1351
1467
  "[zyfai] session-key check at activation failed (non-fatal):",
@@ -1353,20 +1469,51 @@ var ZyfaiAgent = class {
1353
1469
  );
1354
1470
  }
1355
1471
  }
1356
- async ensureSessionKey(ownerAddress, chainId, smartWallet) {
1472
+ async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
1473
+ const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
1474
+ if (this.sessionKeyAttempts.has(attemptKey)) {
1475
+ debugLog(
1476
+ "zyfai:onboard",
1477
+ "session key: already attempted this session, skipping",
1478
+ { chainId }
1479
+ );
1480
+ return;
1481
+ }
1357
1482
  try {
1358
- const installed = await this.getPublicClient(chainId).readContract({
1359
- address: smartWallet,
1360
- abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1361
- functionName: "isModuleInstalled",
1362
- // moduleTypeId 1 = validator (ERC-7579)
1363
- args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
1364
- });
1365
- if (installed) return;
1483
+ if (hasActiveSessionKey === true) {
1484
+ debugLog(
1485
+ "zyfai:onboard",
1486
+ "session key: already on record -> nothing to do",
1487
+ { chainId }
1488
+ );
1489
+ return;
1490
+ }
1491
+ if (hasActiveSessionKey === void 0) {
1492
+ const installed = await this.readSmartSessionsInstalled(
1493
+ chainId,
1494
+ smartWallet
1495
+ );
1496
+ debugLog(
1497
+ "zyfai:onboard",
1498
+ `session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
1499
+ { smartWallet, chainId }
1500
+ );
1501
+ if (installed) return;
1502
+ }
1503
+ this.sessionKeyAttempts.add(attemptKey);
1366
1504
  console.warn(
1367
- `[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
1505
+ `[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
1368
1506
  );
1507
+ debugLog("zyfai:onboard", "session key: calling createSessionKey", {
1508
+ chainId,
1509
+ smartWallet,
1510
+ hasActiveSessionKey
1511
+ });
1369
1512
  await this.sdk.createSessionKey(ownerAddress, chainId);
1513
+ debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
1514
+ chainId,
1515
+ smartWallet
1516
+ });
1370
1517
  } catch (error) {
1371
1518
  console.warn(
1372
1519
  "[zyfai] createSessionKey skipped (non-fatal):",
@@ -1374,6 +1521,16 @@ var ZyfaiAgent = class {
1374
1521
  );
1375
1522
  }
1376
1523
  }
1524
+ /** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
1525
+ async readSmartSessionsInstalled(chainId, smartWallet) {
1526
+ return this.getPublicClient(chainId).readContract({
1527
+ address: smartWallet,
1528
+ abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1529
+ functionName: "isModuleInstalled",
1530
+ // moduleTypeId 1 = validator (ERC-7579)
1531
+ args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
1532
+ });
1533
+ }
1377
1534
  /**
1378
1535
  * Guarantee the user's Safe has code on `chainId` before anything transfers
1379
1536
  * funds into it, and return its address.
@@ -1394,8 +1551,24 @@ var ZyfaiAgent = class {
1394
1551
  */
1395
1552
  async ensureWalletDeployed(ownerAddress, chainId) {
1396
1553
  const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
1554
+ debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
1555
+ eoa: ownerAddress,
1556
+ smartWallet: wallet.address,
1557
+ isDeployed: wallet.isDeployed,
1558
+ isOwner: wallet.isOwner
1559
+ });
1397
1560
  if (wallet.isDeployed) {
1398
- await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1561
+ debugLog(
1562
+ "zyfai:onboard",
1563
+ "Safe already has code -> no deploy needed, checking the session key"
1564
+ );
1565
+ const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1566
+ await this.ensureSessionKey(
1567
+ ownerAddress,
1568
+ chainId,
1569
+ wallet.address,
1570
+ hasActiveSessionKey
1571
+ );
1399
1572
  return wallet.address;
1400
1573
  }
1401
1574
  const poolFirst = await this.isPredeployedPoolWallet(
@@ -1411,14 +1584,27 @@ var ZyfaiAgent = class {
1411
1584
  this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
1412
1585
  );
1413
1586
  const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
1587
+ debugLog(
1588
+ "zyfai:onboard",
1589
+ `Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
1590
+ );
1414
1591
  try {
1415
1592
  await first();
1593
+ debugLog(
1594
+ "zyfai:onboard",
1595
+ `${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
1596
+ );
1416
1597
  } catch (error) {
1417
1598
  console.warn(
1418
1599
  `[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
1419
1600
  error instanceof Error ? error.message : String(error)
1420
1601
  );
1602
+ debugLog(
1603
+ "zyfai:onboard",
1604
+ `falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
1605
+ );
1421
1606
  await second();
1607
+ debugLog("zyfai:onboard", "fallback deploy path succeeded");
1422
1608
  }
1423
1609
  const redeployed = await this.sdk.getSmartWalletAddress(
1424
1610
  ownerAddress,
@@ -1883,12 +2069,9 @@ async function postSponsorTransferAuth(input) {
1883
2069
  {
1884
2070
  statusCode: res.status,
1885
2071
  responseBody: text.slice(0, 500),
1886
- // A 4xx is an explicit refusal before execution (DTO validation,
1887
- // signature rejection, simulation) — nothing was broadcast, so a
1888
- // user-paid retry cannot double-spend. 503 SPONSOR_UNAVAILABLE means
1889
- // the relayer was out of gas, also pre-broadcast. Any other 5xx stays
1890
- // ambiguous: the transfer may already be in flight.
1891
- safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
2072
+ // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
2073
+ // before broadcast, so it is safe to fall back to a user-paid deposit.
2074
+ safeToFallback: res.status === 503
1892
2075
  }
1893
2076
  );
1894
2077
  }
@@ -2160,9 +2343,9 @@ function makeSponsoredDepositCallback(deps) {
2160
2343
  };
2161
2344
  }
2162
2345
 
2163
- // src/lib/sponsored-permit2-deposit.ts
2346
+ // src/lib/sponsored-weth-deposit.ts
2164
2347
  var PERMIT_WINDOW_SECONDS = 15 * 60;
2165
- function makeSponsoredPermit2Callback(deps) {
2348
+ function makeSponsoredWethCallback(deps) {
2166
2349
  const get = deps.httpGet ?? getSponsorRelayerAddress;
2167
2350
  const post = deps.httpPost ?? postSponsorPermit2Transfer;
2168
2351
  return async (smartWallet, chainId, amount) => {
@@ -2171,7 +2354,7 @@ function makeSponsoredPermit2Callback(deps) {
2171
2354
  if (!token) {
2172
2355
  throw new OwneyError(
2173
2356
  "CHAIN_UNSUPPORTED",
2174
- `No sponsored ${deps.assetLabel} configured for chain ${chainId}`
2357
+ `No sponsored WETH configured for chain ${chainId}`
2175
2358
  );
2176
2359
  }
2177
2360
  const amountWei = BigInt(amount);
@@ -2183,14 +2366,14 @@ function makeSponsoredPermit2Callback(deps) {
2183
2366
  if (balance < amountWei) {
2184
2367
  throw new OwneyError(
2185
2368
  "DEPOSIT_INSUFFICIENT_BALANCE",
2186
- `Insufficient ${deps.assetLabel} balance for this deposit.`,
2369
+ "Insufficient WETH balance for this deposit.",
2187
2370
  { token, chainId: cid, balance: balance.toString(), amount }
2188
2371
  );
2189
2372
  }
2190
2373
  } catch (err) {
2191
2374
  if (err instanceof OwneyError) throw err;
2192
2375
  console.warn(
2193
- `[owney-sdk] ${deps.assetLabel} balance pre-check failed (non-fatal):`,
2376
+ "[owney-sdk] WETH balance pre-check failed (non-fatal):",
2194
2377
  err instanceof Error ? err.message : String(err)
2195
2378
  );
2196
2379
  }
@@ -2198,7 +2381,7 @@ function makeSponsoredPermit2Callback(deps) {
2198
2381
  if (allowance < amountWei) {
2199
2382
  throw new OwneyError(
2200
2383
  "PERMIT2_APPROVAL_REQUIRED",
2201
- `${deps.assetLabel} gasless deposits need a one-time Permit2 approval; call approvePermit2("${deps.assetLabel}") first`,
2384
+ "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
2202
2385
  { token, chainId: cid, allowance: allowance.toString(), amount }
2203
2386
  );
2204
2387
  }
@@ -2279,8 +2462,6 @@ var SPONSORED_WETH_BY_CHAIN = {
2279
2462
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
2280
2463
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
2281
2464
  };
2282
- var PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS = 10;
2283
- var PERMIT2_ALLOWANCE_POLL_INTERVAL_MS = 1500;
2284
2465
  function shouldFallbackToUserPaid(error, asset, appCallback) {
2285
2466
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
2286
2467
  }
@@ -2305,7 +2486,7 @@ var OwneySDK = class {
2305
2486
  routingApiBaseUrl;
2306
2487
  referralSource;
2307
2488
  cachedSponsoredCallback = null;
2308
- cachedPermit2Callbacks = /* @__PURE__ */ new Map();
2489
+ cachedWethSponsoredCallback = null;
2309
2490
  initializingAgentsPromise = null;
2310
2491
  constructor(config) {
2311
2492
  this.apiKey = config.apiKey;
@@ -2333,7 +2514,7 @@ var OwneySDK = class {
2333
2514
  }
2334
2515
  this.state = { provider, walletAddress, chainId: null };
2335
2516
  this.cachedSponsoredCallback = null;
2336
- this.cachedPermit2Callbacks.clear();
2517
+ this.cachedWethSponsoredCallback = null;
2337
2518
  }
2338
2519
  /**
2339
2520
  * Disconnect from all agents and clear connection state.
@@ -2347,7 +2528,7 @@ var OwneySDK = class {
2347
2528
  this.disabledAgents.clear();
2348
2529
  this.state = null;
2349
2530
  this.cachedSponsoredCallback = null;
2350
- this.cachedPermit2Callbacks.clear();
2531
+ this.cachedWethSponsoredCallback = null;
2351
2532
  }
2352
2533
  isAgentDisabled(agentId) {
2353
2534
  return this.disabledAgents.has(agentId);
@@ -2424,23 +2605,25 @@ var OwneySDK = class {
2424
2605
  return callback;
2425
2606
  }
2426
2607
  /**
2427
- * Lazily builds (and caches) the Permit2 sponsored callback for one asset.
2428
- * Used for WETH always, and for USDC when the connected owner is a smart
2429
- * account — EIP-3009 verifies with ecrecover inside USDC and cannot accept a
2430
- * contract signature, while Permit2 supports ERC-1271.
2608
+ * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
2609
+ * callback used when the caller omits `depositCallback` for a WETH
2610
+ * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2611
+ * `PermitTransferFrom` instead of an EIP-3009 authorization.
2431
2612
  */
2432
- getDefaultPermit2Callback(asset, onApproved) {
2433
- const cached = this.cachedPermit2Callbacks.get(asset);
2434
- if (!onApproved && cached) return cached;
2613
+ getDefaultWethSponsoredCallback(onApproved) {
2614
+ if (!onApproved && this.cachedWethSponsoredCallback)
2615
+ return this.cachedWethSponsoredCallback;
2435
2616
  const provider = this.requireConnectedProvider();
2436
2617
  const owner = this.state.walletAddress;
2437
- const callback = makeSponsoredPermit2Callback({
2618
+ const callback = makeSponsoredWethCallback({
2438
2619
  apiKey: this.apiKey,
2439
2620
  baseUrl: this.routingApiBaseUrl,
2440
2621
  ownerAddress: owner,
2441
- assetLabel: asset,
2442
2622
  onApproved,
2443
- tokenAddressByChain: asset === "USDC" ? SPONSORED_USDC_BY_CHAIN : SPONSORED_WETH_BY_CHAIN,
2623
+ tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
2624
+ // Casts work around viem's chain-narrowed Client vs the generic
2625
+ // PublicClient/WalletClient param types — structurally identical at
2626
+ // runtime, but the two share a name TS treats as unrelated.
2444
2627
  getPublicClient: (cid) => createPublicClient2({
2445
2628
  chain: VIEM_CHAIN2[cid],
2446
2629
  transport: custom(provider)
@@ -2451,34 +2634,9 @@ var OwneySDK = class {
2451
2634
  transport: custom(provider)
2452
2635
  })
2453
2636
  });
2454
- if (!onApproved) this.cachedPermit2Callbacks.set(asset, callback);
2637
+ if (!onApproved) this.cachedWethSponsoredCallback = callback;
2455
2638
  return callback;
2456
2639
  }
2457
- /**
2458
- * True when the connected owner has bytecode — a deployed smart wallet, or an
2459
- * EOA carrying an EIP-7702 delegation (the Base app's "Upgrade your wallet").
2460
- * Such wallets sign via ERC-1271, which EIP-3009 cannot verify.
2461
- *
2462
- * Fails OPEN: an unreadable code check returns false so a working EOA deposit
2463
- * is never blocked by a flaky provider.
2464
- */
2465
- async ownerHasCode() {
2466
- try {
2467
- const provider = this.requireConnectedProvider();
2468
- const code = await provider.request({
2469
- method: "eth_getCode",
2470
- params: [this.state.walletAddress, "latest"]
2471
- });
2472
- return Boolean(code) && code !== "0x";
2473
- } catch (error) {
2474
- debugLog(
2475
- "deposit",
2476
- "owner code check failed; assuming EOA",
2477
- error instanceof Error ? error.message : String(error)
2478
- );
2479
- return false;
2480
- }
2481
- }
2482
2640
  getAgent(agentId) {
2483
2641
  const agent = this.agents.get(agentId);
2484
2642
  if (!agent) {
@@ -2674,11 +2832,7 @@ var OwneySDK = class {
2674
2832
  }
2675
2833
  const state = this.requireState();
2676
2834
  const chainId = this.requireChainId();
2677
- const ownerIsSmartAccount = depositCallback === void 0 && asset !== "WETH" ? await this.ownerHasCode() : false;
2678
- const effectiveCallback = depositCallback ?? (asset === "WETH" || ownerIsSmartAccount ? this.getDefaultPermit2Callback(
2679
- asset === "WETH" ? "WETH" : "USDC",
2680
- onApproved
2681
- ) : this.getDefaultSponsoredCallback(onApproved));
2835
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2682
2836
  if (agentId) {
2683
2837
  if (this.isAgentDisabled(agentId)) {
2684
2838
  throw new OwneyError(
@@ -2747,16 +2901,13 @@ var OwneySDK = class {
2747
2901
  * 1. Missing Permit2 allowance: when the app did not supply its own
2748
2902
  * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
2749
2903
  * WETH deposit, this is the wallet's first gasless WETH deposit. We send
2750
- * the one-time (user-paid) Permit2 approval via `approvePermit2()`, wait
2751
- * for the new allowance to actually be readable via
2752
- * `waitForPermit2Allowance()` (the wallet's own RPC can lag a freshly
2753
- * mined tx), then retry the SAME sponsored attempt once. Bounded to one
2754
- * approval attempt per call so a wallet/agent that keeps reporting the
2755
- * allowance as missing can't loop forever. If `approvePermit2()` itself
2756
- * throws (e.g. the user rejects the wallet prompt), that error
2757
- * propagates as-is — the user said no to a transaction, so we must not
2758
- * turn around and ask them to pay for a different one via the fallback
2759
- * below.
2904
+ * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
2905
+ * retry the SAME sponsored attempt once. Bounded to one approval attempt
2906
+ * per call so a wallet/agent that keeps reporting the allowance as
2907
+ * missing can't loop forever. If `approvePermit2()` itself throws (e.g.
2908
+ * the user rejects the wallet prompt), that error propagates as-is —
2909
+ * the user said no to a transaction, so we must not turn around and ask
2910
+ * them to pay for a different one via the fallback below.
2760
2911
  * 2. `shouldFallbackToUserPaid`: the SDK's own WETH sponsor path failed at
2761
2912
  * the infrastructure layer in a provably safe-to-retry way, so we warn
2762
2913
  * and retry once with `undefined`, falling through to a user-paid
@@ -2768,49 +2919,6 @@ var OwneySDK = class {
2768
2919
  * propagates unchanged. Scoped per agent invocation so one agent's failure
2769
2920
  * doesn't force a retry of sibling agents in a multi-agent split.
2770
2921
  */
2771
- /**
2772
- * Polls the owner's Permit2 allowance for `asset` until it covers `amount`
2773
- * or the attempt budget runs out. Called right after `approvePermit2()`
2774
- * resolves: the approval tx is mined, but the wallet's own RPC can still
2775
- * serve a stale (zero) allowance on the very next read (replica lag), which
2776
- * would otherwise make the immediate retry fail with the same
2777
- * PERMIT2_APPROVAL_REQUIRED error the user just approved past. Never
2778
- * throws — if the allowance still hasn't caught up after the budget, the
2779
- * caller's retry will surface the honest PERMIT2_APPROVAL_REQUIRED error
2780
- * rather than this helper inventing a different one.
2781
- */
2782
- async waitForPermit2Allowance(asset, amount) {
2783
- const state = this.requireState();
2784
- const chainId = this.requireChainId();
2785
- const provider = this.requireConnectedProvider();
2786
- const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
2787
- if (!token) return;
2788
- const publicClient = createPublicClient2({
2789
- chain: VIEM_CHAIN2[chainId],
2790
- transport: custom(provider)
2791
- });
2792
- for (let attempt = 0; attempt < PERMIT2_ALLOWANCE_POLL_MAX_ATTEMPTS; attempt++) {
2793
- if (attempt > 0) {
2794
- await new Promise(
2795
- (resolve) => setTimeout(resolve, PERMIT2_ALLOWANCE_POLL_INTERVAL_MS)
2796
- );
2797
- }
2798
- try {
2799
- const allowance = await readPermit2Allowance(
2800
- publicClient,
2801
- token,
2802
- state.walletAddress
2803
- );
2804
- if (allowance >= amount) return;
2805
- } catch (error) {
2806
- debugLog(
2807
- "deposit",
2808
- "Permit2 allowance poll read failed; retrying",
2809
- error instanceof Error ? error.message : String(error)
2810
- );
2811
- }
2812
- }
2813
- }
2814
2922
  async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback) {
2815
2923
  const attempt = (callback) => withFailureReporting(
2816
2924
  this.apiKey,
@@ -2828,16 +2936,12 @@ var OwneySDK = class {
2828
2936
  try {
2829
2937
  return await attempt(effectiveCallback);
2830
2938
  } catch (error) {
2831
- if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
2939
+ if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
2832
2940
  approvalAttempted = true;
2833
2941
  console.warn(
2834
- `[owney-sdk] First ${asset} gasless deposit: sending one-time Permit2 approval...`
2835
- );
2836
- await this.approvePermit2(asset);
2837
- await this.waitForPermit2Allowance(
2838
- asset,
2839
- BigInt(amount)
2942
+ "[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
2840
2943
  );
2944
+ await this.approvePermit2();
2841
2945
  continue;
2842
2946
  }
2843
2947
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -3351,32 +3455,23 @@ var OwneySDK = class {
3351
3455
  return agent.ensureAutoSelectProtocols(state, chainId, asset);
3352
3456
  }
3353
3457
  /**
3354
- * One-time, user-paid approval of Permit2 on the given sponsored token for
3355
- * the active chain. Required once per wallet per chain per token before a
3356
- * gasless deposit of that asset over Permit2; afterwards deposit() is
3357
- * signature-only for it. This call itself is a real transaction the user
3358
- * pays gas for. Resolves only after the approval transaction is mined (1
3359
- * confirmation), so a subsequent deposit() will see the new allowance;
3360
- * throws if the transaction reverted.
3458
+ * One-time, user-paid approval of Permit2 on the sponsored WETH token for
3459
+ * the active chain. Required once per wallet per chain before gasless WETH
3460
+ * deposits; afterwards deposit() is signature-only. Resolves only after the
3461
+ * approval transaction is mined (1 confirmation), so a subsequent deposit()
3462
+ * will see the new allowance; throws if the transaction reverted.
3361
3463
  * @returns the approval transaction hash.
3362
3464
  */
3363
3465
  async approvePermit2(asset = "WETH") {
3466
+ void asset;
3364
3467
  const state = this.requireState();
3365
3468
  const chainId = this.requireChainId();
3366
- let guard = this.agents.get("zyfai");
3367
- if (!guard) {
3368
- try {
3369
- guard = this.getEligibleAgents(chainId, asset)[0];
3370
- } catch {
3371
- guard = void 0;
3372
- }
3373
- }
3374
- if (guard) this.validateAssetSupport(guard, chainId, asset);
3375
- const token = asset === "USDC" ? SPONSORED_USDC_BY_CHAIN[chainId] : SPONSORED_WETH_BY_CHAIN[chainId];
3469
+ this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
3470
+ const token = SPONSORED_WETH_BY_CHAIN[chainId];
3376
3471
  if (!token) {
3377
3472
  throw new OwneyError(
3378
3473
  "CHAIN_UNSUPPORTED",
3379
- `No sponsored ${asset} on chain ${chainId}`
3474
+ `No sponsored WETH on chain ${chainId}`
3380
3475
  );
3381
3476
  }
3382
3477
  const provider = this.requireConnectedProvider();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.7-beta.2",
3
+ "version": "0.7.7-beta.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",