@owney/sdk 0.7.7-beta.3 → 0.7.7-beta.5

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
@@ -879,12 +879,6 @@ var ZyfaiAgent = class {
879
879
  connectedAddress = null;
880
880
  connectedChainId = null;
881
881
  smartWalletCache = /* @__PURE__ */ new Map();
882
- /**
883
- * `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
884
- * against re-running it on every activation/deposit when Zyfai's
885
- * `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
886
- */
887
- sessionKeyAttempts = /* @__PURE__ */ new Set();
888
882
  connectAuthPromise = null;
889
883
  rpcUrls;
890
884
  publicClients = /* @__PURE__ */ new Map();
@@ -1342,74 +1336,10 @@ var ZyfaiAgent = class {
1342
1336
  * recoverable (ensureWalletDeployed tries the other deploy call), but
1343
1337
  * failing the deposit outright is not.
1344
1338
  */
1345
- /**
1346
- * Zyfai's own answer to "what kind of wallet is this, and can the agent act on
1347
- * it", read from the positions payload (`GET /data/position`, which
1348
- * `getPositions` wraps).
1349
- *
1350
- * `predeployed` and `hasActiveSessionKey` are both returned at runtime but
1351
- * declared on neither `Portfolio` nor their docs, hence the cast. Verified
1352
- * live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
1353
- * false}` while a pool wallet reports `{true, true}`.
1354
- *
1355
- * Preferred over the SDK's private `isPredeployed`, which is cached from the
1356
- * login response and can go stale mid-session. Returns an empty object on any
1357
- * failure so every caller degrades to its own fallback.
1358
- */
1359
- async readWalletState(ownerAddress) {
1360
- try {
1361
- const { portfolio } = await this.sdk.getPositions(ownerAddress);
1362
- const raw = portfolio;
1363
- debugLog("zyfai:onboard", "wallet state from getPositions", {
1364
- predeployed: raw?.predeployed,
1365
- hasActiveSessionKey: raw?.hasActiveSessionKey
1366
- });
1367
- return {
1368
- predeployed: raw?.predeployed,
1369
- hasActiveSessionKey: raw?.hasActiveSessionKey
1370
- };
1371
- } catch (error) {
1372
- console.warn(
1373
- "[zyfai] getPositions wallet-state read failed (non-fatal):",
1374
- error instanceof Error ? error.message : String(error)
1375
- );
1376
- return {};
1377
- }
1378
- }
1379
1339
  async isPredeployedPoolWallet(ownerAddress, smartWallet) {
1380
- const { predeployed } = await this.readWalletState(ownerAddress);
1381
- if (typeof predeployed === "boolean") {
1382
- debugLog(
1383
- "zyfai:onboard",
1384
- `wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1385
- { source: "getPositions.predeployed", predeployed }
1386
- );
1387
- return predeployed;
1388
- }
1389
- const internals = this.sdk;
1390
- if (typeof internals.isPredeployed === "boolean") {
1391
- debugLog(
1392
- "zyfai:onboard",
1393
- `wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1394
- {
1395
- source: "login response (cached)",
1396
- predeployed: internals.isPredeployed
1397
- }
1398
- );
1399
- return internals.isPredeployed;
1400
- }
1401
1340
  try {
1402
1341
  const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
1403
- const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
1404
- debugLog(
1405
- "zyfai:onboard",
1406
- `wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1407
- {
1408
- source: "getSmartWalletByEOA fallback",
1409
- poolRecord: byEoa?.smartWallet
1410
- }
1411
- );
1412
- return isPool;
1342
+ return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
1413
1343
  } catch (error) {
1414
1344
  console.warn(
1415
1345
  "[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
@@ -1447,24 +1377,8 @@ var ZyfaiAgent = class {
1447
1377
  ownerAddress,
1448
1378
  chainId
1449
1379
  );
1450
- debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
1451
- smartWallet: wallet.address,
1452
- isDeployed: wallet.isDeployed
1453
- });
1454
- if (!wallet.isDeployed) {
1455
- debugLog(
1456
- "zyfai:onboard",
1457
- "Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
1458
- );
1459
- return;
1460
- }
1461
- const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1462
- await this.ensureSessionKey(
1463
- ownerAddress,
1464
- chainId,
1465
- wallet.address,
1466
- hasActiveSessionKey
1467
- );
1380
+ if (!wallet.isDeployed) return;
1381
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1468
1382
  } catch (error) {
1469
1383
  console.warn(
1470
1384
  "[zyfai] session-key check at activation failed (non-fatal):",
@@ -1472,51 +1386,20 @@ var ZyfaiAgent = class {
1472
1386
  );
1473
1387
  }
1474
1388
  }
1475
- async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
1476
- const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
1477
- if (this.sessionKeyAttempts.has(attemptKey)) {
1478
- debugLog(
1479
- "zyfai:onboard",
1480
- "session key: already attempted this session, skipping",
1481
- { chainId }
1482
- );
1483
- return;
1484
- }
1389
+ async ensureSessionKey(ownerAddress, chainId, smartWallet) {
1485
1390
  try {
1486
- if (hasActiveSessionKey === true) {
1487
- debugLog(
1488
- "zyfai:onboard",
1489
- "session key: already on record -> nothing to do",
1490
- { chainId }
1491
- );
1492
- return;
1493
- }
1494
- if (hasActiveSessionKey === void 0) {
1495
- const installed = await this.readSmartSessionsInstalled(
1496
- chainId,
1497
- smartWallet
1498
- );
1499
- debugLog(
1500
- "zyfai:onboard",
1501
- `session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
1502
- { smartWallet, chainId }
1503
- );
1504
- if (installed) return;
1505
- }
1506
- this.sessionKeyAttempts.add(attemptKey);
1391
+ const installed = await this.getPublicClient(chainId).readContract({
1392
+ address: smartWallet,
1393
+ abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1394
+ functionName: "isModuleInstalled",
1395
+ // moduleTypeId 1 = validator (ERC-7579)
1396
+ args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
1397
+ });
1398
+ if (installed) return;
1507
1399
  console.warn(
1508
- `[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
1400
+ `[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
1509
1401
  );
1510
- debugLog("zyfai:onboard", "session key: calling createSessionKey", {
1511
- chainId,
1512
- smartWallet,
1513
- hasActiveSessionKey
1514
- });
1515
1402
  await this.sdk.createSessionKey(ownerAddress, chainId);
1516
- debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
1517
- chainId,
1518
- smartWallet
1519
- });
1520
1403
  } catch (error) {
1521
1404
  console.warn(
1522
1405
  "[zyfai] createSessionKey skipped (non-fatal):",
@@ -1524,16 +1407,6 @@ var ZyfaiAgent = class {
1524
1407
  );
1525
1408
  }
1526
1409
  }
1527
- /** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
1528
- async readSmartSessionsInstalled(chainId, smartWallet) {
1529
- return this.getPublicClient(chainId).readContract({
1530
- address: smartWallet,
1531
- abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1532
- functionName: "isModuleInstalled",
1533
- // moduleTypeId 1 = validator (ERC-7579)
1534
- args: [1n, import_sdk.SMART_SESSIONS_VALIDATOR, "0x"]
1535
- });
1536
- }
1537
1410
  /**
1538
1411
  * Guarantee the user's Safe has code on `chainId` before anything transfers
1539
1412
  * funds into it, and return its address.
@@ -1554,24 +1427,8 @@ var ZyfaiAgent = class {
1554
1427
  */
1555
1428
  async ensureWalletDeployed(ownerAddress, chainId) {
1556
1429
  const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
1557
- debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
1558
- eoa: ownerAddress,
1559
- smartWallet: wallet.address,
1560
- isDeployed: wallet.isDeployed,
1561
- isOwner: wallet.isOwner
1562
- });
1563
1430
  if (wallet.isDeployed) {
1564
- debugLog(
1565
- "zyfai:onboard",
1566
- "Safe already has code -> no deploy needed, checking the session key"
1567
- );
1568
- const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1569
- await this.ensureSessionKey(
1570
- ownerAddress,
1571
- chainId,
1572
- wallet.address,
1573
- hasActiveSessionKey
1574
- );
1431
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1575
1432
  return wallet.address;
1576
1433
  }
1577
1434
  const poolFirst = await this.isPredeployedPoolWallet(
@@ -1587,27 +1444,14 @@ var ZyfaiAgent = class {
1587
1444
  this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
1588
1445
  );
1589
1446
  const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
1590
- debugLog(
1591
- "zyfai:onboard",
1592
- `Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
1593
- );
1594
1447
  try {
1595
1448
  await first();
1596
- debugLog(
1597
- "zyfai:onboard",
1598
- `${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
1599
- );
1600
1449
  } catch (error) {
1601
1450
  console.warn(
1602
1451
  `[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
1603
1452
  error instanceof Error ? error.message : String(error)
1604
1453
  );
1605
- debugLog(
1606
- "zyfai:onboard",
1607
- `falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
1608
- );
1609
1454
  await second();
1610
- debugLog("zyfai:onboard", "fallback deploy path succeeded");
1611
1455
  }
1612
1456
  const redeployed = await this.sdk.getSmartWalletAddress(
1613
1457
  ownerAddress,
@@ -1995,7 +1839,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1995
1839
  }
1996
1840
 
1997
1841
  // src/client.ts
1998
- var import_viem5 = require("viem");
1842
+ var import_viem6 = require("viem");
1999
1843
  var import_chains2 = require("viem/chains");
2000
1844
 
2001
1845
  // src/lib/transfer-auth.ts
@@ -2425,6 +2269,97 @@ function makeSponsoredWethCallback(deps) {
2425
2269
  };
2426
2270
  }
2427
2271
 
2272
+ // src/lib/sponsored-calls-deposit.ts
2273
+ var import_viem5 = require("viem");
2274
+ var DEFAULT_POLL_INTERVAL_MS = 1500;
2275
+ var DEFAULT_MAX_POLLS = 30;
2276
+ async function paymasterSupported(provider, owner, chainId) {
2277
+ const caps = await provider.request({
2278
+ method: "wallet_getCapabilities",
2279
+ params: [owner]
2280
+ });
2281
+ const forChain = caps?.[(0, import_viem5.toHex)(chainId)] ?? caps?.[String(chainId)];
2282
+ return Boolean(forChain?.paymasterService?.supported);
2283
+ }
2284
+ function makeSponsoredCallsCallback(deps) {
2285
+ const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2286
+ const maxPolls = deps.maxPolls ?? DEFAULT_MAX_POLLS;
2287
+ const absolutePaymasterUrl = () => {
2288
+ const configured = deps.paymasterServiceUrl;
2289
+ if (/^https?:\/\//i.test(configured)) return configured;
2290
+ const origin = globalThis.location?.origin;
2291
+ if (!origin) {
2292
+ throw new OwneyError(
2293
+ "SPONSORED_CALLS_UNSUPPORTED",
2294
+ `paymasterServiceUrl "${configured}" is relative and no page origin is available to resolve it.`
2295
+ );
2296
+ }
2297
+ return new URL(configured, origin).toString();
2298
+ };
2299
+ return async (smartWallet, chainId, amount) => {
2300
+ const cid = chainId;
2301
+ const token = deps.tokenAddressByChain[cid];
2302
+ if (!token) {
2303
+ throw new OwneyError(
2304
+ "CHAIN_UNSUPPORTED",
2305
+ `No sponsored token configured for chain ${chainId}`
2306
+ );
2307
+ }
2308
+ if (!await paymasterSupported(deps.provider, deps.ownerAddress, chainId)) {
2309
+ throw new OwneyError(
2310
+ "SPONSORED_CALLS_UNSUPPORTED",
2311
+ `Wallet does not advertise paymasterService on chain ${chainId}.`,
2312
+ { chainId }
2313
+ );
2314
+ }
2315
+ const data = (0, import_viem5.encodeFunctionData)({
2316
+ abi: import_viem5.erc20Abi,
2317
+ functionName: "transfer",
2318
+ args: [smartWallet, BigInt(amount)]
2319
+ });
2320
+ const sendResult = await deps.provider.request({
2321
+ method: "wallet_sendCalls",
2322
+ params: [
2323
+ {
2324
+ version: "2.0.0",
2325
+ from: deps.ownerAddress,
2326
+ chainId: (0, import_viem5.toHex)(chainId),
2327
+ atomicRequired: false,
2328
+ calls: [{ to: token, value: "0x0", data }],
2329
+ capabilities: {
2330
+ paymasterService: { url: absolutePaymasterUrl() }
2331
+ }
2332
+ }
2333
+ ]
2334
+ });
2335
+ deps.onApproved?.();
2336
+ const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
2337
+ if (!callsId) {
2338
+ throw new OwneyError(
2339
+ "SPONSORED_CALLS_NO_ID",
2340
+ "Wallet accepted wallet_sendCalls but returned no calls id.",
2341
+ { chainId }
2342
+ );
2343
+ }
2344
+ for (let attempt = 0; attempt < maxPolls; attempt += 1) {
2345
+ const status = await deps.provider.request({
2346
+ method: "wallet_getCallsStatus",
2347
+ params: [callsId]
2348
+ });
2349
+ const txHash = status?.receipts?.[0]?.transactionHash;
2350
+ if (txHash) return txHash;
2351
+ if (pollIntervalMs > 0) {
2352
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2353
+ }
2354
+ }
2355
+ throw new OwneyError(
2356
+ "SPONSORED_CALLS_NO_RECEIPT",
2357
+ `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
2358
+ { chainId, callsId }
2359
+ );
2360
+ };
2361
+ }
2362
+
2428
2363
  // src/client.ts
2429
2364
  function encodeMultiAgentCursor(map) {
2430
2365
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -2486,12 +2421,15 @@ var OwneySDK = class {
2486
2421
  referralSource;
2487
2422
  cachedSponsoredCallback = null;
2488
2423
  cachedWethSponsoredCallback = null;
2424
+ paymasterServiceUrl;
2425
+ cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
2489
2426
  initializingAgentsPromise = null;
2490
2427
  constructor(config) {
2491
2428
  this.apiKey = config.apiKey;
2492
2429
  if (config.debug) setOwneyDebug(true);
2493
2430
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2494
2431
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2432
+ this.paymasterServiceUrl = config.paymasterServiceUrl;
2495
2433
  this.referralSource = config.referralSource;
2496
2434
  }
2497
2435
  /**
@@ -2514,6 +2452,7 @@ var OwneySDK = class {
2514
2452
  this.state = { provider, walletAddress, chainId: null };
2515
2453
  this.cachedSponsoredCallback = null;
2516
2454
  this.cachedWethSponsoredCallback = null;
2455
+ this.cachedSponsoredCallsCallbacks.clear();
2517
2456
  }
2518
2457
  /**
2519
2458
  * Disconnect from all agents and clear connection state.
@@ -2528,6 +2467,7 @@ var OwneySDK = class {
2528
2467
  this.state = null;
2529
2468
  this.cachedSponsoredCallback = null;
2530
2469
  this.cachedWethSponsoredCallback = null;
2470
+ this.cachedSponsoredCallsCallbacks.clear();
2531
2471
  }
2532
2472
  isAgentDisabled(agentId) {
2533
2473
  return this.disabledAgents.has(agentId);
@@ -2590,14 +2530,14 @@ var OwneySDK = class {
2590
2530
  // Casts work around viem's chain-narrowed Client vs the generic
2591
2531
  // PublicClient/WalletClient param types — structurally identical at
2592
2532
  // runtime, but the two share a name TS treats as unrelated.
2593
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2533
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2594
2534
  chain: VIEM_CHAIN2[cid],
2595
- transport: (0, import_viem5.custom)(provider)
2535
+ transport: (0, import_viem6.custom)(provider)
2596
2536
  }),
2597
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2537
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2598
2538
  account: owner,
2599
2539
  chain: VIEM_CHAIN2[cid],
2600
- transport: (0, import_viem5.custom)(provider)
2540
+ transport: (0, import_viem6.custom)(provider)
2601
2541
  })
2602
2542
  });
2603
2543
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -2609,6 +2549,26 @@ var OwneySDK = class {
2609
2549
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2610
2550
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
2611
2551
  */
2552
+ getDefaultSponsoredCallsCallback(asset, onApproved) {
2553
+ const cached = this.cachedSponsoredCallsCallbacks.get(asset);
2554
+ if (!onApproved && cached) return cached;
2555
+ const provider = this.requireConnectedProvider();
2556
+ const callback = makeSponsoredCallsCallback({
2557
+ provider,
2558
+ ownerAddress: this.state.walletAddress,
2559
+ paymasterServiceUrl: this.paymasterServiceUrl,
2560
+ onApproved,
2561
+ tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
2562
+ });
2563
+ if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
2564
+ return callback;
2565
+ }
2566
+ /**
2567
+ * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
2568
+ * callback used when the caller omits `depositCallback` for a WETH deposit.
2569
+ * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2570
+ * `PermitTransferFrom` instead of an EIP-3009 authorization.
2571
+ */
2612
2572
  getDefaultWethSponsoredCallback(onApproved) {
2613
2573
  if (!onApproved && this.cachedWethSponsoredCallback)
2614
2574
  return this.cachedWethSponsoredCallback;
@@ -2623,14 +2583,14 @@ var OwneySDK = class {
2623
2583
  // Casts work around viem's chain-narrowed Client vs the generic
2624
2584
  // PublicClient/WalletClient param types — structurally identical at
2625
2585
  // runtime, but the two share a name TS treats as unrelated.
2626
- getPublicClient: (cid) => (0, import_viem5.createPublicClient)({
2586
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
2627
2587
  chain: VIEM_CHAIN2[cid],
2628
- transport: (0, import_viem5.custom)(provider)
2588
+ transport: (0, import_viem6.custom)(provider)
2629
2589
  }),
2630
- getWalletClient: (cid) => (0, import_viem5.createWalletClient)({
2590
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
2631
2591
  account: owner,
2632
2592
  chain: VIEM_CHAIN2[cid],
2633
- transport: (0, import_viem5.custom)(provider)
2593
+ transport: (0, import_viem6.custom)(provider)
2634
2594
  })
2635
2595
  });
2636
2596
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -2831,7 +2791,7 @@ var OwneySDK = class {
2831
2791
  }
2832
2792
  const state = this.requireState();
2833
2793
  const chainId = this.requireChainId();
2834
- const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2794
+ const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2835
2795
  if (agentId) {
2836
2796
  if (this.isAgentDisabled(agentId)) {
2837
2797
  throw new OwneyError(
@@ -3474,10 +3434,10 @@ var OwneySDK = class {
3474
3434
  );
3475
3435
  }
3476
3436
  const provider = this.requireConnectedProvider();
3477
- const wallet = (0, import_viem5.createWalletClient)({
3437
+ const wallet = (0, import_viem6.createWalletClient)({
3478
3438
  account: state.walletAddress,
3479
3439
  chain: VIEM_CHAIN2[chainId],
3480
- transport: (0, import_viem5.custom)(provider)
3440
+ transport: (0, import_viem6.custom)(provider)
3481
3441
  });
3482
3442
  const hash = await wallet.writeContract({
3483
3443
  address: token,
@@ -3487,9 +3447,9 @@ var OwneySDK = class {
3487
3447
  account: state.walletAddress,
3488
3448
  chain: VIEM_CHAIN2[chainId]
3489
3449
  });
3490
- const publicClient = (0, import_viem5.createPublicClient)({
3450
+ const publicClient = (0, import_viem6.createPublicClient)({
3491
3451
  chain: VIEM_CHAIN2[chainId],
3492
- transport: (0, import_viem5.custom)(provider)
3452
+ transport: (0, import_viem6.custom)(provider)
3493
3453
  });
3494
3454
  const receipt = await publicClient.waitForTransactionReceipt({
3495
3455
  hash,
@@ -3598,7 +3558,7 @@ var OwneySDK = class {
3598
3558
  };
3599
3559
 
3600
3560
  // src/agents/zyfai/zyfai.siwx.ts
3601
- var import_viem6 = require("viem");
3561
+ var import_viem7 = require("viem");
3602
3562
  var import_siwe = require("siwe");
3603
3563
  var import_sdk2 = require("@zyfai/sdk");
3604
3564
 
@@ -3725,7 +3685,7 @@ function buildSIWXConfig(deps) {
3725
3685
  issuedAt,
3726
3686
  toString() {
3727
3687
  return new import_siwe.SiweMessage({
3728
- address: (0, import_viem6.getAddress)(accountAddress),
3688
+ address: (0, import_viem7.getAddress)(accountAddress),
3729
3689
  chainId: numericChainId(chainId),
3730
3690
  domain,
3731
3691
  uri,
package/dist/index.d.cts CHANGED
@@ -30,6 +30,17 @@ interface OwneySDKConfig {
30
30
  * already-deployed app: set `globalThis.__OWNEY_DEBUG__ = true` in devtools.
31
31
  */
32
32
  debug?: boolean;
33
+ /**
34
+ * Absolute URL of an ERC-7677 paymaster service. When set, deposits go out as
35
+ * an EIP-5792 `wallet_sendCalls` with `capabilities.paymasterService`, so a
36
+ * wallet that advertises sponsorship pays no native gas — the case EIP-3009
37
+ * cannot serve, because USDC verifies it with `ecrecover` and a smart account
38
+ * signs via ERC-1271.
39
+ *
40
+ * MUST be absolute: the wallet fetches this URL itself, so a relative path is
41
+ * unresolvable. Omit it and every rail behaves exactly as before.
42
+ */
43
+ paymasterServiceUrl?: string;
33
44
  }
34
45
  interface ConnectionState {
35
46
  provider: any;
@@ -393,6 +404,8 @@ declare class OwneySDK {
393
404
  private referralSource?;
394
405
  private cachedSponsoredCallback;
395
406
  private cachedWethSponsoredCallback;
407
+ private paymasterServiceUrl?;
408
+ private cachedSponsoredCallsCallbacks;
396
409
  private initializingAgentsPromise;
397
410
  constructor(config: OwneySDKConfig);
398
411
  /**
@@ -433,6 +446,13 @@ declare class OwneySDK {
433
446
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
434
447
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
435
448
  */
449
+ private getDefaultSponsoredCallsCallback;
450
+ /**
451
+ * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
452
+ * callback used when the caller omits `depositCallback` for a WETH deposit.
453
+ * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
454
+ * `PermitTransferFrom` instead of an EIP-3009 authorization.
455
+ */
436
456
  private getDefaultWethSponsoredCallback;
437
457
  private getAgent;
438
458
  private getActiveAgents;
@@ -609,7 +629,7 @@ declare class OwneySDK {
609
629
  getAllocationApy({ agentId, }?: AllocationApyOptions): Promise<OwneyAllocationApy>;
610
630
  }
611
631
 
612
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
632
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
613
633
  declare class OwneyError extends Error {
614
634
  readonly code: OwneyErrorCode;
615
635
  readonly details?: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -30,6 +30,17 @@ interface OwneySDKConfig {
30
30
  * already-deployed app: set `globalThis.__OWNEY_DEBUG__ = true` in devtools.
31
31
  */
32
32
  debug?: boolean;
33
+ /**
34
+ * Absolute URL of an ERC-7677 paymaster service. When set, deposits go out as
35
+ * an EIP-5792 `wallet_sendCalls` with `capabilities.paymasterService`, so a
36
+ * wallet that advertises sponsorship pays no native gas — the case EIP-3009
37
+ * cannot serve, because USDC verifies it with `ecrecover` and a smart account
38
+ * signs via ERC-1271.
39
+ *
40
+ * MUST be absolute: the wallet fetches this URL itself, so a relative path is
41
+ * unresolvable. Omit it and every rail behaves exactly as before.
42
+ */
43
+ paymasterServiceUrl?: string;
33
44
  }
34
45
  interface ConnectionState {
35
46
  provider: any;
@@ -393,6 +404,8 @@ declare class OwneySDK {
393
404
  private referralSource?;
394
405
  private cachedSponsoredCallback;
395
406
  private cachedWethSponsoredCallback;
407
+ private paymasterServiceUrl?;
408
+ private cachedSponsoredCallsCallbacks;
396
409
  private initializingAgentsPromise;
397
410
  constructor(config: OwneySDKConfig);
398
411
  /**
@@ -433,6 +446,13 @@ declare class OwneySDK {
433
446
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
434
447
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
435
448
  */
449
+ private getDefaultSponsoredCallsCallback;
450
+ /**
451
+ * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
452
+ * callback used when the caller omits `depositCallback` for a WETH deposit.
453
+ * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
454
+ * `PermitTransferFrom` instead of an EIP-3009 authorization.
455
+ */
436
456
  private getDefaultWethSponsoredCallback;
437
457
  private getAgent;
438
458
  private getActiveAgents;
@@ -609,7 +629,7 @@ declare class OwneySDK {
609
629
  getAllocationApy({ agentId, }?: AllocationApyOptions): Promise<OwneyAllocationApy>;
610
630
  }
611
631
 
612
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
632
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_SAIL_ERROR" | "API_SAIL_TIMEOUT" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
613
633
  declare class OwneyError extends Error {
614
634
  readonly code: OwneyErrorCode;
615
635
  readonly details?: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -846,12 +846,6 @@ var ZyfaiAgent = class {
846
846
  connectedAddress = null;
847
847
  connectedChainId = null;
848
848
  smartWalletCache = /* @__PURE__ */ new Map();
849
- /**
850
- * `${eoa}:${chainId}` keys we have already tried createSessionKey for. Guards
851
- * against re-running it on every activation/deposit when Zyfai's
852
- * `hasActiveSessionKey` does not flip to true — see ensureSessionKey.
853
- */
854
- sessionKeyAttempts = /* @__PURE__ */ new Set();
855
849
  connectAuthPromise = null;
856
850
  rpcUrls;
857
851
  publicClients = /* @__PURE__ */ new Map();
@@ -1309,74 +1303,10 @@ var ZyfaiAgent = class {
1309
1303
  * recoverable (ensureWalletDeployed tries the other deploy call), but
1310
1304
  * failing the deposit outright is not.
1311
1305
  */
1312
- /**
1313
- * Zyfai's own answer to "what kind of wallet is this, and can the agent act on
1314
- * it", read from the positions payload (`GET /data/position`, which
1315
- * `getPositions` wraps).
1316
- *
1317
- * `predeployed` and `hasActiveSessionKey` are both returned at runtime but
1318
- * declared on neither `Portfolio` nor their docs, hence the cast. Verified
1319
- * live: an old wallet reports `{predeployed: false, hasActiveSessionKey:
1320
- * false}` while a pool wallet reports `{true, true}`.
1321
- *
1322
- * Preferred over the SDK's private `isPredeployed`, which is cached from the
1323
- * login response and can go stale mid-session. Returns an empty object on any
1324
- * failure so every caller degrades to its own fallback.
1325
- */
1326
- async readWalletState(ownerAddress) {
1327
- try {
1328
- const { portfolio } = await this.sdk.getPositions(ownerAddress);
1329
- const raw = portfolio;
1330
- debugLog("zyfai:onboard", "wallet state from getPositions", {
1331
- predeployed: raw?.predeployed,
1332
- hasActiveSessionKey: raw?.hasActiveSessionKey
1333
- });
1334
- return {
1335
- predeployed: raw?.predeployed,
1336
- hasActiveSessionKey: raw?.hasActiveSessionKey
1337
- };
1338
- } catch (error) {
1339
- console.warn(
1340
- "[zyfai] getPositions wallet-state read failed (non-fatal):",
1341
- error instanceof Error ? error.message : String(error)
1342
- );
1343
- return {};
1344
- }
1345
- }
1346
1306
  async isPredeployedPoolWallet(ownerAddress, smartWallet) {
1347
- const { predeployed } = await this.readWalletState(ownerAddress);
1348
- if (typeof predeployed === "boolean") {
1349
- debugLog(
1350
- "zyfai:onboard",
1351
- `wallet class: ${predeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1352
- { source: "getPositions.predeployed", predeployed }
1353
- );
1354
- return predeployed;
1355
- }
1356
- const internals = this.sdk;
1357
- if (typeof internals.isPredeployed === "boolean") {
1358
- debugLog(
1359
- "zyfai:onboard",
1360
- `wallet class: ${internals.isPredeployed ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1361
- {
1362
- source: "login response (cached)",
1363
- predeployed: internals.isPredeployed
1364
- }
1365
- );
1366
- return internals.isPredeployed;
1367
- }
1368
1307
  try {
1369
1308
  const byEoa = await this.sdk.getSmartWalletByEOA(ownerAddress);
1370
- const isPool = typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
1371
- debugLog(
1372
- "zyfai:onboard",
1373
- `wallet class: ${isPool ? "PREDEPLOYED (new flow)" : "OLD (legacy flow)"}`,
1374
- {
1375
- source: "getSmartWalletByEOA fallback",
1376
- poolRecord: byEoa?.smartWallet
1377
- }
1378
- );
1379
- return isPool;
1309
+ return typeof byEoa?.smartWallet === "string" && byEoa.smartWallet.toLowerCase() === smartWallet.toLowerCase();
1380
1310
  } catch (error) {
1381
1311
  console.warn(
1382
1312
  "[zyfai] getSmartWalletByEOA failed; assuming a legacy wallet:",
@@ -1414,24 +1344,8 @@ var ZyfaiAgent = class {
1414
1344
  ownerAddress,
1415
1345
  chainId
1416
1346
  );
1417
- debugLog("zyfai:onboard", `activation check on chain ${chainId}`, {
1418
- smartWallet: wallet.address,
1419
- isDeployed: wallet.isDeployed
1420
- });
1421
- if (!wallet.isDeployed) {
1422
- debugLog(
1423
- "zyfai:onboard",
1424
- "Safe not deployed on this chain; activation does nothing (deposit owns deploys)"
1425
- );
1426
- return;
1427
- }
1428
- const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1429
- await this.ensureSessionKey(
1430
- ownerAddress,
1431
- chainId,
1432
- wallet.address,
1433
- hasActiveSessionKey
1434
- );
1347
+ if (!wallet.isDeployed) return;
1348
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1435
1349
  } catch (error) {
1436
1350
  console.warn(
1437
1351
  "[zyfai] session-key check at activation failed (non-fatal):",
@@ -1439,51 +1353,20 @@ var ZyfaiAgent = class {
1439
1353
  );
1440
1354
  }
1441
1355
  }
1442
- async ensureSessionKey(ownerAddress, chainId, smartWallet, hasActiveSessionKey) {
1443
- const attemptKey = `${ownerAddress.toLowerCase()}:${chainId}`;
1444
- if (this.sessionKeyAttempts.has(attemptKey)) {
1445
- debugLog(
1446
- "zyfai:onboard",
1447
- "session key: already attempted this session, skipping",
1448
- { chainId }
1449
- );
1450
- return;
1451
- }
1356
+ async ensureSessionKey(ownerAddress, chainId, smartWallet) {
1452
1357
  try {
1453
- if (hasActiveSessionKey === true) {
1454
- debugLog(
1455
- "zyfai:onboard",
1456
- "session key: already on record -> nothing to do",
1457
- { chainId }
1458
- );
1459
- return;
1460
- }
1461
- if (hasActiveSessionKey === void 0) {
1462
- const installed = await this.readSmartSessionsInstalled(
1463
- chainId,
1464
- smartWallet
1465
- );
1466
- debugLog(
1467
- "zyfai:onboard",
1468
- `session key: Zyfai state unavailable; on-chain validator ${installed ? "INSTALLED" : "MISSING"}`,
1469
- { smartWallet, chainId }
1470
- );
1471
- if (installed) return;
1472
- }
1473
- this.sessionKeyAttempts.add(attemptKey);
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;
1474
1366
  console.warn(
1475
- `[zyfai] no session key for ${smartWallet} on chain ${chainId}; creating one so the agent can allocate.`
1367
+ `[zyfai] Safe ${smartWallet} has no smart-sessions validator on chain ${chainId}; creating a session key so the agent can allocate.`
1476
1368
  );
1477
- debugLog("zyfai:onboard", "session key: calling createSessionKey", {
1478
- chainId,
1479
- smartWallet,
1480
- hasActiveSessionKey
1481
- });
1482
1369
  await this.sdk.createSessionKey(ownerAddress, chainId);
1483
- debugLog("zyfai:onboard", "session key: createSessionKey succeeded", {
1484
- chainId,
1485
- smartWallet
1486
- });
1487
1370
  } catch (error) {
1488
1371
  console.warn(
1489
1372
  "[zyfai] createSessionKey skipped (non-fatal):",
@@ -1491,16 +1374,6 @@ var ZyfaiAgent = class {
1491
1374
  );
1492
1375
  }
1493
1376
  }
1494
- /** Ground truth for "can Zyfai's automation act on this Safe, on this chain". */
1495
- async readSmartSessionsInstalled(chainId, smartWallet) {
1496
- return this.getPublicClient(chainId).readContract({
1497
- address: smartWallet,
1498
- abi: ERC7579_IS_MODULE_INSTALLED_ABI,
1499
- functionName: "isModuleInstalled",
1500
- // moduleTypeId 1 = validator (ERC-7579)
1501
- args: [1n, SMART_SESSIONS_VALIDATOR, "0x"]
1502
- });
1503
- }
1504
1377
  /**
1505
1378
  * Guarantee the user's Safe has code on `chainId` before anything transfers
1506
1379
  * funds into it, and return its address.
@@ -1521,24 +1394,8 @@ var ZyfaiAgent = class {
1521
1394
  */
1522
1395
  async ensureWalletDeployed(ownerAddress, chainId) {
1523
1396
  const wallet = await this.sdk.getSmartWalletAddress(ownerAddress, chainId);
1524
- debugLog("zyfai:onboard", `--- deposit onboarding, chain ${chainId} ---`, {
1525
- eoa: ownerAddress,
1526
- smartWallet: wallet.address,
1527
- isDeployed: wallet.isDeployed,
1528
- isOwner: wallet.isOwner
1529
- });
1530
1397
  if (wallet.isDeployed) {
1531
- debugLog(
1532
- "zyfai:onboard",
1533
- "Safe already has code -> no deploy needed, checking the session key"
1534
- );
1535
- const { hasActiveSessionKey } = await this.readWalletState(ownerAddress);
1536
- await this.ensureSessionKey(
1537
- ownerAddress,
1538
- chainId,
1539
- wallet.address,
1540
- hasActiveSessionKey
1541
- );
1398
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1542
1399
  return wallet.address;
1543
1400
  }
1544
1401
  const poolFirst = await this.isPredeployedPoolWallet(
@@ -1554,27 +1411,14 @@ var ZyfaiAgent = class {
1554
1411
  this.sdk.deploySafe(ownerAddress, chainId, "aggressive", true)
1555
1412
  );
1556
1413
  const [first, second] = poolFirst ? [deployAsPool, deployAsLegacy] : [deployAsLegacy, deployAsPool];
1557
- debugLog(
1558
- "zyfai:onboard",
1559
- `Safe has no code on chain ${chainId} -> deploying via ${poolFirst ? "deployOnChains (pool path)" : "deploySafe(createSessionKey=true) (legacy path)"}`
1560
- );
1561
1414
  try {
1562
1415
  await first();
1563
- debugLog(
1564
- "zyfai:onboard",
1565
- `${poolFirst ? "deployOnChains" : "deploySafe"} succeeded`
1566
- );
1567
1416
  } catch (error) {
1568
1417
  console.warn(
1569
1418
  `[zyfai] ${poolFirst ? "deployOnChains" : "deploySafe"} failed on chain ${chainId}; trying the other deploy path:`,
1570
1419
  error instanceof Error ? error.message : String(error)
1571
1420
  );
1572
- debugLog(
1573
- "zyfai:onboard",
1574
- `falling back to ${poolFirst ? "deploySafe (legacy path)" : "deployOnChains (pool path)"}`
1575
- );
1576
1421
  await second();
1577
- debugLog("zyfai:onboard", "fallback deploy path succeeded");
1578
1422
  }
1579
1423
  const redeployed = await this.sdk.getSmartWalletAddress(
1580
1424
  ownerAddress,
@@ -2396,6 +2240,97 @@ function makeSponsoredWethCallback(deps) {
2396
2240
  };
2397
2241
  }
2398
2242
 
2243
+ // src/lib/sponsored-calls-deposit.ts
2244
+ import { encodeFunctionData, erc20Abi, toHex } from "viem";
2245
+ var DEFAULT_POLL_INTERVAL_MS = 1500;
2246
+ var DEFAULT_MAX_POLLS = 30;
2247
+ async function paymasterSupported(provider, owner, chainId) {
2248
+ const caps = await provider.request({
2249
+ method: "wallet_getCapabilities",
2250
+ params: [owner]
2251
+ });
2252
+ const forChain = caps?.[toHex(chainId)] ?? caps?.[String(chainId)];
2253
+ return Boolean(forChain?.paymasterService?.supported);
2254
+ }
2255
+ function makeSponsoredCallsCallback(deps) {
2256
+ const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2257
+ const maxPolls = deps.maxPolls ?? DEFAULT_MAX_POLLS;
2258
+ const absolutePaymasterUrl = () => {
2259
+ const configured = deps.paymasterServiceUrl;
2260
+ if (/^https?:\/\//i.test(configured)) return configured;
2261
+ const origin = globalThis.location?.origin;
2262
+ if (!origin) {
2263
+ throw new OwneyError(
2264
+ "SPONSORED_CALLS_UNSUPPORTED",
2265
+ `paymasterServiceUrl "${configured}" is relative and no page origin is available to resolve it.`
2266
+ );
2267
+ }
2268
+ return new URL(configured, origin).toString();
2269
+ };
2270
+ return async (smartWallet, chainId, amount) => {
2271
+ const cid = chainId;
2272
+ const token = deps.tokenAddressByChain[cid];
2273
+ if (!token) {
2274
+ throw new OwneyError(
2275
+ "CHAIN_UNSUPPORTED",
2276
+ `No sponsored token configured for chain ${chainId}`
2277
+ );
2278
+ }
2279
+ if (!await paymasterSupported(deps.provider, deps.ownerAddress, chainId)) {
2280
+ throw new OwneyError(
2281
+ "SPONSORED_CALLS_UNSUPPORTED",
2282
+ `Wallet does not advertise paymasterService on chain ${chainId}.`,
2283
+ { chainId }
2284
+ );
2285
+ }
2286
+ const data = encodeFunctionData({
2287
+ abi: erc20Abi,
2288
+ functionName: "transfer",
2289
+ args: [smartWallet, BigInt(amount)]
2290
+ });
2291
+ const sendResult = await deps.provider.request({
2292
+ method: "wallet_sendCalls",
2293
+ params: [
2294
+ {
2295
+ version: "2.0.0",
2296
+ from: deps.ownerAddress,
2297
+ chainId: toHex(chainId),
2298
+ atomicRequired: false,
2299
+ calls: [{ to: token, value: "0x0", data }],
2300
+ capabilities: {
2301
+ paymasterService: { url: absolutePaymasterUrl() }
2302
+ }
2303
+ }
2304
+ ]
2305
+ });
2306
+ deps.onApproved?.();
2307
+ const callsId = typeof sendResult === "string" ? sendResult : sendResult?.id;
2308
+ if (!callsId) {
2309
+ throw new OwneyError(
2310
+ "SPONSORED_CALLS_NO_ID",
2311
+ "Wallet accepted wallet_sendCalls but returned no calls id.",
2312
+ { chainId }
2313
+ );
2314
+ }
2315
+ for (let attempt = 0; attempt < maxPolls; attempt += 1) {
2316
+ const status = await deps.provider.request({
2317
+ method: "wallet_getCallsStatus",
2318
+ params: [callsId]
2319
+ });
2320
+ const txHash = status?.receipts?.[0]?.transactionHash;
2321
+ if (txHash) return txHash;
2322
+ if (pollIntervalMs > 0) {
2323
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2324
+ }
2325
+ }
2326
+ throw new OwneyError(
2327
+ "SPONSORED_CALLS_NO_RECEIPT",
2328
+ `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
2329
+ { chainId, callsId }
2330
+ );
2331
+ };
2332
+ }
2333
+
2399
2334
  // src/client.ts
2400
2335
  function encodeMultiAgentCursor(map) {
2401
2336
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -2457,12 +2392,15 @@ var OwneySDK = class {
2457
2392
  referralSource;
2458
2393
  cachedSponsoredCallback = null;
2459
2394
  cachedWethSponsoredCallback = null;
2395
+ paymasterServiceUrl;
2396
+ cachedSponsoredCallsCallbacks = /* @__PURE__ */ new Map();
2460
2397
  initializingAgentsPromise = null;
2461
2398
  constructor(config) {
2462
2399
  this.apiKey = config.apiKey;
2463
2400
  if (config.debug) setOwneyDebug(true);
2464
2401
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
2465
2402
  this.routingApiBaseUrl = config.routingApiBaseUrl;
2403
+ this.paymasterServiceUrl = config.paymasterServiceUrl;
2466
2404
  this.referralSource = config.referralSource;
2467
2405
  }
2468
2406
  /**
@@ -2485,6 +2423,7 @@ var OwneySDK = class {
2485
2423
  this.state = { provider, walletAddress, chainId: null };
2486
2424
  this.cachedSponsoredCallback = null;
2487
2425
  this.cachedWethSponsoredCallback = null;
2426
+ this.cachedSponsoredCallsCallbacks.clear();
2488
2427
  }
2489
2428
  /**
2490
2429
  * Disconnect from all agents and clear connection state.
@@ -2499,6 +2438,7 @@ var OwneySDK = class {
2499
2438
  this.state = null;
2500
2439
  this.cachedSponsoredCallback = null;
2501
2440
  this.cachedWethSponsoredCallback = null;
2441
+ this.cachedSponsoredCallsCallbacks.clear();
2502
2442
  }
2503
2443
  isAgentDisabled(agentId) {
2504
2444
  return this.disabledAgents.has(agentId);
@@ -2580,6 +2520,26 @@ var OwneySDK = class {
2580
2520
  * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2581
2521
  * `PermitTransferFrom` instead of an EIP-3009 authorization.
2582
2522
  */
2523
+ getDefaultSponsoredCallsCallback(asset, onApproved) {
2524
+ const cached = this.cachedSponsoredCallsCallbacks.get(asset);
2525
+ if (!onApproved && cached) return cached;
2526
+ const provider = this.requireConnectedProvider();
2527
+ const callback = makeSponsoredCallsCallback({
2528
+ provider,
2529
+ ownerAddress: this.state.walletAddress,
2530
+ paymasterServiceUrl: this.paymasterServiceUrl,
2531
+ onApproved,
2532
+ tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
2533
+ });
2534
+ if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
2535
+ return callback;
2536
+ }
2537
+ /**
2538
+ * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
2539
+ * callback used when the caller omits `depositCallback` for a WETH deposit.
2540
+ * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
2541
+ * `PermitTransferFrom` instead of an EIP-3009 authorization.
2542
+ */
2583
2543
  getDefaultWethSponsoredCallback(onApproved) {
2584
2544
  if (!onApproved && this.cachedWethSponsoredCallback)
2585
2545
  return this.cachedWethSponsoredCallback;
@@ -2802,7 +2762,7 @@ var OwneySDK = class {
2802
2762
  }
2803
2763
  const state = this.requireState();
2804
2764
  const chainId = this.requireChainId();
2805
- const effectiveCallback = depositCallback ?? (asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2765
+ const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved) : this.getDefaultSponsoredCallback(onApproved));
2806
2766
  if (agentId) {
2807
2767
  if (this.isAgentDisabled(agentId)) {
2808
2768
  throw new OwneyError(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.7-beta.3",
3
+ "version": "0.7.7-beta.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",