@owney/sdk 0.7.21-beta.4 → 0.7.21-beta.6

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
@@ -32,6 +32,26 @@ __export(index_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(index_exports);
34
34
 
35
+ // src/lib/deposit-batch-callback.ts
36
+ var batchCallbacks = /* @__PURE__ */ new WeakMap();
37
+ var getDepositBatchTransfer = (callback) => callback ? batchCallbacks.get(callback) : void 0;
38
+ function toBatchTransfer(to, amount, verification) {
39
+ return {
40
+ to,
41
+ amount,
42
+ ...verification ? {
43
+ yieldseeker: {
44
+ signature: verification.signature,
45
+ userId: verification.userId,
46
+ agentId: verification.yieldseekerAgentId
47
+ }
48
+ } : {}
49
+ };
50
+ }
51
+ function registerDepositBatch(callback, transfer) {
52
+ batchCallbacks.set(callback, transfer);
53
+ }
54
+
35
55
  // src/errors.ts
36
56
  var OwneyError = class extends Error {
37
57
  code;
@@ -2098,39 +2118,6 @@ async function ensureWalletOnChain(pub, wallet, expected) {
2098
2118
 
2099
2119
  // src/lib/transfer-auth.ts
2100
2120
  var import_viem2 = require("viem");
2101
- var ERC20_META_ABI = [
2102
- { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2103
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2104
- ];
2105
- function buildTransferWithAuthorizationTypedData(input) {
2106
- return {
2107
- domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2108
- types: {
2109
- TransferWithAuthorization: [
2110
- { name: "from", type: "address" },
2111
- { name: "to", type: "address" },
2112
- { name: "value", type: "uint256" },
2113
- { name: "validAfter", type: "uint256" },
2114
- { name: "validBefore", type: "uint256" },
2115
- { name: "nonce", type: "bytes32" }
2116
- ]
2117
- },
2118
- primaryType: "TransferWithAuthorization",
2119
- message: input.message
2120
- };
2121
- }
2122
- async function readTokenMeta(publicClient, token) {
2123
- const [tokenName, tokenVersion] = await Promise.all([
2124
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2125
- publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2126
- ]);
2127
- return { tokenName, tokenVersion };
2128
- }
2129
- function randomAuthNonce() {
2130
- const bytes = new Uint8Array(32);
2131
- globalThis.crypto.getRandomValues(bytes);
2132
- return (0, import_viem2.bytesToHex)(bytes);
2133
- }
2134
2121
 
2135
2122
  // src/lib/sponsor-client.ts
2136
2123
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
@@ -2173,65 +2160,21 @@ async function postPaymasterIntent(input) {
2173
2160
  }
2174
2161
  return parsed.data;
2175
2162
  }
2176
- async function postSponsorTransferAuth(input) {
2163
+ async function getSponsorRelayerAddress(input) {
2177
2164
  const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2178
2165
  let res;
2179
2166
  try {
2180
- res = await fetch(`${base5}/api/v1/sponsor/erc20-transfer-auth`, {
2181
- method: "POST",
2182
- headers: {
2183
- "content-type": "application/json",
2184
- "x-owney-api-key": input.apiKey,
2185
- ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2186
- },
2187
- body: JSON.stringify(input.body)
2188
- });
2189
- } catch (networkError) {
2190
- throw new OwneyError(
2191
- "SPONSOR_REQUEST_FAILED",
2192
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2193
- { cause: String(networkError) }
2194
- );
2195
- }
2196
- const text = await res.text();
2197
- let parsed = null;
2198
- try {
2199
- parsed = JSON.parse(text);
2200
- } catch {
2201
- }
2202
- if (!res.ok || !parsed?.success || !parsed.data) {
2203
- throw new OwneyError(
2204
- "SPONSOR_REQUEST_FAILED",
2205
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2167
+ res = await fetch(
2168
+ `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2206
2169
  {
2207
- statusCode: res.status,
2208
- responseBody: text.slice(0, 500),
2209
- // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
2210
- // before broadcast, so it is safe to fall back to a user-paid deposit.
2211
- safeToFallback: res.status === 503
2170
+ headers: { "x-owney-api-key": input.apiKey }
2212
2171
  }
2213
2172
  );
2214
- }
2215
- return parsed.data;
2216
- }
2217
- async function postSponsorPermit2Transfer(input) {
2218
- const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2219
- let res;
2220
- try {
2221
- res = await fetch(`${base5}/api/v1/sponsor/permit2-transfer`, {
2222
- method: "POST",
2223
- headers: {
2224
- "content-type": "application/json",
2225
- "x-owney-api-key": input.apiKey,
2226
- ...input.yieldseekerSignature ? { Authorization: `Signature ${input.yieldseekerSignature}` } : {}
2227
- },
2228
- body: JSON.stringify(input.body)
2229
- });
2230
2173
  } catch (networkError) {
2231
2174
  throw new OwneyError(
2232
2175
  "SPONSOR_REQUEST_FAILED",
2233
2176
  `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2234
- { cause: String(networkError), safeToFallback: false }
2177
+ { cause: String(networkError), safeToFallback: true }
2235
2178
  );
2236
2179
  }
2237
2180
  const text = await res.text();
@@ -2240,60 +2183,64 @@ async function postSponsorPermit2Transfer(input) {
2240
2183
  parsed = JSON.parse(text);
2241
2184
  } catch {
2242
2185
  }
2243
- if (!res.ok || !parsed?.success || !parsed.data) {
2186
+ if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
2244
2187
  throw new OwneyError(
2245
2188
  "SPONSOR_REQUEST_FAILED",
2246
2189
  `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2247
2190
  {
2248
2191
  statusCode: res.status,
2249
2192
  responseBody: text.slice(0, 500),
2250
- safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
2193
+ safeToFallback: true
2251
2194
  }
2252
2195
  );
2253
2196
  }
2254
- return parsed.data;
2197
+ return parsed.data.relayer;
2255
2198
  }
2256
- async function getSponsorRelayerAddress(input) {
2257
- const base5 = input.baseUrl ?? ROUTING_API_BASE_URL;
2199
+ async function postSponsorBatchTransfer(input) {
2258
2200
  let res;
2259
2201
  try {
2260
2202
  res = await fetch(
2261
- `${base5}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
2203
+ `${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
2262
2204
  {
2263
- headers: { "x-owney-api-key": input.apiKey }
2205
+ method: "POST",
2206
+ headers: {
2207
+ "content-type": "application/json",
2208
+ "x-owney-api-key": input.apiKey
2209
+ },
2210
+ body: JSON.stringify(input.body)
2264
2211
  }
2265
2212
  );
2266
- } catch (networkError) {
2213
+ } catch {
2267
2214
  throw new OwneyError(
2268
2215
  "SPONSOR_REQUEST_FAILED",
2269
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
2270
- { cause: String(networkError), safeToFallback: true }
2216
+ "Deposit status is unknown. Retry the same amount to check it.",
2217
+ { safeToFallback: false }
2271
2218
  );
2272
2219
  }
2273
- const text = await res.text();
2274
- let parsed = null;
2275
- try {
2276
- parsed = JSON.parse(text);
2277
- } catch {
2278
- }
2279
- if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
2220
+ const parsed = await res.json().catch(() => null);
2221
+ if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
2280
2222
  throw new OwneyError(
2281
2223
  "SPONSOR_REQUEST_FAILED",
2282
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
2224
+ "Deposit could not be confirmed. Retry the same amount to check its status.",
2283
2225
  {
2284
2226
  statusCode: res.status,
2285
- responseBody: text.slice(0, 500),
2286
- safeToFallback: true
2227
+ safeToFallback: false,
2228
+ notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
2287
2229
  }
2288
2230
  );
2289
2231
  }
2290
- return parsed.data.relayer;
2232
+ return parsed.data;
2291
2233
  }
2292
2234
 
2293
2235
  // src/lib/permit2.ts
2294
2236
  var import_viem3 = require("viem");
2295
2237
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2296
- var MAX_UINT256 = 2n ** 256n - 1n;
2238
+ function permit2ApprovalAmount(requiredAmount) {
2239
+ if (requiredAmount <= 0n) {
2240
+ throw new Error("Permit2 approval requires a positive deposit amount");
2241
+ }
2242
+ return requiredAmount;
2243
+ }
2297
2244
  var ERC20_ALLOWANCE_ABI = [
2298
2245
  {
2299
2246
  type: "function",
@@ -2323,29 +2270,6 @@ var ERC20_ALLOWANCE_ABI = [
2323
2270
  outputs: [{ name: "", type: "uint256" }]
2324
2271
  }
2325
2272
  ];
2326
- function buildPermitTransferFromTypedData(input) {
2327
- return {
2328
- domain: {
2329
- name: "Permit2",
2330
- chainId: input.chainId,
2331
- verifyingContract: PERMIT2_ADDRESS
2332
- },
2333
- types: {
2334
- PermitTransferFrom: [
2335
- { name: "permitted", type: "TokenPermissions" },
2336
- { name: "spender", type: "address" },
2337
- { name: "nonce", type: "uint256" },
2338
- { name: "deadline", type: "uint256" }
2339
- ],
2340
- TokenPermissions: [
2341
- { name: "token", type: "address" },
2342
- { name: "amount", type: "uint256" }
2343
- ]
2344
- },
2345
- primaryType: "PermitTransferFrom",
2346
- message: input.message
2347
- };
2348
- }
2349
2273
  function randomPermit2Nonce() {
2350
2274
  const bytes = new Uint8Array(32);
2351
2275
  globalThis.crypto.getRandomValues(bytes);
@@ -2388,88 +2312,6 @@ function makeVerificationAwareDepositCallback(implementation) {
2388
2312
  });
2389
2313
  return callback;
2390
2314
  }
2391
- function makeSponsoredDepositCallback(deps) {
2392
- const post = deps.httpPost ?? postSponsorTransferAuth;
2393
- return makeVerificationAwareDepositCallback(
2394
- async (smartWallet, chainId, amount, verification) => {
2395
- const cid = chainId;
2396
- const token = deps.tokenAddressByChain[cid];
2397
- if (!token) {
2398
- throw new OwneyError(
2399
- "CHAIN_UNSUPPORTED",
2400
- `No sponsored token configured for chain ${chainId}`
2401
- );
2402
- }
2403
- const pub = deps.getPublicClient(cid);
2404
- const wallet = deps.getWalletClient(cid);
2405
- await ensureWalletOnChain(pub, wallet, cid);
2406
- try {
2407
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
2408
- if (balance < BigInt(amount)) {
2409
- throw new OwneyError(
2410
- "DEPOSIT_INSUFFICIENT_BALANCE",
2411
- "Insufficient balance for this deposit.",
2412
- { token, chainId: cid, balance: balance.toString(), amount }
2413
- );
2414
- }
2415
- } catch (err) {
2416
- if (err instanceof OwneyError) throw err;
2417
- console.warn(
2418
- "[owney-sdk] Deposit balance pre-check failed (non-fatal):",
2419
- err instanceof Error ? err.message : String(err)
2420
- );
2421
- }
2422
- const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
2423
- const validAfter = 0n;
2424
- const validBefore = BigInt(
2425
- Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS
2426
- );
2427
- const nonce = randomAuthNonce();
2428
- const typedData = buildTransferWithAuthorizationTypedData({
2429
- token,
2430
- chainId: cid,
2431
- tokenName,
2432
- tokenVersion,
2433
- message: {
2434
- from: deps.ownerAddress,
2435
- to: smartWallet,
2436
- value: BigInt(amount),
2437
- validAfter,
2438
- validBefore,
2439
- nonce
2440
- }
2441
- });
2442
- const authSignature = await wallet.signTypedData({
2443
- account: deps.ownerAddress,
2444
- ...typedData
2445
- });
2446
- deps.onApproved?.();
2447
- const result = await post({
2448
- baseUrl: deps.baseUrl,
2449
- apiKey: deps.apiKey,
2450
- ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
2451
- body: {
2452
- chainId: cid,
2453
- token,
2454
- from: deps.ownerAddress,
2455
- to: smartWallet,
2456
- value: amount,
2457
- validAfter: validAfter.toString(),
2458
- validBefore: validBefore.toString(),
2459
- nonce,
2460
- authSignature,
2461
- tokenName,
2462
- tokenVersion,
2463
- ...verification?.agentId === "yieldseeker" ? {
2464
- yieldseekerUserId: verification.userId,
2465
- yieldseekerAgentId: verification.yieldseekerAgentId
2466
- } : {}
2467
- }
2468
- });
2469
- return result.txHash;
2470
- }
2471
- );
2472
- }
2473
2315
 
2474
2316
  // src/agents/yieldseeker/yieldseeker.auth.ts
2475
2317
  var import_siwe = require("siwe");
@@ -3148,7 +2990,7 @@ function depositDestination(context, movement) {
3148
2990
  if (receipt && minted) return { protocol: "Vault", pool: receipt.name || receipt.symbol, tokenSymbol: context.asset };
3149
2991
  return void 0;
3150
2992
  }
3151
- function movementEntry(movement, wallet, agent, asset, ownerAddress, destination) {
2993
+ function movementEntry(movement, wallet, agent, asset, ownerAddress, vaultAddresses, destination) {
3152
2994
  const from = movement.fromAddress.toLowerCase();
3153
2995
  const to = movement.toAddress.toLowerCase();
3154
2996
  const owner = ownerAddress.toLowerCase();
@@ -3158,7 +3000,7 @@ function movementEntry(movement, wallet, agent, asset, ownerAddress, destination
3158
3000
  return void 0;
3159
3001
  }
3160
3002
  let action;
3161
- if (from === owner && to === agentWallet) {
3003
+ if (to === agentWallet && !vaultAddresses.has(from)) {
3162
3004
  action = "Top up";
3163
3005
  } else if (from === agentWallet && to === owner) {
3164
3006
  action = "Withdraw";
@@ -3208,6 +3050,7 @@ function mapYieldseekerHistory(contexts, options) {
3208
3050
  context.agent,
3209
3051
  context.asset,
3210
3052
  options.ownerAddress,
3053
+ options.vaultAddresses,
3211
3054
  depositDestination(context, movement)
3212
3055
  )
3213
3056
  ),
@@ -3288,6 +3131,7 @@ var OWNEY_AGENT_NAME = "owney";
3288
3131
  var YIELDSEEKER_USERNAME_PREFIX = "owney_";
3289
3132
  var YIELDSEEKER_USERNAME_RANDOM_LENGTH = 14;
3290
3133
  var YIELDSEEKER_USERNAME_CREATE_ATTEMPTS = 3;
3134
+ var YIELDSEEKER_YIELD_OPTIONS_CACHE_MS = 6e4;
3291
3135
  function generateYieldseekerUsername() {
3292
3136
  const suffix = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, YIELDSEEKER_USERNAME_RANDOM_LENGTH).toLowerCase();
3293
3137
  return `${YIELDSEEKER_USERNAME_PREFIX}${suffix}`;
@@ -3349,6 +3193,8 @@ var YieldseekerAgent = class {
3349
3193
  agentContexts = /* @__PURE__ */ new Map();
3350
3194
  users = /* @__PURE__ */ new Map();
3351
3195
  pendingAgents = /* @__PURE__ */ new Map();
3196
+ yieldOptions = /* @__PURE__ */ new Map();
3197
+ pendingYieldOptions = /* @__PURE__ */ new Map();
3352
3198
  constructor(owneyApiKey, options = {}) {
3353
3199
  this.api = new YieldseekerApiClient(
3354
3200
  owneyApiKey,
@@ -3415,7 +3261,6 @@ var YieldseekerAgent = class {
3415
3261
  chainId
3416
3262
  });
3417
3263
  }
3418
- await this.deployAfterFunding(state, chainId, context);
3419
3264
  } finally {
3420
3265
  await this.refreshSnapshotAfterMovement(
3421
3266
  state,
@@ -3567,9 +3412,20 @@ var YieldseekerAgent = class {
3567
3412
  historic: true,
3568
3413
  actions: true
3569
3414
  });
3415
+ const catalog = await Promise.all(
3416
+ [...new Set(contexts.map((context) => context.asset))].map(
3417
+ (contextAsset) => this.loadYieldOptions(contextAsset)
3418
+ )
3419
+ );
3420
+ const vaultAddresses = new Set(
3421
+ catalog.flat().filter(
3422
+ (yieldOption) => yieldOption.chainId === chainId && (0, import_viem6.isAddress)(yieldOption.address)
3423
+ ).map((yieldOption) => yieldOption.address.toLowerCase())
3424
+ );
3570
3425
  return mapYieldseekerHistory(contexts, {
3571
3426
  limit: options?.limit ?? 10,
3572
3427
  ownerAddress: state.walletAddress,
3428
+ vaultAddresses,
3573
3429
  ...options?.fromDate ? { fromDate: options.fromDate } : {},
3574
3430
  ...options?.toDate ? { toDate: options.toDate } : {}
3575
3431
  });
@@ -3588,18 +3444,32 @@ var YieldseekerAgent = class {
3588
3444
  const assets = requested ? [requested] : ["USDC", "WETH"];
3589
3445
  const values = await Promise.all(
3590
3446
  assets.map(async (asset) => {
3591
- const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3592
- const response = await this.api.request(
3593
- `/chains/8453/assets/${metadata.address}/yield-options`
3594
- );
3595
- if (!Array.isArray(response?.yieldOptions)) {
3596
- throw this.invalidResponse("yield options");
3597
- }
3598
- return { asset, yieldOptions: response.yieldOptions };
3447
+ return { asset, yieldOptions: await this.loadYieldOptions(asset) };
3599
3448
  })
3600
3449
  );
3601
3450
  return mapYieldseekerAgentApy(values, days);
3602
3451
  }
3452
+ async loadYieldOptions(asset) {
3453
+ const cached = this.yieldOptions.get(asset);
3454
+ if (cached && cached.expiresAt > Date.now()) return cached.value;
3455
+ const pending = this.pendingYieldOptions.get(asset);
3456
+ if (pending) return pending;
3457
+ const metadata = YIELDSEEKER_ASSET_METADATA[asset];
3458
+ const request = this.api.request(
3459
+ `/chains/8453/assets/${metadata.address}/yield-options`
3460
+ ).then((response) => {
3461
+ if (!Array.isArray(response?.yieldOptions)) {
3462
+ throw this.invalidResponse("yield options");
3463
+ }
3464
+ this.yieldOptions.set(asset, {
3465
+ expiresAt: Date.now() + YIELDSEEKER_YIELD_OPTIONS_CACHE_MS,
3466
+ value: response.yieldOptions
3467
+ });
3468
+ return response.yieldOptions;
3469
+ }).finally(() => this.pendingYieldOptions.delete(asset));
3470
+ this.pendingYieldOptions.set(asset, request);
3471
+ return request;
3472
+ }
3603
3473
  userKey(state, chainId) {
3604
3474
  return `${state.walletAddress.toLowerCase()}:${chainId}`;
3605
3475
  }
@@ -3678,8 +3548,9 @@ var YieldseekerAgent = class {
3678
3548
  const pending = this.pendingAgents.get(key2);
3679
3549
  if (pending) return pending;
3680
3550
  const request = this.resolveAgent(state, chainId, asset, true).then(
3681
- (context) => {
3551
+ async (context) => {
3682
3552
  if (!context) throw this.invalidResponse("agent creation");
3553
+ await this.deployAgent(state, chainId, context);
3683
3554
  this.agentContexts.set(key2, context);
3684
3555
  return context;
3685
3556
  }
@@ -3824,24 +3695,21 @@ var YieldseekerAgent = class {
3824
3695
  ...actions?.actions ? { actions: actions.actions } : {}
3825
3696
  };
3826
3697
  }
3827
- async deployAfterFunding(state, chainId, context) {
3698
+ async deployAgent(state, chainId, context) {
3828
3699
  if (context.wallet.initializedDate != null) return;
3829
- try {
3830
- const deployed = await this.walletRequest(
3831
- state,
3832
- chainId,
3833
- this.agentPath(context, "deploy"),
3834
- { method: "POST", body: {} }
3835
- );
3836
- if (deployed?.agentWallet) {
3837
- context.wallet = deployed.agentWallet;
3838
- }
3839
- } catch (error) {
3840
- console.warn(
3841
- "[owney-sdk] Yieldseeker deposit is funded but not deployable yet:",
3842
- error
3843
- );
3700
+ const walletAddress = context.wallet.walletAddress.toLowerCase();
3701
+ const deployed = await this.walletRequest(
3702
+ state,
3703
+ chainId,
3704
+ this.agentPath(context, "deploy"),
3705
+ { method: "POST", body: {} }
3706
+ );
3707
+ if (!deployed?.agentWallet || !(0, import_viem6.isAddress)(deployed.agentWallet.walletAddress) || deployed.agentWallet.walletAddress.toLowerCase() !== walletAddress) {
3708
+ throw this.invalidResponse("agent deployment", {
3709
+ reason: "Deploy did not return the expected Agent Wallet."
3710
+ });
3844
3711
  }
3712
+ context.wallet = deployed.agentWallet;
3845
3713
  }
3846
3714
  async refreshSnapshotAfterMovement(state, chainId, context, movement) {
3847
3715
  try {
@@ -4325,105 +4193,305 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
4325
4193
  }
4326
4194
 
4327
4195
  // src/client.ts
4328
- var import_viem9 = require("viem");
4196
+ var import_viem11 = require("viem");
4329
4197
  var import_chains4 = require("viem/chains");
4330
4198
 
4331
- // src/lib/sponsored-weth-deposit.ts
4332
- var PERMIT_WINDOW_SECONDS = 15 * 60;
4333
- function makeSponsoredWethCallback(deps) {
4334
- const get = deps.httpGet ?? getSponsorRelayerAddress;
4335
- const post = deps.httpPost ?? postSponsorPermit2Transfer;
4336
- return makeVerificationAwareDepositCallback(
4337
- async (smartWallet, chainId, amount, verification) => {
4338
- const cid = chainId;
4339
- const token = deps.tokenAddressByChain[cid];
4340
- if (!token) {
4341
- throw new OwneyError(
4342
- "CHAIN_UNSUPPORTED",
4343
- `No sponsored WETH configured for chain ${chainId}`
4344
- );
4345
- }
4346
- const amountWei = BigInt(amount);
4347
- const pub = deps.getPublicClient(cid);
4348
- const wallet = deps.getWalletClient(cid);
4349
- await ensureWalletOnChain(pub, wallet, cid);
4350
- try {
4351
- const balance = await readErc20Balance(pub, token, deps.ownerAddress);
4352
- if (balance < amountWei) {
4353
- throw new OwneyError(
4354
- "DEPOSIT_INSUFFICIENT_BALANCE",
4355
- "Insufficient WETH balance for this deposit.",
4356
- { token, chainId: cid, balance: balance.toString(), amount }
4357
- );
4358
- }
4359
- } catch (err) {
4360
- if (err instanceof OwneyError) throw err;
4361
- console.warn(
4362
- "[owney-sdk] WETH balance pre-check failed (non-fatal):",
4363
- err instanceof Error ? err.message : String(err)
4364
- );
4365
- }
4366
- const allowance = await readPermit2Allowance(
4367
- pub,
4368
- token,
4369
- deps.ownerAddress
4199
+ // src/lib/sponsored-token-batch.ts
4200
+ var import_viem9 = require("viem");
4201
+
4202
+ // src/lib/permit2-batch.ts
4203
+ var import_viem8 = require("viem");
4204
+ var BATCH_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4205
+ var PERMIT_BATCH_TYPES = {
4206
+ PermitBatchWitnessTransferFrom: [
4207
+ { name: "permitted", type: "TokenPermissions[]" },
4208
+ { name: "spender", type: "address" },
4209
+ { name: "nonce", type: "uint256" },
4210
+ { name: "deadline", type: "uint256" },
4211
+ { name: "witness", type: "Deposit" }
4212
+ ],
4213
+ Deposit: [{ name: "recipients", type: "address[]" }],
4214
+ TokenPermissions: [
4215
+ { name: "token", type: "address" },
4216
+ { name: "amount", type: "uint256" }
4217
+ ]
4218
+ };
4219
+ var PERMIT2_BATCH_ABI = (0, import_viem8.parseAbi)([
4220
+ "struct TokenPermissions { address token; uint256 amount; }",
4221
+ "struct PermitBatchTransferFrom { TokenPermissions[] permitted; uint256 nonce; uint256 deadline; }",
4222
+ "struct SignatureTransferDetails { address to; uint256 requestedAmount; }",
4223
+ "function permitWitnessTransferFrom(PermitBatchTransferFrom permit,SignatureTransferDetails[] transferDetails,address owner,bytes32 witness,string witnessTypeString,bytes signature)",
4224
+ "function nonceBitmap(address owner,uint256 wordPos) view returns (uint256)"
4225
+ ]);
4226
+ function batchPermit(b) {
4227
+ return {
4228
+ permitted: b.transfers.map((t) => ({
4229
+ token: b.token,
4230
+ amount: BigInt(t.amount)
4231
+ })),
4232
+ nonce: BigInt(b.nonce),
4233
+ deadline: BigInt(b.deadline)
4234
+ };
4235
+ }
4236
+ function batchTypedData(b, spender) {
4237
+ return {
4238
+ domain: {
4239
+ name: "Permit2",
4240
+ chainId: b.chainId,
4241
+ verifyingContract: BATCH_PERMIT2_ADDRESS
4242
+ },
4243
+ types: PERMIT_BATCH_TYPES,
4244
+ primaryType: "PermitBatchWitnessTransferFrom",
4245
+ message: {
4246
+ ...batchPermit(b),
4247
+ spender,
4248
+ witness: { recipients: b.transfers.map((t) => t.to) }
4249
+ }
4250
+ };
4251
+ }
4252
+
4253
+ // src/lib/sponsored-token-batch.ts
4254
+ var memory = /* @__PURE__ */ new Map();
4255
+ var inflight = /* @__PURE__ */ new Map();
4256
+ var planOf = (transfers) => JSON.stringify(
4257
+ transfers.map((t) => ({
4258
+ to: t.to.toLowerCase(),
4259
+ amount: BigInt(t.amount).toString()
4260
+ }))
4261
+ );
4262
+ function read(key2) {
4263
+ return typeof window === "undefined" ? memory.get(key2) : window.localStorage.getItem(key2);
4264
+ }
4265
+ function save(key2, body) {
4266
+ const value = JSON.stringify({
4267
+ ...body,
4268
+ transfers: body.transfers.map(({ to, amount }) => ({ to, amount }))
4269
+ });
4270
+ if (typeof window === "undefined") memory.set(key2, value);
4271
+ else window.localStorage.setItem(key2, value);
4272
+ }
4273
+ function clear(key2) {
4274
+ if (typeof window === "undefined") memory.delete(key2);
4275
+ else window.localStorage.removeItem(key2);
4276
+ }
4277
+ function sponsorTokenBatch(i) {
4278
+ const key2 = `owney.token-batch.v1:${(0, import_viem9.keccak256)((0, import_viem9.toBytes)(i.apiKey))}:${i.baseUrl ?? "default"}:${i.chainId}:${i.owner.toLowerCase()}:${i.token.toLowerCase()}`;
4279
+ const plan = planOf(i.transfers);
4280
+ const active = inflight.get(key2);
4281
+ if (active) {
4282
+ if (active.plan !== plan)
4283
+ return Promise.reject(
4284
+ new Error(
4285
+ "A token deposit is already in progress. Wait for its result before depositing again."
4286
+ )
4370
4287
  );
4371
- if (allowance < amountWei) {
4372
- throw new OwneyError(
4373
- "PERMIT2_APPROVAL_REQUIRED",
4374
- "WETH gasless deposits need a one-time Permit2 approval; call approvePermit2() first",
4375
- { token, chainId: cid, allowance: allowance.toString(), amount }
4376
- );
4288
+ return active.promise;
4289
+ }
4290
+ const promise = execute(i, key2, plan).finally(() => inflight.delete(key2));
4291
+ inflight.set(key2, { plan, promise });
4292
+ return promise;
4293
+ }
4294
+ async function execute(i, key2, plan) {
4295
+ if (!i.transfers.length || i.transfers.length > 16 || i.transfers.some(
4296
+ (t) => BigInt(t.amount) <= 0n || BigInt(t.amount) >= 1n << 256n
4297
+ ) || new Set(i.transfers.map((t) => t.to.toLowerCase())).size !== i.transfers.length)
4298
+ throw new Error("Invalid token deposit shares.");
4299
+ const send = async (initial) => {
4300
+ let body = initial;
4301
+ save(key2, body);
4302
+ try {
4303
+ if (!body.serializedTransaction) {
4304
+ const prepared = await postSponsorBatchTransfer({
4305
+ apiKey: i.apiKey,
4306
+ baseUrl: i.baseUrl,
4307
+ body
4308
+ });
4309
+ if (!prepared.serializedTransaction || (0, import_viem9.keccak256)(prepared.serializedTransaction) !== prepared.txHash)
4310
+ throw new Error(
4311
+ "Sponsorship API did not return a valid prepared transaction."
4312
+ );
4313
+ body = {
4314
+ ...body,
4315
+ serializedTransaction: prepared.serializedTransaction
4316
+ };
4317
+ save(key2, body);
4377
4318
  }
4378
- const relayer = await get({
4379
- baseUrl: deps.baseUrl,
4380
- apiKey: deps.apiKey,
4381
- chainId: cid
4382
- });
4383
- const nonce = randomPermit2Nonce();
4384
- const deadline = BigInt(
4385
- Math.floor(Date.now() / 1e3) + PERMIT_WINDOW_SECONDS
4386
- );
4387
- const typedData = buildPermitTransferFromTypedData({
4388
- chainId: cid,
4389
- message: {
4390
- permitted: { token, amount: amountWei },
4391
- spender: relayer,
4392
- nonce,
4393
- deadline
4394
- }
4395
- });
4396
- const signature = await wallet.signTypedData({
4397
- account: deps.ownerAddress,
4398
- ...typedData
4399
- });
4400
- deps.onApproved?.();
4401
- const result = await post({
4402
- baseUrl: deps.baseUrl,
4403
- apiKey: deps.apiKey,
4404
- ...verification?.agentId === "yieldseeker" ? { yieldseekerSignature: verification.signature } : {},
4405
- body: {
4406
- chainId: cid,
4407
- token,
4408
- from: deps.ownerAddress,
4409
- to: smartWallet,
4410
- amount,
4411
- nonce: nonce.toString(),
4412
- deadline: deadline.toString(),
4413
- signature,
4414
- ...verification?.agentId === "yieldseeker" ? {
4415
- yieldseekerUserId: verification.userId,
4416
- yieldseekerAgentId: verification.yieldseekerAgentId
4417
- } : {}
4418
- }
4319
+ const result = await postSponsorBatchTransfer({
4320
+ apiKey: i.apiKey,
4321
+ baseUrl: i.baseUrl,
4322
+ body
4419
4323
  });
4324
+ if (result.txHash !== (0, import_viem9.keccak256)(body.serializedTransaction))
4325
+ throw new Error(
4326
+ "Sponsorship receipt does not match the pending transaction."
4327
+ );
4328
+ clear(key2);
4420
4329
  return result.txHash;
4330
+ } catch (error) {
4331
+ if (!body.serializedTransaction || error instanceof OwneyError && error.details?.notSubmitted === true)
4332
+ clear(key2);
4333
+ throw error;
4421
4334
  }
4335
+ };
4336
+ const saved = read(key2);
4337
+ if (saved) {
4338
+ const previous = JSON.parse(saved);
4339
+ if (previous.chainId !== i.chainId || !(0, import_viem9.isAddressEqual)(previous.from, i.owner) || !(0, import_viem9.isAddressEqual)(previous.token, i.token) || planOf(previous.transfers) !== plan)
4340
+ throw new Error(
4341
+ "Retry the previous token deposit and agent split first to reconcile its status."
4342
+ );
4343
+ i.onApproved?.();
4344
+ return send({ ...previous, transfers: i.transfers });
4345
+ }
4346
+ const total = i.transfers.reduce((sum, t) => sum + BigInt(t.amount), 0n);
4347
+ const [balance, allowance] = await Promise.all([
4348
+ readErc20Balance(i.pub, i.token, i.owner),
4349
+ readPermit2Allowance(i.pub, i.token, i.owner)
4350
+ ]);
4351
+ if (balance < total)
4352
+ throw new OwneyError(
4353
+ "DEPOSIT_INSUFFICIENT_BALANCE",
4354
+ "Insufficient token balance for this deposit."
4355
+ );
4356
+ if (allowance < total)
4357
+ throw new OwneyError(
4358
+ "PERMIT2_APPROVAL_REQUIRED",
4359
+ "token deposits need a one-time Permit2 approval."
4360
+ );
4361
+ const relayer = await getSponsorRelayerAddress({
4362
+ apiKey: i.apiKey,
4363
+ baseUrl: i.baseUrl,
4364
+ chainId: i.chainId
4365
+ });
4366
+ const now = (await i.pub.getBlock()).timestamp;
4367
+ const unsigned = {
4368
+ chainId: i.chainId,
4369
+ token: i.token,
4370
+ from: i.owner,
4371
+ transfers: i.transfers,
4372
+ nonce: randomPermit2Nonce().toString(),
4373
+ deadline: (now + 900n).toString()
4374
+ };
4375
+ const signature = await i.wallet.signTypedData({
4376
+ account: i.owner,
4377
+ ...batchTypedData(unsigned, relayer)
4378
+ });
4379
+ i.onApproved?.();
4380
+ return send({ ...unsigned, signature });
4381
+ }
4382
+
4383
+ // src/lib/sponsored-token-deposit.ts
4384
+ function makeSponsoredTokenCallback(deps) {
4385
+ const batch = async (chainId, transfers) => {
4386
+ if (chainId !== 8453 && chainId !== 42161 && chainId !== 1)
4387
+ throw new OwneyError(
4388
+ "CHAIN_UNSUPPORTED",
4389
+ `No sponsored token configured for chain ${chainId}`
4390
+ );
4391
+ const token = deps.tokenAddressByChain[chainId];
4392
+ if (!token)
4393
+ throw new OwneyError(
4394
+ "CHAIN_UNSUPPORTED",
4395
+ `No sponsored token configured for chain ${chainId}`
4396
+ );
4397
+ const pub = deps.getPublicClient(chainId), wallet = deps.getWalletClient(chainId);
4398
+ await ensureWalletOnChain(pub, wallet, chainId);
4399
+ return sponsorTokenBatch({
4400
+ apiKey: deps.apiKey,
4401
+ baseUrl: deps.baseUrl,
4402
+ owner: deps.ownerAddress,
4403
+ token,
4404
+ chainId,
4405
+ transfers,
4406
+ pub,
4407
+ wallet,
4408
+ onApproved: deps.onApproved
4409
+ });
4410
+ };
4411
+ const callback = makeVerificationAwareDepositCallback(
4412
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4422
4413
  );
4414
+ registerDepositBatch(callback, batch);
4415
+ return callback;
4416
+ }
4417
+
4418
+ // src/lib/agent-deposit-batch.ts
4419
+ function deferred() {
4420
+ let resolve, reject;
4421
+ const promise = new Promise((yes, no) => {
4422
+ resolve = yes;
4423
+ reject = no;
4424
+ });
4425
+ void promise.catch(() => {
4426
+ });
4427
+ return { promise, resolve, reject };
4428
+ }
4429
+ async function runAgentDepositBatch(chainId, legs, transfer) {
4430
+ const funding = deferred();
4431
+ const tasks = [];
4432
+ const transfers = [];
4433
+ try {
4434
+ for (const leg of legs) {
4435
+ const ready = deferred();
4436
+ let entered = false;
4437
+ const callback = makeVerificationAwareDepositCallback(
4438
+ (to, cid, amount, verification) => {
4439
+ if (entered || cid !== chainId || BigInt(amount) !== BigInt(leg.amount)) {
4440
+ const error = new Error(
4441
+ "Agent changed its prepared deposit share."
4442
+ );
4443
+ ready.reject(error);
4444
+ throw error;
4445
+ }
4446
+ entered = true;
4447
+ ready.resolve(toBatchTransfer(to, amount, verification));
4448
+ return funding.promise;
4449
+ }
4450
+ );
4451
+ const task = Promise.resolve().then(() => leg.run(callback));
4452
+ tasks.push(task);
4453
+ void task.then(
4454
+ () => {
4455
+ if (!entered)
4456
+ ready.reject(
4457
+ new Error("Agent did not prepare a deposit transfer.")
4458
+ );
4459
+ },
4460
+ (error) => ready.reject(error)
4461
+ );
4462
+ transfers.push(await ready.promise);
4463
+ }
4464
+ const txHash = await transfer(chainId, transfers);
4465
+ funding.resolve(txHash);
4466
+ const settled = await Promise.allSettled(tasks);
4467
+ const agentResults = {};
4468
+ const failures = [];
4469
+ for (const [index, result] of settled.entries()) {
4470
+ if (result.status === "fulfilled")
4471
+ agentResults[legs[index].id] = result.value;
4472
+ else failures.push(legs[index].id);
4473
+ }
4474
+ if (failures.length)
4475
+ throw new OwneyError(
4476
+ "DEPOSIT_PARTIAL_FAILURE",
4477
+ "The deposit was sent to all agents, but some agent updates could not be confirmed. Check activity before depositing again.",
4478
+ {
4479
+ txHash,
4480
+ fundsSubmitted: true,
4481
+ agentResults,
4482
+ failedAgentIds: failures
4483
+ }
4484
+ );
4485
+ return { agentResults };
4486
+ } catch (error) {
4487
+ funding.reject(error);
4488
+ await Promise.allSettled(tasks);
4489
+ throw error;
4490
+ }
4423
4491
  }
4424
4492
 
4425
4493
  // src/lib/sponsored-calls-deposit.ts
4426
- var import_viem8 = require("viem");
4494
+ var import_viem10 = require("viem");
4427
4495
  var DEFAULT_POLL_INTERVAL_MS = 1500;
4428
4496
  var DEFAULT_MAX_POLLS = 30;
4429
4497
  async function paymasterSupported(provider, owner, chainId) {
@@ -4431,7 +4499,7 @@ async function paymasterSupported(provider, owner, chainId) {
4431
4499
  method: "wallet_getCapabilities",
4432
4500
  params: [owner]
4433
4501
  });
4434
- const forChain = caps?.[(0, import_viem8.toHex)(chainId)] ?? caps?.[String(chainId)];
4502
+ const forChain = caps?.[(0, import_viem10.toHex)(chainId)] ?? caps?.[String(chainId)];
4435
4503
  return Boolean(forChain?.paymasterService?.supported);
4436
4504
  }
4437
4505
  function makeSponsoredCallsCallback(deps) {
@@ -4449,7 +4517,7 @@ function makeSponsoredCallsCallback(deps) {
4449
4517
  }
4450
4518
  return new URL(configured, origin).toString();
4451
4519
  };
4452
- return makeVerificationAwareDepositCallback(async (smartWallet, chainId, amount, verification) => {
4520
+ const batch = async (chainId, transfers) => {
4453
4521
  const cid = chainId;
4454
4522
  const token = deps.tokenAddressByChain[cid];
4455
4523
  if (!token) {
@@ -4465,19 +4533,24 @@ function makeSponsoredCallsCallback(deps) {
4465
4533
  { chainId }
4466
4534
  );
4467
4535
  }
4468
- const data = (0, import_viem8.encodeFunctionData)({
4469
- abi: import_viem8.erc20Abi,
4470
- functionName: "transfer",
4471
- args: [smartWallet, BigInt(amount)]
4472
- });
4536
+ const calls = transfers.map((transfer) => ({
4537
+ to: token,
4538
+ value: "0x0",
4539
+ data: (0, import_viem10.encodeFunctionData)({
4540
+ abi: import_viem10.erc20Abi,
4541
+ functionName: "transfer",
4542
+ args: [transfer.to, BigInt(transfer.amount)]
4543
+ })
4544
+ }));
4473
4545
  let paymasterUrl = absolutePaymasterUrl();
4474
- if (verification?.agentId === "yieldseeker") {
4475
- if (chainId !== 8453) {
4546
+ for (const transfer of transfers) {
4547
+ const verification = transfer.yieldseeker;
4548
+ if (!verification) continue;
4549
+ if (chainId !== 8453)
4476
4550
  throw new OwneyError(
4477
4551
  "CHAIN_UNSUPPORTED",
4478
- `Yieldseeker Base Account sponsorship is not available on chain ${chainId}.`
4552
+ `Yieldseeker sponsorship is not available on chain ${chainId}.`
4479
4553
  );
4480
- }
4481
4554
  const { intent } = await postPaymasterIntent({
4482
4555
  baseUrl: deps.routingApiBaseUrl,
4483
4556
  apiKey: deps.apiKey,
@@ -4486,14 +4559,14 @@ function makeSponsoredCallsCallback(deps) {
4486
4559
  chainId,
4487
4560
  token,
4488
4561
  from: deps.ownerAddress,
4489
- to: smartWallet,
4490
- amount,
4562
+ to: transfer.to,
4563
+ amount: transfer.amount,
4491
4564
  yieldseekerUserId: verification.userId,
4492
- yieldseekerAgentId: verification.yieldseekerAgentId
4565
+ yieldseekerAgentId: verification.agentId
4493
4566
  }
4494
4567
  });
4495
4568
  const url = new URL(paymasterUrl);
4496
- url.searchParams.set("owneyIntent", intent);
4569
+ url.searchParams.append("owneyIntent", intent);
4497
4570
  paymasterUrl = url.toString();
4498
4571
  }
4499
4572
  const sendResult = await deps.provider.request({
@@ -4502,9 +4575,9 @@ function makeSponsoredCallsCallback(deps) {
4502
4575
  {
4503
4576
  version: "2.0.0",
4504
4577
  from: deps.ownerAddress,
4505
- chainId: (0, import_viem8.toHex)(chainId),
4506
- atomicRequired: false,
4507
- calls: [{ to: token, value: "0x0", data }],
4578
+ chainId: (0, import_viem10.toHex)(chainId),
4579
+ atomicRequired: transfers.length > 1,
4580
+ calls,
4508
4581
  capabilities: {
4509
4582
  paymasterService: { url: paymasterUrl }
4510
4583
  }
@@ -4526,7 +4599,24 @@ function makeSponsoredCallsCallback(deps) {
4526
4599
  params: [callsId]
4527
4600
  });
4528
4601
  const txHash = status?.receipts?.[0]?.transactionHash;
4529
- if (txHash) return txHash;
4602
+ if (status?.receipts?.some((receipt) => receipt.status === "0x0") || typeof status?.status === "number" && status.status >= 400) {
4603
+ throw new OwneyError(
4604
+ "SPONSOR_REQUEST_FAILED",
4605
+ "The sponsored deposit did not complete successfully.",
4606
+ { chainId, callsId, safeToFallback: false }
4607
+ );
4608
+ }
4609
+ if (txHash && (transfers.length === 1 || status?.status === 200 || status?.status === "CONFIRMED")) {
4610
+ if (status?.receipts?.some(
4611
+ (receipt) => receipt.transactionHash !== txHash
4612
+ ))
4613
+ throw new OwneyError(
4614
+ "SPONSORED_CALLS_NO_RECEIPT",
4615
+ "The wallet returned multiple transactions for an atomic deposit. Check activity before trying again.",
4616
+ { chainId, callsId }
4617
+ );
4618
+ return txHash;
4619
+ }
4530
4620
  if (pollIntervalMs > 0) {
4531
4621
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
4532
4622
  }
@@ -4536,7 +4626,12 @@ function makeSponsoredCallsCallback(deps) {
4536
4626
  `No receipt for calls ${callsId} after ${maxPolls} polls; the deposit may still settle.`,
4537
4627
  { chainId, callsId }
4538
4628
  );
4539
- });
4629
+ };
4630
+ const callback = makeVerificationAwareDepositCallback(
4631
+ (to, chainId, amount, verification) => batch(chainId, [toBatchTransfer(to, amount, verification)])
4632
+ );
4633
+ registerDepositBatch(callback, batch);
4634
+ return callback;
4540
4635
  }
4541
4636
 
4542
4637
  // src/client.ts
@@ -4575,6 +4670,11 @@ var SPONSORED_WETH_BY_CHAIN = {
4575
4670
  42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
4576
4671
  1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
4577
4672
  };
4673
+ var SPONSORED_TOKENS_BY_ASSET = { USDC: SPONSORED_USDC_BY_CHAIN, WETH: SPONSORED_WETH_BY_CHAIN };
4674
+ function sponsoredTokensFor(asset) {
4675
+ if (!Object.prototype.hasOwnProperty.call(SPONSORED_TOKENS_BY_ASSET, asset)) throw new OwneyError("ASSET_UNSUPPORTED", `No sponsored token configured for ${asset}`);
4676
+ return SPONSORED_TOKENS_BY_ASSET[asset];
4677
+ }
4578
4678
  function shouldFallbackToUserPaid(error, asset, appCallback) {
4579
4679
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
4580
4680
  }
@@ -4709,18 +4809,13 @@ var OwneySDK = class {
4709
4809
  }
4710
4810
  return this.state.provider;
4711
4811
  }
4712
- /**
4713
- * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
4714
- * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
4715
- * provider with viem `custom(provider)` to read token meta and sign the
4716
- * `TransferWithAuthorization`, then POSTs to the sponsor API.
4717
- */
4812
+ /** Builds the default USDC batch callback for the connected wallet. */
4718
4813
  getDefaultSponsoredCallback(onApproved) {
4719
4814
  if (!onApproved && this.cachedSponsoredCallback)
4720
4815
  return this.cachedSponsoredCallback;
4721
4816
  const provider = this.requireConnectedProvider();
4722
4817
  const owner = this.state.walletAddress;
4723
- const callback = makeSponsoredDepositCallback({
4818
+ const callback = makeSponsoredTokenCallback({
4724
4819
  apiKey: this.apiKey,
4725
4820
  baseUrl: this.routingApiBaseUrl,
4726
4821
  ownerAddress: owner,
@@ -4729,25 +4824,20 @@ var OwneySDK = class {
4729
4824
  // Casts work around viem's chain-narrowed Client vs the generic
4730
4825
  // PublicClient/WalletClient param types — structurally identical at
4731
4826
  // runtime, but the two share a name TS treats as unrelated.
4732
- getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
4827
+ getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
4733
4828
  chain: VIEM_CHAIN2[cid],
4734
- transport: (0, import_viem9.custom)(provider)
4829
+ transport: (0, import_viem11.custom)(provider)
4735
4830
  }),
4736
- getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
4831
+ getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
4737
4832
  account: owner,
4738
4833
  chain: VIEM_CHAIN2[cid],
4739
- transport: (0, import_viem9.custom)(provider)
4834
+ transport: (0, import_viem11.custom)(provider)
4740
4835
  })
4741
4836
  });
4742
4837
  if (!onApproved) this.cachedSponsoredCallback = callback;
4743
4838
  return callback;
4744
4839
  }
4745
- /**
4746
- * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
4747
- * callback used when the caller omits `depositCallback` for a WETH
4748
- * deposit. Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
4749
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
4750
- */
4840
+ /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
4751
4841
  getDefaultSponsoredCallsCallback(asset, onApproved) {
4752
4842
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
4753
4843
  if (!onApproved && cached) return cached;
@@ -4759,7 +4849,7 @@ var OwneySDK = class {
4759
4849
  ownerAddress: this.state.walletAddress,
4760
4850
  paymasterServiceUrl: this.paymasterServiceUrl,
4761
4851
  onApproved,
4762
- tokenAddressByChain: asset === "WETH" ? SPONSORED_WETH_BY_CHAIN : SPONSORED_USDC_BY_CHAIN
4852
+ tokenAddressByChain: sponsoredTokensFor(asset)
4763
4853
  });
4764
4854
  if (!onApproved) this.cachedSponsoredCallsCallbacks.set(asset, callback);
4765
4855
  return callback;
@@ -4768,14 +4858,14 @@ var OwneySDK = class {
4768
4858
  * Lazily builds (and caches) the default Permit2 sponsored WETH deposit
4769
4859
  * callback used when the caller omits `depositCallback` for a WETH deposit.
4770
4860
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
4771
- * `PermitTransferFrom` instead of an EIP-3009 authorization.
4861
+ * single-use batch authorization instead of an EIP-3009 authorization.
4772
4862
  */
4773
4863
  getDefaultWethSponsoredCallback(onApproved) {
4774
4864
  if (!onApproved && this.cachedWethSponsoredCallback)
4775
4865
  return this.cachedWethSponsoredCallback;
4776
4866
  const provider = this.requireConnectedProvider();
4777
4867
  const owner = this.state.walletAddress;
4778
- const callback = makeSponsoredWethCallback({
4868
+ const callback = makeSponsoredTokenCallback({
4779
4869
  apiKey: this.apiKey,
4780
4870
  baseUrl: this.routingApiBaseUrl,
4781
4871
  ownerAddress: owner,
@@ -4784,14 +4874,14 @@ var OwneySDK = class {
4784
4874
  // Casts work around viem's chain-narrowed Client vs the generic
4785
4875
  // PublicClient/WalletClient param types — structurally identical at
4786
4876
  // runtime, but the two share a name TS treats as unrelated.
4787
- getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
4877
+ getPublicClient: (cid) => (0, import_viem11.createPublicClient)({
4788
4878
  chain: VIEM_CHAIN2[cid],
4789
- transport: (0, import_viem9.custom)(provider)
4879
+ transport: (0, import_viem11.custom)(provider)
4790
4880
  }),
4791
- getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
4881
+ getWalletClient: (cid) => (0, import_viem11.createWalletClient)({
4792
4882
  account: owner,
4793
4883
  chain: VIEM_CHAIN2[cid],
4794
- transport: (0, import_viem9.custom)(provider)
4884
+ transport: (0, import_viem11.custom)(provider)
4795
4885
  })
4796
4886
  });
4797
4887
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -5081,7 +5171,8 @@ var OwneySDK = class {
5081
5171
  * @param options.asset - Asset symbol to deposit (e.g. "USDC")
5082
5172
  * @param options.depositCallback - Callback that performs the token transfer and returns a tx hash.
5083
5173
  * When agentId is omitted, this callback is invoked once per eligible agent with that agent's
5084
- * split amount and smart wallet address — expect multiple wallet prompts.
5174
+ * split amount and smart wallet address. Default sponsored deposits batch
5175
+ * all shares into one signature; custom callbacks still run once per agent.
5085
5176
  * @param options.agentId - Optional explicit target. Otherwise split equally,
5086
5177
  * or fund remaining agents when a recovery deposit cannot meet every minimum.
5087
5178
  * @returns {OwneyDepositResult} for a single agent, or {OwneyMultiDepositResult} with per-agent results
@@ -5166,6 +5257,39 @@ var OwneySDK = class {
5166
5257
  }
5167
5258
  );
5168
5259
  }
5260
+ const batchTransfer = getDepositBatchTransfer(effectiveCallback);
5261
+ if (!depositCallback && batchTransfer) {
5262
+ return runAgentDepositBatch(
5263
+ chainId,
5264
+ agentAmounts.map(({ agent, amount: amount2 }) => ({
5265
+ id: agent.id,
5266
+ amount: amount2,
5267
+ run: (callback) => withFailureReporting(
5268
+ this.apiKey,
5269
+ agent.id,
5270
+ () => agent.deposit(state, chainId, amount2, asset, callback),
5271
+ this.routingApiBaseUrl
5272
+ )
5273
+ })),
5274
+ async (cid, transfers) => {
5275
+ try {
5276
+ return await batchTransfer(cid, transfers);
5277
+ } catch (error) {
5278
+ if (!(error instanceof OwneyError) || error.code !== "PERMIT2_APPROVAL_REQUIRED")
5279
+ throw error;
5280
+ const requiredAmount = transfers.reduce(
5281
+ (sum, transfer) => sum + BigInt(transfer.amount),
5282
+ 0n
5283
+ );
5284
+ await this.approvePermit2(
5285
+ asset,
5286
+ requiredAmount
5287
+ );
5288
+ return batchTransfer(cid, transfers);
5289
+ }
5290
+ }
5291
+ );
5292
+ }
5169
5293
  const agentResults = {};
5170
5294
  for (const [
5171
5295
  index,
@@ -5205,7 +5329,7 @@ var OwneySDK = class {
5205
5329
  *
5206
5330
  * 1. Missing Permit2 allowance: when the app did not supply its own
5207
5331
  * callback and the attempt fails with `PERMIT2_APPROVAL_REQUIRED` on a
5208
- * WETH deposit, this is the wallet's first gasless WETH deposit. We send
5332
+ * token deposit, this is the wallet's first Permit2 deposit for that token. We send
5209
5333
  * the one-time (user-paid) Permit2 approval via `approvePermit2()` and
5210
5334
  * retry the SAME sponsored attempt once. Bounded to one approval attempt
5211
5335
  * per call so a wallet/agent that keeps reporting the allowance as
@@ -5242,12 +5366,15 @@ var OwneySDK = class {
5242
5366
  try {
5243
5367
  return await attempt(effectiveCallback);
5244
5368
  } catch (error) {
5245
- if (!approvalAttempted && appCallback === void 0 && asset === "WETH" && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
5369
+ if (!approvalAttempted && appCallback === void 0 && (asset === "WETH" || asset === "USDC") && error instanceof OwneyError && error.code === "PERMIT2_APPROVAL_REQUIRED") {
5246
5370
  approvalAttempted = true;
5247
5371
  console.warn(
5248
- "[owney-sdk] First WETH deposit: sending one-time Permit2 approval..."
5372
+ "[owney-sdk] First token deposit: sending one-time Permit2 approval..."
5373
+ );
5374
+ await this.approvePermit2(
5375
+ asset,
5376
+ BigInt(amount)
5249
5377
  );
5250
- await this.approvePermit2();
5251
5378
  continue;
5252
5379
  }
5253
5380
  if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
@@ -5900,43 +6027,44 @@ var OwneySDK = class {
5900
6027
  return pending;
5901
6028
  }
5902
6029
  /**
5903
- * One-time, user-paid approval of Permit2 on the sponsored WETH token for
5904
- * the active chain. Required once per wallet per chain before gasless WETH
5905
- * deposits; afterwards deposit() is signature-only. Resolves only after the
5906
- * approval transaction is mined (1 confirmation), so a subsequent deposit()
5907
- * will see the new allowance; throws if the transaction reverted.
6030
+ * User-paid approval of Permit2 on the selected token for the active chain.
6031
+ * Approves exactly the pending deposit amount. Another approval is required
6032
+ * for a later deposit once this allowance has been consumed. Resolves after
6033
+ * one confirmation so the subsequent deposit attempt sees the new allowance.
6034
+ *
6035
+ * @param requiredAmount Raw base-unit amount the pending deposit must cover.
5908
6036
  * @returns the approval transaction hash.
5909
6037
  */
5910
- async approvePermit2(asset = "WETH") {
5911
- void asset;
6038
+ async approvePermit2(asset = "WETH", requiredAmount = 0n) {
5912
6039
  const state = this.requireState();
5913
6040
  const chainId = this.requireChainId();
5914
- this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
5915
- const token = SPONSORED_WETH_BY_CHAIN[chainId];
6041
+ this.getEligibleAgents(chainId, asset, { excludeDisabled: true });
6042
+ const token = sponsoredTokensFor(asset)[chainId];
5916
6043
  if (!token) {
5917
6044
  throw new OwneyError(
5918
6045
  "CHAIN_UNSUPPORTED",
5919
- `No sponsored WETH on chain ${chainId}`
6046
+ `No sponsored token on chain ${chainId}`
5920
6047
  );
5921
6048
  }
5922
6049
  const provider = this.requireConnectedProvider();
5923
- const wallet = (0, import_viem9.createWalletClient)({
6050
+ const publicClient = (0, import_viem11.createPublicClient)({
6051
+ chain: VIEM_CHAIN2[chainId],
6052
+ transport: (0, import_viem11.custom)(provider)
6053
+ });
6054
+ const approvalAmount = permit2ApprovalAmount(requiredAmount);
6055
+ const wallet = (0, import_viem11.createWalletClient)({
5924
6056
  account: state.walletAddress,
5925
6057
  chain: VIEM_CHAIN2[chainId],
5926
- transport: (0, import_viem9.custom)(provider)
6058
+ transport: (0, import_viem11.custom)(provider)
5927
6059
  });
5928
6060
  const hash = await wallet.writeContract({
5929
6061
  address: token,
5930
6062
  abi: ERC20_ALLOWANCE_ABI,
5931
6063
  functionName: "approve",
5932
- args: [PERMIT2_ADDRESS, MAX_UINT256],
6064
+ args: [PERMIT2_ADDRESS, approvalAmount],
5933
6065
  account: state.walletAddress,
5934
6066
  chain: VIEM_CHAIN2[chainId]
5935
6067
  });
5936
- const publicClient = (0, import_viem9.createPublicClient)({
5937
- chain: VIEM_CHAIN2[chainId],
5938
- transport: (0, import_viem9.custom)(provider)
5939
- });
5940
6068
  const receipt = await publicClient.waitForTransactionReceipt({
5941
6069
  hash,
5942
6070
  confirmations: 1
@@ -6049,7 +6177,7 @@ var OwneySDK = class {
6049
6177
  };
6050
6178
 
6051
6179
  // src/agents/zyfai/zyfai.siwx.ts
6052
- var import_viem10 = require("viem");
6180
+ var import_viem12 = require("viem");
6053
6181
  var import_siwe2 = require("siwe");
6054
6182
  var import_sdk2 = require("@zyfai/sdk");
6055
6183
 
@@ -6176,7 +6304,7 @@ function buildSIWXConfig(deps) {
6176
6304
  issuedAt,
6177
6305
  toString() {
6178
6306
  return new import_siwe2.SiweMessage({
6179
- address: (0, import_viem10.getAddress)(accountAddress),
6307
+ address: (0, import_viem12.getAddress)(accountAddress),
6180
6308
  chainId: numericChainId(chainId),
6181
6309
  domain,
6182
6310
  uri,