@owney/sdk 0.7.25-beta.20 → 0.7.25-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -252,6 +252,240 @@ var AgentChainIncompatibleError = class extends OwneyError {
252
252
  }
253
253
  };
254
254
 
255
+ // src/lib/transfer-auth.ts
256
+ import { bytesToHex } from "viem";
257
+
258
+ // src/lib/sponsor-client.ts
259
+ var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
260
+ async function postPaymasterIntent(input) {
261
+ const base6 = input.baseUrl ?? ROUTING_API_BASE_URL;
262
+ let res;
263
+ try {
264
+ res = await fetch(`${base6}/api/v1/sponsor/paymaster-intent`, {
265
+ method: "POST",
266
+ headers: {
267
+ "content-type": "application/json",
268
+ "x-owney-api-key": input.apiKey,
269
+ Authorization: `Signature ${input.yieldseekerSignature}`
270
+ },
271
+ body: JSON.stringify(input.body)
272
+ });
273
+ } catch (networkError) {
274
+ throw new OwneyError(
275
+ "SPONSOR_REQUEST_FAILED",
276
+ `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
277
+ { cause: String(networkError), safeToFallback: true }
278
+ );
279
+ }
280
+ const text = await res.text();
281
+ let parsed = null;
282
+ try {
283
+ parsed = JSON.parse(text);
284
+ } catch {
285
+ }
286
+ if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
287
+ throw new OwneyError(
288
+ "SPONSOR_REQUEST_FAILED",
289
+ `Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
290
+ {
291
+ statusCode: res.status,
292
+ responseBody: text.slice(0, 500),
293
+ safeToFallback: true
294
+ }
295
+ );
296
+ }
297
+ return parsed.data;
298
+ }
299
+ async function getSponsorRelayerAddress(input) {
300
+ const base6 = input.baseUrl ?? ROUTING_API_BASE_URL;
301
+ let res;
302
+ try {
303
+ res = await fetch(
304
+ `${base6}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
305
+ {
306
+ headers: { "x-owney-api-key": input.apiKey }
307
+ }
308
+ );
309
+ } catch (networkError) {
310
+ throw new OwneyError(
311
+ "SPONSOR_REQUEST_FAILED",
312
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
313
+ { cause: String(networkError), safeToFallback: true }
314
+ );
315
+ }
316
+ const text = await res.text();
317
+ let parsed = null;
318
+ try {
319
+ parsed = JSON.parse(text);
320
+ } catch {
321
+ }
322
+ if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
323
+ throw new OwneyError(
324
+ "SPONSOR_REQUEST_FAILED",
325
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
326
+ {
327
+ statusCode: res.status,
328
+ responseBody: text.slice(0, 500),
329
+ safeToFallback: true
330
+ }
331
+ );
332
+ }
333
+ return parsed.data.relayer;
334
+ }
335
+ async function postSponsorBatchTransfer(input) {
336
+ let res;
337
+ try {
338
+ res = await fetch(
339
+ `${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
340
+ {
341
+ method: "POST",
342
+ headers: {
343
+ "content-type": "application/json",
344
+ "x-owney-api-key": input.apiKey
345
+ },
346
+ body: JSON.stringify(input.body)
347
+ }
348
+ );
349
+ } catch {
350
+ throw new OwneyError(
351
+ "SPONSOR_REQUEST_FAILED",
352
+ "Deposit status is unknown. Retry the same amount to check it.",
353
+ { safeToFallback: false }
354
+ );
355
+ }
356
+ const parsed = await res.json().catch(() => null);
357
+ if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
358
+ throw new OwneyError(
359
+ "SPONSOR_REQUEST_FAILED",
360
+ "Deposit could not be confirmed. Retry the same amount to check its status.",
361
+ {
362
+ statusCode: res.status,
363
+ safeToFallback: false,
364
+ notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
365
+ }
366
+ );
367
+ }
368
+ return parsed.data;
369
+ }
370
+
371
+ // src/lib/permit2.ts
372
+ import { bytesToHex as bytesToHex2 } from "viem";
373
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
374
+ var MAX_UINT256 = 2n ** 256n - 1n;
375
+ function permit2ApprovalAmount(requiredAmount) {
376
+ if (requiredAmount <= 0n) {
377
+ throw new Error("Permit2 approval requires a positive deposit amount");
378
+ }
379
+ return MAX_UINT256;
380
+ }
381
+ var ERC20_ALLOWANCE_ABI = [
382
+ {
383
+ type: "function",
384
+ name: "allowance",
385
+ stateMutability: "view",
386
+ inputs: [
387
+ { name: "owner", type: "address" },
388
+ { name: "spender", type: "address" }
389
+ ],
390
+ outputs: [{ name: "", type: "uint256" }]
391
+ },
392
+ {
393
+ type: "function",
394
+ name: "approve",
395
+ stateMutability: "nonpayable",
396
+ inputs: [
397
+ { name: "spender", type: "address" },
398
+ { name: "amount", type: "uint256" }
399
+ ],
400
+ outputs: [{ name: "", type: "bool" }]
401
+ },
402
+ {
403
+ type: "function",
404
+ name: "balanceOf",
405
+ stateMutability: "view",
406
+ inputs: [{ name: "account", type: "address" }],
407
+ outputs: [{ name: "", type: "uint256" }]
408
+ }
409
+ ];
410
+ function randomPermit2Nonce() {
411
+ const bytes = new Uint8Array(32);
412
+ globalThis.crypto.getRandomValues(bytes);
413
+ return BigInt(bytesToHex2(bytes));
414
+ }
415
+ async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
416
+ return publicClient.readContract({
417
+ address: token,
418
+ abi: ERC20_ALLOWANCE_ABI,
419
+ functionName: "allowance",
420
+ args: [owner, PERMIT2_ADDRESS],
421
+ ...blockNumber === void 0 ? {} : { blockNumber }
422
+ });
423
+ }
424
+ async function readErc20Balance(publicClient, token, owner) {
425
+ return publicClient.readContract({
426
+ address: token,
427
+ abi: ERC20_ALLOWANCE_ABI,
428
+ functionName: "balanceOf",
429
+ args: [owner]
430
+ });
431
+ }
432
+
433
+ // src/lib/chain-guard.ts
434
+ var CHAIN_NAMES = {
435
+ 1: "Ethereum",
436
+ 8453: "Base",
437
+ 42161: "Arbitrum"
438
+ };
439
+ function chainName(chainId) {
440
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
441
+ }
442
+ async function ensureWalletOnChain(pub, wallet, expected) {
443
+ const actual = await pub.getChainId();
444
+ if (actual === expected) return;
445
+ try {
446
+ await wallet.switchChain({ id: expected });
447
+ } catch (error) {
448
+ throw new OwneyError(
449
+ "CHAIN_MISMATCH",
450
+ `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
451
+ {
452
+ expectedChainId: expected,
453
+ actualChainId: actual,
454
+ cause: error instanceof Error ? error.message : String(error)
455
+ }
456
+ );
457
+ }
458
+ const after = await pub.getChainId();
459
+ if (after !== expected) {
460
+ throw new OwneyError(
461
+ "CHAIN_MISMATCH",
462
+ `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
463
+ { expectedChainId: expected, actualChainId: after }
464
+ );
465
+ }
466
+ }
467
+
468
+ // src/lib/sponsored-deposit.ts
469
+ var AUTH_WINDOW_SECONDS = 15 * 60;
470
+ var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
471
+ function provideDepositVerificationContext(callback, context) {
472
+ callback[verificationSetter]?.(context);
473
+ }
474
+ function makeVerificationAwareDepositCallback(implementation) {
475
+ let nextVerification;
476
+ const callback = async (smartWallet, chainId, amount) => {
477
+ const verification = nextVerification;
478
+ nextVerification = void 0;
479
+ return implementation(smartWallet, chainId, amount, verification);
480
+ };
481
+ Object.defineProperty(callback, verificationSetter, {
482
+ value: (context) => {
483
+ nextVerification = context;
484
+ }
485
+ });
486
+ return callback;
487
+ }
488
+
255
489
  // src/lib/rate-limit.ts
256
490
  function rateLimitDelay(error, now = Date.now()) {
257
491
  const seen = /* @__PURE__ */ new Set();
@@ -576,7 +810,7 @@ var SupportedAssets = [
576
810
  ];
577
811
 
578
812
  // src/agents/zyfai/zyfai.deposit.ts
579
- async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWallet, assertCurrent }) {
813
+ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWallet, assertCurrent, onBeforeSubmission }) {
580
814
  const owner = state.walletAddress;
581
815
  const provider = state.provider;
582
816
  const token = SupportedAssets.find((value) => value.chainId === chain.id && value.symbol === asset);
@@ -600,6 +834,8 @@ async function submitZyfaiDeposit({ sdk, state, chain, amount, asset, smartWalle
600
834
  assertCurrent();
601
835
  if (!Array.isArray(accounts) || typeof accounts[0] !== "string" || accounts[0].toLowerCase() !== owner.toLowerCase() || Number(chainId) !== chain.id)
602
836
  throw new OwneyError("NOT_CONNECTED", "Wallet changed while preparing Zyfai deposit.", void 0, "zyfai");
837
+ await onBeforeSubmission?.();
838
+ assertCurrent();
603
839
  }
604
840
  return provider.request(args);
605
841
  }
@@ -2439,7 +2675,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
2439
2675
  await this.ensureSessionKey(ownerAddress, chainId, redeployed.address);
2440
2676
  return redeployed.address;
2441
2677
  }
2442
- async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
2678
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission) {
2443
2679
  const validChainId = isValidChainId(chainId);
2444
2680
  const owner = state.walletAddress;
2445
2681
  const provider = state.provider;
@@ -2472,7 +2708,18 @@ var ZyfaiAgent = class _ZyfaiAgent {
2472
2708
  amount,
2473
2709
  asset,
2474
2710
  smartWallet,
2475
- assertCurrent
2711
+ assertCurrent,
2712
+ onBeforeSubmission: onBeforeSubmission ? () => onBeforeSubmission({
2713
+ agentId: this.id,
2714
+ chainId,
2715
+ token: asset,
2716
+ walletAddress: owner,
2717
+ agentWallet: smartWallet,
2718
+ intendedAmount: amount,
2719
+ transfer: "pending",
2720
+ registration: "pending",
2721
+ delivery: "not-applicable"
2722
+ }) : void 0
2476
2723
  });
2477
2724
  let evidence = {
2478
2725
  agentId: this.id,
@@ -3083,240 +3330,6 @@ import {
3083
3330
  } from "viem";
3084
3331
  import { base as base4 } from "viem/chains";
3085
3332
 
3086
- // src/lib/chain-guard.ts
3087
- var CHAIN_NAMES = {
3088
- 1: "Ethereum",
3089
- 8453: "Base",
3090
- 42161: "Arbitrum"
3091
- };
3092
- function chainName(chainId) {
3093
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
3094
- }
3095
- async function ensureWalletOnChain(pub, wallet, expected) {
3096
- const actual = await pub.getChainId();
3097
- if (actual === expected) return;
3098
- try {
3099
- await wallet.switchChain({ id: expected });
3100
- } catch (error) {
3101
- throw new OwneyError(
3102
- "CHAIN_MISMATCH",
3103
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
3104
- {
3105
- expectedChainId: expected,
3106
- actualChainId: actual,
3107
- cause: error instanceof Error ? error.message : String(error)
3108
- }
3109
- );
3110
- }
3111
- const after = await pub.getChainId();
3112
- if (after !== expected) {
3113
- throw new OwneyError(
3114
- "CHAIN_MISMATCH",
3115
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
3116
- { expectedChainId: expected, actualChainId: after }
3117
- );
3118
- }
3119
- }
3120
-
3121
- // src/lib/transfer-auth.ts
3122
- import { bytesToHex } from "viem";
3123
-
3124
- // src/lib/sponsor-client.ts
3125
- var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
3126
- async function postPaymasterIntent(input) {
3127
- const base6 = input.baseUrl ?? ROUTING_API_BASE_URL;
3128
- let res;
3129
- try {
3130
- res = await fetch(`${base6}/api/v1/sponsor/paymaster-intent`, {
3131
- method: "POST",
3132
- headers: {
3133
- "content-type": "application/json",
3134
- "x-owney-api-key": input.apiKey,
3135
- Authorization: `Signature ${input.yieldseekerSignature}`
3136
- },
3137
- body: JSON.stringify(input.body)
3138
- });
3139
- } catch (networkError) {
3140
- throw new OwneyError(
3141
- "SPONSOR_REQUEST_FAILED",
3142
- `Paymaster intent network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3143
- { cause: String(networkError), safeToFallback: true }
3144
- );
3145
- }
3146
- const text = await res.text();
3147
- let parsed = null;
3148
- try {
3149
- parsed = JSON.parse(text);
3150
- } catch {
3151
- }
3152
- if (!res.ok || !parsed?.success || typeof parsed.data?.intent !== "string" || typeof parsed.data.expiresAt !== "number") {
3153
- throw new OwneyError(
3154
- "SPONSOR_REQUEST_FAILED",
3155
- `Paymaster intent API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3156
- {
3157
- statusCode: res.status,
3158
- responseBody: text.slice(0, 500),
3159
- safeToFallback: true
3160
- }
3161
- );
3162
- }
3163
- return parsed.data;
3164
- }
3165
- async function getSponsorRelayerAddress(input) {
3166
- const base6 = input.baseUrl ?? ROUTING_API_BASE_URL;
3167
- let res;
3168
- try {
3169
- res = await fetch(
3170
- `${base6}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
3171
- {
3172
- headers: { "x-owney-api-key": input.apiKey }
3173
- }
3174
- );
3175
- } catch (networkError) {
3176
- throw new OwneyError(
3177
- "SPONSOR_REQUEST_FAILED",
3178
- `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
3179
- { cause: String(networkError), safeToFallback: true }
3180
- );
3181
- }
3182
- const text = await res.text();
3183
- let parsed = null;
3184
- try {
3185
- parsed = JSON.parse(text);
3186
- } catch {
3187
- }
3188
- if (!res.ok || !parsed?.success || !parsed.data?.relayer) {
3189
- throw new OwneyError(
3190
- "SPONSOR_REQUEST_FAILED",
3191
- `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
3192
- {
3193
- statusCode: res.status,
3194
- responseBody: text.slice(0, 500),
3195
- safeToFallback: true
3196
- }
3197
- );
3198
- }
3199
- return parsed.data.relayer;
3200
- }
3201
- async function postSponsorBatchTransfer(input) {
3202
- let res;
3203
- try {
3204
- res = await fetch(
3205
- `${input.baseUrl ?? ROUTING_API_BASE_URL}/api/v1/sponsor/permit2-batch`,
3206
- {
3207
- method: "POST",
3208
- headers: {
3209
- "content-type": "application/json",
3210
- "x-owney-api-key": input.apiKey
3211
- },
3212
- body: JSON.stringify(input.body)
3213
- }
3214
- );
3215
- } catch {
3216
- throw new OwneyError(
3217
- "SPONSOR_REQUEST_FAILED",
3218
- "Deposit status is unknown. Retry the same amount to check it.",
3219
- { safeToFallback: false }
3220
- );
3221
- }
3222
- const parsed = await res.json().catch(() => null);
3223
- if (!res.ok || !parsed?.success || !/^0x[0-9a-fA-F]{64}$/.test(parsed.data?.txHash ?? "")) {
3224
- throw new OwneyError(
3225
- "SPONSOR_REQUEST_FAILED",
3226
- "Deposit could not be confirmed. Retry the same amount to check its status.",
3227
- {
3228
- statusCode: res.status,
3229
- safeToFallback: false,
3230
- notSubmitted: parsed?.notSubmitted === true || parsed?.error?.notSubmitted === true || parsed?.error?.details?.notSubmitted === true
3231
- }
3232
- );
3233
- }
3234
- return parsed.data;
3235
- }
3236
-
3237
- // src/lib/permit2.ts
3238
- import { bytesToHex as bytesToHex2 } from "viem";
3239
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
3240
- var MAX_UINT256 = 2n ** 256n - 1n;
3241
- function permit2ApprovalAmount(requiredAmount) {
3242
- if (requiredAmount <= 0n) {
3243
- throw new Error("Permit2 approval requires a positive deposit amount");
3244
- }
3245
- return MAX_UINT256;
3246
- }
3247
- var ERC20_ALLOWANCE_ABI = [
3248
- {
3249
- type: "function",
3250
- name: "allowance",
3251
- stateMutability: "view",
3252
- inputs: [
3253
- { name: "owner", type: "address" },
3254
- { name: "spender", type: "address" }
3255
- ],
3256
- outputs: [{ name: "", type: "uint256" }]
3257
- },
3258
- {
3259
- type: "function",
3260
- name: "approve",
3261
- stateMutability: "nonpayable",
3262
- inputs: [
3263
- { name: "spender", type: "address" },
3264
- { name: "amount", type: "uint256" }
3265
- ],
3266
- outputs: [{ name: "", type: "bool" }]
3267
- },
3268
- {
3269
- type: "function",
3270
- name: "balanceOf",
3271
- stateMutability: "view",
3272
- inputs: [{ name: "account", type: "address" }],
3273
- outputs: [{ name: "", type: "uint256" }]
3274
- }
3275
- ];
3276
- function randomPermit2Nonce() {
3277
- const bytes = new Uint8Array(32);
3278
- globalThis.crypto.getRandomValues(bytes);
3279
- return BigInt(bytesToHex2(bytes));
3280
- }
3281
- async function readPermit2Allowance(publicClient, token, owner, blockNumber) {
3282
- return publicClient.readContract({
3283
- address: token,
3284
- abi: ERC20_ALLOWANCE_ABI,
3285
- functionName: "allowance",
3286
- args: [owner, PERMIT2_ADDRESS],
3287
- ...blockNumber === void 0 ? {} : { blockNumber }
3288
- });
3289
- }
3290
- async function readErc20Balance(publicClient, token, owner) {
3291
- return publicClient.readContract({
3292
- address: token,
3293
- abi: ERC20_ALLOWANCE_ABI,
3294
- functionName: "balanceOf",
3295
- args: [owner]
3296
- });
3297
- }
3298
-
3299
- // src/lib/sponsored-deposit.ts
3300
- var AUTH_WINDOW_SECONDS = 15 * 60;
3301
- var verificationSetter = /* @__PURE__ */ Symbol("owney.depositVerificationSetter");
3302
- function provideDepositVerificationContext(callback, context) {
3303
- callback[verificationSetter]?.(context);
3304
- }
3305
- function makeVerificationAwareDepositCallback(implementation) {
3306
- let nextVerification;
3307
- const callback = async (smartWallet, chainId, amount) => {
3308
- const verification = nextVerification;
3309
- nextVerification = void 0;
3310
- return implementation(smartWallet, chainId, amount, verification);
3311
- };
3312
- Object.defineProperty(callback, verificationSetter, {
3313
- value: (context) => {
3314
- nextVerification = context;
3315
- }
3316
- });
3317
- return callback;
3318
- }
3319
-
3320
3333
  // src/agents/yieldseeker/yieldseeker.auth.ts
3321
3334
  import { SiweMessage, generateNonce } from "siwe";
3322
3335
  import {
@@ -5382,7 +5395,7 @@ var YieldseekerAgent = class {
5382
5395
  this.assertAsset(targetAsset);
5383
5396
  await this.ensureAgent(state, chainId, targetAsset);
5384
5397
  }
5385
- async deposit(state, chainId, amount, asset, depositCallback, onProgress) {
5398
+ async deposit(state, chainId, amount, asset, depositCallback, onProgress, onBeforeSubmission) {
5386
5399
  this.assertChain(chainId);
5387
5400
  this.assertAsset(asset);
5388
5401
  if (BigInt(amount) <= 0n) {
@@ -5438,7 +5451,17 @@ var YieldseekerAgent = class {
5438
5451
  chainId
5439
5452
  }, submitted, (value) => {
5440
5453
  receipt = value;
5441
- });
5454
+ }, onBeforeSubmission ? () => onBeforeSubmission({
5455
+ agentId: this.id,
5456
+ chainId,
5457
+ token: asset,
5458
+ walletAddress: state.walletAddress,
5459
+ agentWallet: context.wallet.walletAddress,
5460
+ intendedAmount: amount,
5461
+ transfer: "pending",
5462
+ registration: "not-applicable",
5463
+ delivery: "not-applicable"
5464
+ }) : void 0);
5442
5465
  } catch (error) {
5443
5466
  if (error instanceof OwneyError && error.details?.ownerTransferNotExecuted === true && error.details.reason === "TRANSACTION_REPLACED_WITH_DIFFERENT_CALL") {
5444
5467
  notifyFunding(onProgress, {
@@ -7034,8 +7057,9 @@ var YieldseekerAgent = class {
7034
7057
  this.id
7035
7058
  );
7036
7059
  }
7037
- async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt) {
7060
+ async submitTransaction(state, chainId, transaction, onSubmitted, onReceipt, onBeforeSubmission) {
7038
7061
  if (this.transactionExecutor) {
7062
+ await onBeforeSubmission?.();
7039
7063
  const hash2 = await this.transactionExecutor(state, chainId, transaction, onReceipt);
7040
7064
  onSubmitted?.(hash2);
7041
7065
  return hash2;
@@ -7045,7 +7069,10 @@ var YieldseekerAgent = class {
7045
7069
  const walletClient = createWalletClient4({
7046
7070
  account,
7047
7071
  chain: base4,
7048
- transport: custom4(state.provider)
7072
+ transport: onBeforeSubmission ? custom4({ request: async (args) => {
7073
+ if (args.method === "eth_sendTransaction") await onBeforeSubmission();
7074
+ return state.provider.request(args);
7075
+ } }, { retryCount: 0 }) : custom4(state.provider)
7049
7076
  });
7050
7077
  const walletChainClient = createPublicClient3({
7051
7078
  chain: base4,
@@ -7897,6 +7924,7 @@ async function execute(i, key2, plan) {
7897
7924
  };
7898
7925
  save(key2, body);
7899
7926
  }
7927
+ await i.onBeforeSubmission?.(keccak256(body.serializedTransaction));
7900
7928
  try {
7901
7929
  i.onSubmitted?.(keccak256(body.serializedTransaction));
7902
7930
  } catch {
@@ -7924,6 +7952,13 @@ async function execute(i, key2, plan) {
7924
7952
  clear(key2, body);
7925
7953
  return hash;
7926
7954
  } catch (error) {
7955
+ if (initial.serializedTransaction && error instanceof OwneyError && error.code === "DEPOSIT_CHECKPOINT_FAILED") {
7956
+ throw new OwneyError(
7957
+ error.code,
7958
+ "Could not save recovery of the existing deposit.",
7959
+ { ...error.details, notSubmitted: false, safeToFallback: false }
7960
+ );
7961
+ }
7927
7962
  if (!body.serializedTransaction && !body.serializedPermitTransaction || rejectedPreparation(error) || error instanceof OwneyError && error.details?.notSubmitted === true)
7928
7963
  clear(key2, body);
7929
7964
  throw error;
@@ -8050,6 +8085,7 @@ function makeSponsoredTokenCallback(deps) {
8050
8085
  wallet,
8051
8086
  onApproved: deps.onApproved,
8052
8087
  onSubmitted: (hash) => deps.onSubmitted?.(hash, transfers),
8088
+ onBeforeSubmission: (hash) => deps.onBeforeSubmission?.(hash, transfers),
8053
8089
  resumeTxHash: deps.resumeTxHash
8054
8090
  });
8055
8091
  };
@@ -8221,6 +8257,7 @@ function makeSponsoredCallsCallback(deps) {
8221
8257
  } catch {
8222
8258
  }
8223
8259
  };
8260
+ await deps.onBeforeSubmission?.(requestedId, transfers);
8224
8261
  progress(requestedId, "pending");
8225
8262
  let sendResult;
8226
8263
  try {
@@ -8518,8 +8555,8 @@ var OwneySDK = class {
8518
8555
  return createPaidRpcClient(VIEM_CHAIN2[chainId], this.rpcUrls);
8519
8556
  }
8520
8557
  /** Builds the default USDC batch callback for the connected wallet. */
8521
- getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
8522
- if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedSponsoredCallback)
8558
+ getDefaultSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission) {
8559
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && this.cachedSponsoredCallback)
8523
8560
  return this.cachedSponsoredCallback;
8524
8561
  const provider = this.requireConnectedProvider();
8525
8562
  const owner = this.state.walletAddress;
@@ -8529,6 +8566,7 @@ var OwneySDK = class {
8529
8566
  ownerAddress: owner,
8530
8567
  onApproved,
8531
8568
  onSubmitted,
8569
+ onBeforeSubmission,
8532
8570
  resumeTxHash,
8533
8571
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
8534
8572
  // Casts work around viem's chain-narrowed Client vs the generic
@@ -8545,13 +8583,13 @@ var OwneySDK = class {
8545
8583
  transport: custom5(provider)
8546
8584
  })
8547
8585
  });
8548
- if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedSponsoredCallback = callback;
8586
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission) this.cachedSponsoredCallback = callback;
8549
8587
  return callback;
8550
8588
  }
8551
8589
  /** Builds the wallet-native sponsored calls callback for compatible paymasters. */
8552
- getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) {
8590
+ getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, onBeforeSubmission) {
8553
8591
  const cached = this.cachedSponsoredCallsCallbacks.get(asset);
8554
- if (!onApproved && !onSubmitted && !onCallsProgress && cached) return cached;
8592
+ if (!onApproved && !onSubmitted && !onCallsProgress && !onBeforeSubmission && cached) return cached;
8555
8593
  const provider = this.requireConnectedProvider();
8556
8594
  const callback = makeSponsoredCallsCallback({
8557
8595
  apiKey: this.apiKey,
@@ -8562,9 +8600,10 @@ var OwneySDK = class {
8562
8600
  onApproved,
8563
8601
  onSubmitted,
8564
8602
  onCallsProgress,
8603
+ onBeforeSubmission,
8565
8604
  tokenAddressByChain: sponsoredTokensFor(asset)
8566
8605
  });
8567
- if (!onApproved && !onSubmitted && !onCallsProgress) this.cachedSponsoredCallsCallbacks.set(asset, callback);
8606
+ if (!onApproved && !onSubmitted && !onCallsProgress && !onBeforeSubmission) this.cachedSponsoredCallsCallbacks.set(asset, callback);
8568
8607
  return callback;
8569
8608
  }
8570
8609
  /**
@@ -8573,8 +8612,8 @@ var OwneySDK = class {
8573
8612
  * Mirrors `getDefaultSponsoredCallback()` but signs a Permit2
8574
8613
  * single-use batch authorization instead of an EIP-3009 authorization.
8575
8614
  */
8576
- getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash) {
8577
- if (!onApproved && !onSubmitted && !resumeTxHash && this.cachedWethSponsoredCallback)
8615
+ getDefaultWethSponsoredCallback(onApproved, onSubmitted, resumeTxHash, onBeforeSubmission) {
8616
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission && this.cachedWethSponsoredCallback)
8578
8617
  return this.cachedWethSponsoredCallback;
8579
8618
  const provider = this.requireConnectedProvider();
8580
8619
  const owner = this.state.walletAddress;
@@ -8584,6 +8623,7 @@ var OwneySDK = class {
8584
8623
  ownerAddress: owner,
8585
8624
  onApproved,
8586
8625
  onSubmitted,
8626
+ onBeforeSubmission,
8587
8627
  resumeTxHash,
8588
8628
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
8589
8629
  // Casts work around viem's chain-narrowed Client vs the generic
@@ -8600,7 +8640,7 @@ var OwneySDK = class {
8600
8640
  transport: custom5(provider)
8601
8641
  })
8602
8642
  });
8603
- if (!onApproved && !onSubmitted && !resumeTxHash) this.cachedWethSponsoredCallback = callback;
8643
+ if (!onApproved && !onSubmitted && !resumeTxHash && !onBeforeSubmission) this.cachedWethSponsoredCallback = callback;
8604
8644
  return callback;
8605
8645
  }
8606
8646
  getAgent(agentId) {
@@ -8916,8 +8956,12 @@ var OwneySDK = class {
8916
8956
  );
8917
8957
  const prepared = /* @__PURE__ */ new Map();
8918
8958
  const paidFallbackAgents = /* @__PURE__ */ new Set();
8919
- const onProgress = options.onProgress ? (evidence) => {
8920
- if (evidence.agentWallet) prepared.set(evidence.agentWallet.toLowerCase(), evidence);
8959
+ const onProgress = options.onProgress || options.onBeforeSubmission ? (evidence) => {
8960
+ if (evidence.agentWallet) {
8961
+ const wallet = evidence.agentWallet.toLowerCase();
8962
+ evidence = { ...prepared.get(wallet), ...evidence };
8963
+ prepared.set(wallet, evidence);
8964
+ }
8921
8965
  notifyFunding(options.onProgress, evidence);
8922
8966
  } : void 0;
8923
8967
  const onSubmitted = onProgress ? (hash, destinations) => {
@@ -8937,9 +8981,45 @@ var OwneySDK = class {
8937
8981
  }
8938
8982
  const state = this.requireState();
8939
8983
  const chainId = this.requireChainId();
8984
+ let checkpointFailure;
8985
+ let checkpointFailedEvidence = [];
8986
+ const checkpoint2 = options.onBeforeSubmission ? async (evidence) => {
8987
+ try {
8988
+ if (this.state !== state || state.chainId !== chainId) throw new Error("Wallet changed before deposit submission.");
8989
+ const submitting = evidence.map((leg) => ({ ...leg, depositSubmission: "started" }));
8990
+ await options.onBeforeSubmission(submitting);
8991
+ if (this.state !== state || state.chainId !== chainId) throw new Error("Wallet changed before deposit submission.");
8992
+ submitting.forEach((leg) => notifyFunding(onProgress, leg));
8993
+ } catch (cause) {
8994
+ checkpointFailedEvidence = evidence;
8995
+ checkpointFailure = new OwneyError(
8996
+ "DEPOSIT_CHECKPOINT_FAILED",
8997
+ "Could not safely save this deposit before submission.",
8998
+ { notSubmitted: !options.resumeTxHash, safeToFallback: false, cause }
8999
+ );
9000
+ throw checkpointFailure;
9001
+ }
9002
+ } : void 0;
9003
+ const checkpointDestinations = checkpoint2 ? async (destinations, identifier) => {
9004
+ const evidence = destinations.map((destination) => {
9005
+ const leg = prepared.get(destination.to.toLowerCase());
9006
+ if (!leg || leg.intendedAmount !== destination.amount)
9007
+ throw new OwneyError("DEPOSIT_CHECKPOINT_FAILED", "Missing prepared deposit destination.", { notSubmitted: true, safeToFallback: false });
9008
+ return { ...leg, ...identifier, transfer: "pending" };
9009
+ });
9010
+ await checkpoint2(evidence);
9011
+ } : void 0;
9012
+ const checkpointHash = checkpointDestinations ? (hash, destinations) => checkpointDestinations(destinations, { txHash: hash }) : void 0;
9013
+ const checkpointCalls = checkpointDestinations ? (id, destinations) => checkpointDestinations(destinations, { callsId: id }) : void 0;
9014
+ const checkpointPaid = checkpoint2 ? (evidence) => checkpoint2([evidence]) : void 0;
9015
+ const customCallback = depositCallback && checkpointDestinations ? makeVerificationAwareDepositCallback(async (to, cid, value, verification) => {
9016
+ if (verification) provideDepositVerificationContext(depositCallback, verification);
9017
+ await checkpointDestinations([{ to, amount: value }], {});
9018
+ return depositCallback(to, cid, value);
9019
+ }) : depositCallback;
8940
9020
  if (options.resumeTxHash && (depositCallback || this.paymasterServiceUrl || !/^0x[\da-f]{64}$/i.test(options.resumeTxHash)))
8941
9021
  throw new OwneyError("DEPOSIT_IN_PROGRESS", "This deposit cannot resume the saved relayer transaction.");
8942
- const effectiveCallback = depositCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash));
9022
+ const effectiveCallback = customCallback ?? (this.paymasterServiceUrl ? this.getDefaultSponsoredCallsCallback(asset, onApproved, onSubmitted, onCallsProgress, checkpointCalls) : asset === "WETH" ? this.getDefaultWethSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash) : this.getDefaultSponsoredCallback(onApproved, onSubmitted, options.resumeTxHash, checkpointHash));
8943
9023
  try {
8944
9024
  if (options.resumeTxHash) {
8945
9025
  const token = SupportedAssets.find((value) => value.chainId === chainId && value.symbol === asset);
@@ -9003,7 +9083,8 @@ var OwneySDK = class {
9003
9083
  depositCallback,
9004
9084
  onProgress,
9005
9085
  () => paidFallbackAgents.add(agent.id),
9006
- !!options.resumeTxHash
9086
+ !!options.resumeTxHash,
9087
+ checkpointPaid
9007
9088
  );
9008
9089
  }
9009
9090
  const totalAmount = BigInt(amount);
@@ -9105,7 +9186,8 @@ var OwneySDK = class {
9105
9186
  depositCallback,
9106
9187
  onProgress,
9107
9188
  () => paidFallbackAgents.add(agent.id),
9108
- !!options.resumeTxHash
9189
+ !!options.resumeTxHash,
9190
+ checkpointPaid
9109
9191
  );
9110
9192
  } catch (error) {
9111
9193
  if (Object.keys(agentResults).length === 0) throw error;
@@ -9125,12 +9207,22 @@ var OwneySDK = class {
9125
9207
  }
9126
9208
  return { agentResults };
9127
9209
  } catch (error) {
9210
+ if (checkpointFailure && !(error instanceof OwneyError && error.details?.notSubmitted === false)) {
9211
+ error = error instanceof OwneyError && error.code === "DEPOSIT_PARTIAL_FAILURE" ? new OwneyError(
9212
+ error.code,
9213
+ "Some deposits completed before another agent could be submitted.",
9214
+ { ...error.details, cause: checkpointFailure },
9215
+ error.agentId
9216
+ ) : checkpointFailure;
9217
+ if (!options.resumeTxHash) for (const evidence of checkpointFailedEvidence)
9218
+ notifyFunding(onProgress, { ...evidence, transfer: "failed" });
9219
+ }
9128
9220
  if (!depositCallback && onProgress && !options.resumeTxHash) {
9129
9221
  const ids = agentId ? [agentId] : this.getEligibleAgents(chainId, asset).map((agent) => agent.id);
9130
9222
  for (const id of ids) {
9131
9223
  const evidence = [...prepared.values()].find((value) => value.agentId === id);
9132
- if (paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
9133
9224
  const rejected = error instanceof OwneyError && error.details?.notSubmitted === true;
9225
+ if (!rejected && paidFallbackAgents.has(id) || evidence?.transfer === "confirmed" || evidence?.transfer === "failed") continue;
9134
9226
  if (!rejected && (evidence?.txHash || evidence?.userOpHash || evidence?.callsId)) continue;
9135
9227
  notifyFunding(onProgress, { ...evidence ?? {
9136
9228
  agentId: id,
@@ -9173,11 +9265,11 @@ var OwneySDK = class {
9173
9265
  * propagates unchanged. Scoped per agent invocation so one agent's failure
9174
9266
  * doesn't force a retry of sibling agents in a multi-agent split.
9175
9267
  */
9176
- async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false) {
9268
+ async depositWithFallback(agent, state, chainId, amount, asset, effectiveCallback, appCallback, onProgress, onPaidFallback, resumeOnly = false, onBeforeSubmission) {
9177
9269
  const attempt = (callback) => withFailureReporting(
9178
9270
  this.apiKey,
9179
9271
  agent.id,
9180
- () => onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
9272
+ () => onBeforeSubmission ? agent.deposit(state, chainId, amount, asset, callback, onProgress, onBeforeSubmission) : onProgress ? agent.deposit(state, chainId, amount, asset, callback, onProgress) : agent.deposit(state, chainId, amount, asset, callback),
9181
9273
  this.routingApiBaseUrl
9182
9274
  );
9183
9275
  let approvalAttempted = false;