@owney/sdk 0.7.26-beta.7 → 0.7.26

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/README.md CHANGED
@@ -134,6 +134,13 @@ Deposit funds into a specific agent, or split equally across all agents if `agen
134
134
  | `options.depositCallback` | `DepositCallback` | Callback that performs the token transfer and returns a tx hash |
135
135
  | `options.agentId` | `AgentId` (optional) | Target agent. Omit to split equally across all agents. |
136
136
 
137
+ Zyfai's native transfer path uses `sendDeposit()`. It returns after the token
138
+ transfer confirms and the deposit is registered, while balance credit may still
139
+ be queued. Balance reads reflect subsequent credit and allocation; the deposit
140
+ call does not wait for `waitForDepositCredit()`.
141
+ A successful deposit result confirms the transfer; it does not guarantee that
142
+ the funds are already credited or earning yield.
143
+
137
144
  Returns: `OwneyDepositResult` (single agent) or `OwneyMultiDepositResult` (all agents)
138
145
 
139
146
  ```typescript
package/dist/index.cjs CHANGED
@@ -1352,7 +1352,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1352
1352
  * chain/account switch fires the provider's `accountsChanged` →
1353
1353
  * `disconnectAccount` (nulls walletClient + authenticatedUserId) while our
1354
1354
  * own `connectAuth` guard still believes this chain is connected and
1355
- * early-returns. Without this, authenticated calls (depositFunds,
1355
+ * early-returns. Without this, authenticated calls (sendDeposit,
1356
1356
  * withdrawFunds) fail with "No account connected".
1357
1357
  */
1358
1358
  async ensureConnected(state, chainId) {
@@ -1930,7 +1930,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1930
1930
  return { txHash, smartWallet, amount };
1931
1931
  }
1932
1932
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1933
- const raw = await this.sdk.depositFunds(
1933
+ const raw = await this.sdk.sendDeposit(
1934
1934
  this.getAddress(),
1935
1935
  validChainId,
1936
1936
  amount,
@@ -2499,9 +2499,24 @@ async function wrapNative(deps, options) {
2499
2499
  message: error instanceof Error ? error.message : String(error)
2500
2500
  });
2501
2501
  }
2502
+ let approvalPending = true;
2503
+ if (options.spender && deps.readAllowance) {
2504
+ try {
2505
+ const allowance = await deps.readAllowance(wrapper, options.spender);
2506
+ approvalPending = allowance < requested;
2507
+ } catch (error) {
2508
+ debugLog(
2509
+ "owney-sdk",
2510
+ "wrap: allowance read failed, assuming an approve",
2511
+ {
2512
+ message: error instanceof Error ? error.message : String(error)
2513
+ }
2514
+ );
2515
+ }
2516
+ }
2502
2517
  const measured = await deps.getMaxFeePerGas();
2503
2518
  const gasPrice = measured > MIN_WALLET_GAS_PRICE ? measured : MIN_WALLET_GAS_PRICE;
2504
- const reserve = (wrapGas + APPROVE_GAS) * gasPrice * GAS_SAFETY;
2519
+ const reserve = (wrapGas + (approvalPending ? APPROVE_GAS : 0n)) * gasPrice * GAS_SAFETY;
2505
2520
  const spendable = balance > reserve ? balance - reserve : 0n;
2506
2521
  const wrapped = requested < spendable ? requested : spendable;
2507
2522
  debugLog("owney-sdk", "wrap: sizing", {
@@ -2509,6 +2524,7 @@ async function wrapNative(deps, options) {
2509
2524
  requested: requested.toString(),
2510
2525
  measuredGasPrice: measured.toString(),
2511
2526
  reservedAtGasPrice: gasPrice.toString(),
2527
+ approvalPending,
2512
2528
  reserve: reserve.toString(),
2513
2529
  wrapped: wrapped.toString()
2514
2530
  });
@@ -3102,7 +3118,7 @@ async function runSameChainFusion(deps, options, walletAddress) {
3102
3118
  const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
3103
3119
  const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
3104
3120
  const permit = await authoriseSpend(deps, options, amount, isNativeSource);
3105
- onStage?.("quoting");
3121
+ onStage?.("building");
3106
3122
  debugLog("owney-sdk", "swap: building same-chain fusion order");
3107
3123
  const built = await deps.api.buildOrder({
3108
3124
  from: {
@@ -3184,7 +3200,7 @@ async function runFusion(deps, options, walletAddress) {
3184
3200
  const amount = await affordableAmount(deps, BigInt(quote.src.amount));
3185
3201
  const permit = await authoriseSpend(deps, options, amount, false);
3186
3202
  const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
3187
- onStage?.("quoting");
3203
+ onStage?.("building");
3188
3204
  debugLog("owney-sdk", "swap: building fusion order", {
3189
3205
  secrets: secretHashes.length
3190
3206
  });
@@ -5012,13 +5028,25 @@ var OwneySDK = class {
5012
5028
  }
5013
5029
  return hash;
5014
5030
  },
5015
- ensureChain: (id) => this.ensureSwapChain(id)
5031
+ ensureChain: (id) => this.ensureSwapChain(id),
5032
+ // Read through the same public client as every other sizing input, so
5033
+ // a wallet sitting on the wrong chain cannot answer for this one.
5034
+ readAllowance: (token, spender) => publicClient.readContract({
5035
+ address: token,
5036
+ abi: ERC20_ALLOWANCE_ABI,
5037
+ functionName: "allowance",
5038
+ args: [walletAddress, spender]
5039
+ })
5016
5040
  },
5017
5041
  {
5018
5042
  chainId,
5019
5043
  wrapper: wrapper.address,
5020
5044
  nativeSymbol: quote.src.symbol,
5021
5045
  requested: BigInt(quote.src.amount),
5046
+ // Lets the reserve skip the approve's gas when one is already in
5047
+ // place. Absent on a quote that names no spender, which keeps the
5048
+ // full reserve (ROUT-242, comment 16234).
5049
+ ...quote.spender ? { spender: quote.spender } : {},
5022
5050
  onSubmitted: () => onStage?.("wrapped")
5023
5051
  }
5024
5052
  );
@@ -5199,6 +5227,64 @@ var OwneySDK = class {
5199
5227
  * the deposit's own check is the authority and refusing to even attempt
5200
5228
  * would strand funds we might have been able to move.
5201
5229
  */
5230
+ /** ERC20 balance of the connected wallet, read through the wallet's own node. */
5231
+ async readTokenBalance(chainId, token) {
5232
+ const state = this.requireState();
5233
+ return readErc20Balance(
5234
+ (0, import_viem9.createPublicClient)({
5235
+ chain: swapChain(chainId),
5236
+ transport: (0, import_viem9.custom)(this.requireConnectedProvider())
5237
+ }),
5238
+ token,
5239
+ state.walletAddress
5240
+ );
5241
+ }
5242
+ /** How much of `token` the wallet has already approved Permit2 to move. */
5243
+ async readTokenPermit2Allowance(chainId, token) {
5244
+ const state = this.requireState();
5245
+ return readPermit2Allowance(
5246
+ (0, import_viem9.createPublicClient)({
5247
+ chain: swapChain(chainId),
5248
+ transport: (0, import_viem9.custom)(this.requireConnectedProvider())
5249
+ }),
5250
+ token,
5251
+ state.walletAddress
5252
+ );
5253
+ }
5254
+ /**
5255
+ * How much of a landed swap to actually deposit.
5256
+ *
5257
+ * The swap reports what ARRIVED, and the deposit used to be handed that
5258
+ * figure directly. It is the entire arrival, so there is no slack: anything
5259
+ * that spends the target asset between the two fails the deposit outright.
5260
+ *
5261
+ * Something does. A first WETH deposit needs a one-time Permit2 approval,
5262
+ * and that approval is a transaction. A wallet holding no native gas token -
5263
+ * the normal state of someone who just swapped everything INTO WETH - pays
5264
+ * for it out of the WETH. The deposit then asked for ~2e12 wei more than was
5265
+ * left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
5266
+ * the funds were safe but unadded (ROUT-242).
5267
+ *
5268
+ * So the approval happens here, before the amount is settled, and the amount
5269
+ * comes from a read taken after it. Two things this deliberately is not:
5270
+ *
5271
+ * - It is not a balance sweep. `received` is a DELTA over the pre-swap
5272
+ * balance, so a wallet already holding WETH reads far above it; depositing
5273
+ * the balance would move funds the user never offered. The arrival is the
5274
+ * ceiling and the balance is only ever allowed to lower it.
5275
+ * - It does not touch USDC, which moves on signed typed data and so spends
5276
+ * nothing of the user's in between, nor a paymaster-configured client,
5277
+ * where the sponsor covers the transfer and Permit2 never applies.
5278
+ */
5279
+ async settleSwapDepositAmount(chainId, token, symbol, received) {
5280
+ if (symbol !== "WETH" || this.paymasterServiceUrl) {
5281
+ return received.toString();
5282
+ }
5283
+ const allowance = await this.readTokenPermit2Allowance(chainId, token);
5284
+ if (allowance < received) await this.approvePermit2();
5285
+ const balance = await this.readTokenBalance(chainId, token);
5286
+ return (balance < received ? balance : received).toString();
5287
+ }
5202
5288
  async walletSeesBalance(chainId, token, amount) {
5203
5289
  const provider = this.requireConnectedProvider();
5204
5290
  const state = this.requireState();
@@ -5427,7 +5513,7 @@ var OwneySDK = class {
5427
5513
  */
5428
5514
  async getWalletBalances(chainIds) {
5429
5515
  const state = this.requireState();
5430
- const chains = chainIds ?? SUPPORTED_CHAIN_IDS;
5516
+ const chains = chainIds ?? SWAP_CHAIN_IDS;
5431
5517
  const failedChainIds = [];
5432
5518
  const results = await Promise.all(
5433
5519
  chains.map(async (chainId) => {
@@ -5533,12 +5619,18 @@ var OwneySDK = class {
5533
5619
  quote.dst.address,
5534
5620
  BigInt(swap.received)
5535
5621
  );
5622
+ const amount = await this.settleSwapDepositAmount(
5623
+ quote.dst.chainId,
5624
+ quote.dst.address,
5625
+ options.to.symbol,
5626
+ BigInt(swap.received)
5627
+ );
5536
5628
  const deposit = await this.deposit({
5537
- amount: swap.received,
5629
+ amount,
5538
5630
  asset: options.to.symbol,
5539
5631
  ...options.agentId ? { agentId: options.agentId } : {}
5540
5632
  });
5541
- return { swap, deposit };
5633
+ return { swap, deposit, deposited: amount };
5542
5634
  } catch (error) {
5543
5635
  const pending = {
5544
5636
  id: `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
package/dist/index.d.cts CHANGED
@@ -220,7 +220,7 @@ type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refun
220
220
  * of the flow and the one place a user would otherwise think nothing is
221
221
  * happening.
222
222
  */
223
- type SwapStage = "withdrawing" | "withdrawn" | "quoting" | "wrapping" | "wrapped" | "approving" | "signing" | "swapping" | "swapped" | "depositing" | "refunding" | "refunded";
223
+ type SwapStage = "withdrawing" | "withdrawn" | "quoting" | "wrapping" | "wrapped" | "approving" | "building" | "signing" | "swapping" | "swapped" | "depositing" | "refunding" | "refunded";
224
224
  /**
225
225
  * Which way the funds travel, which decides what each side may be.
226
226
  *
@@ -1129,6 +1129,36 @@ declare class OwneySDK {
1129
1129
  * the deposit's own check is the authority and refusing to even attempt
1130
1130
  * would strand funds we might have been able to move.
1131
1131
  */
1132
+ /** ERC20 balance of the connected wallet, read through the wallet's own node. */
1133
+ private readTokenBalance;
1134
+ /** How much of `token` the wallet has already approved Permit2 to move. */
1135
+ private readTokenPermit2Allowance;
1136
+ /**
1137
+ * How much of a landed swap to actually deposit.
1138
+ *
1139
+ * The swap reports what ARRIVED, and the deposit used to be handed that
1140
+ * figure directly. It is the entire arrival, so there is no slack: anything
1141
+ * that spends the target asset between the two fails the deposit outright.
1142
+ *
1143
+ * Something does. A first WETH deposit needs a one-time Permit2 approval,
1144
+ * and that approval is a transaction. A wallet holding no native gas token -
1145
+ * the normal state of someone who just swapped everything INTO WETH - pays
1146
+ * for it out of the WETH. The deposit then asked for ~2e12 wei more than was
1147
+ * left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
1148
+ * the funds were safe but unadded (ROUT-242).
1149
+ *
1150
+ * So the approval happens here, before the amount is settled, and the amount
1151
+ * comes from a read taken after it. Two things this deliberately is not:
1152
+ *
1153
+ * - It is not a balance sweep. `received` is a DELTA over the pre-swap
1154
+ * balance, so a wallet already holding WETH reads far above it; depositing
1155
+ * the balance would move funds the user never offered. The arrival is the
1156
+ * ceiling and the balance is only ever allowed to lower it.
1157
+ * - It does not touch USDC, which moves on signed typed data and so spends
1158
+ * nothing of the user's in between, nor a paymaster-configured client,
1159
+ * where the sponsor covers the transfer and Permit2 never applies.
1160
+ */
1161
+ private settleSwapDepositAmount;
1132
1162
  private walletSeesBalance;
1133
1163
  /**
1134
1164
  * Runs a quoted swap. A named seam rather than an inline `executeSwap` call
@@ -1328,6 +1358,12 @@ declare class OwneySDK {
1328
1358
  txHash?: string;
1329
1359
  };
1330
1360
  deposit: OwneyDepositResult | OwneyMultiDepositResult;
1361
+ /**
1362
+ * What was actually deposited, which is not always what the swap
1363
+ * delivered - see `settleSwapDepositAmount`. Callers seeding a headline
1364
+ * want this one; `swap.received` stays what it says it is.
1365
+ */
1366
+ deposited: string;
1331
1367
  }>;
1332
1368
  /**
1333
1369
  * Withdraw from an agent and swap the proceeds into whatever the user wants
package/dist/index.d.ts CHANGED
@@ -220,7 +220,7 @@ type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refun
220
220
  * of the flow and the one place a user would otherwise think nothing is
221
221
  * happening.
222
222
  */
223
- type SwapStage = "withdrawing" | "withdrawn" | "quoting" | "wrapping" | "wrapped" | "approving" | "signing" | "swapping" | "swapped" | "depositing" | "refunding" | "refunded";
223
+ type SwapStage = "withdrawing" | "withdrawn" | "quoting" | "wrapping" | "wrapped" | "approving" | "building" | "signing" | "swapping" | "swapped" | "depositing" | "refunding" | "refunded";
224
224
  /**
225
225
  * Which way the funds travel, which decides what each side may be.
226
226
  *
@@ -1129,6 +1129,36 @@ declare class OwneySDK {
1129
1129
  * the deposit's own check is the authority and refusing to even attempt
1130
1130
  * would strand funds we might have been able to move.
1131
1131
  */
1132
+ /** ERC20 balance of the connected wallet, read through the wallet's own node. */
1133
+ private readTokenBalance;
1134
+ /** How much of `token` the wallet has already approved Permit2 to move. */
1135
+ private readTokenPermit2Allowance;
1136
+ /**
1137
+ * How much of a landed swap to actually deposit.
1138
+ *
1139
+ * The swap reports what ARRIVED, and the deposit used to be handed that
1140
+ * figure directly. It is the entire arrival, so there is no slack: anything
1141
+ * that spends the target asset between the two fails the deposit outright.
1142
+ *
1143
+ * Something does. A first WETH deposit needs a one-time Permit2 approval,
1144
+ * and that approval is a transaction. A wallet holding no native gas token -
1145
+ * the normal state of someone who just swapped everything INTO WETH - pays
1146
+ * for it out of the WETH. The deposit then asked for ~2e12 wei more than was
1147
+ * left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
1148
+ * the funds were safe but unadded (ROUT-242).
1149
+ *
1150
+ * So the approval happens here, before the amount is settled, and the amount
1151
+ * comes from a read taken after it. Two things this deliberately is not:
1152
+ *
1153
+ * - It is not a balance sweep. `received` is a DELTA over the pre-swap
1154
+ * balance, so a wallet already holding WETH reads far above it; depositing
1155
+ * the balance would move funds the user never offered. The arrival is the
1156
+ * ceiling and the balance is only ever allowed to lower it.
1157
+ * - It does not touch USDC, which moves on signed typed data and so spends
1158
+ * nothing of the user's in between, nor a paymaster-configured client,
1159
+ * where the sponsor covers the transfer and Permit2 never applies.
1160
+ */
1161
+ private settleSwapDepositAmount;
1132
1162
  private walletSeesBalance;
1133
1163
  /**
1134
1164
  * Runs a quoted swap. A named seam rather than an inline `executeSwap` call
@@ -1328,6 +1358,12 @@ declare class OwneySDK {
1328
1358
  txHash?: string;
1329
1359
  };
1330
1360
  deposit: OwneyDepositResult | OwneyMultiDepositResult;
1361
+ /**
1362
+ * What was actually deposited, which is not always what the swap
1363
+ * delivered - see `settleSwapDepositAmount`. Callers seeding a headline
1364
+ * want this one; `swap.received` stays what it says it is.
1365
+ */
1366
+ deposited: string;
1331
1367
  }>;
1332
1368
  /**
1333
1369
  * Withdraw from an agent and swap the proceeds into whatever the user wants
package/dist/index.js CHANGED
@@ -1312,7 +1312,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1312
1312
  * chain/account switch fires the provider's `accountsChanged` →
1313
1313
  * `disconnectAccount` (nulls walletClient + authenticatedUserId) while our
1314
1314
  * own `connectAuth` guard still believes this chain is connected and
1315
- * early-returns. Without this, authenticated calls (depositFunds,
1315
+ * early-returns. Without this, authenticated calls (sendDeposit,
1316
1316
  * withdrawFunds) fail with "No account connected".
1317
1317
  */
1318
1318
  async ensureConnected(state, chainId) {
@@ -1890,7 +1890,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1890
1890
  return { txHash, smartWallet, amount };
1891
1891
  }
1892
1892
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
1893
- const raw = await this.sdk.depositFunds(
1893
+ const raw = await this.sdk.sendDeposit(
1894
1894
  this.getAddress(),
1895
1895
  validChainId,
1896
1896
  amount,
@@ -2459,9 +2459,24 @@ async function wrapNative(deps, options) {
2459
2459
  message: error instanceof Error ? error.message : String(error)
2460
2460
  });
2461
2461
  }
2462
+ let approvalPending = true;
2463
+ if (options.spender && deps.readAllowance) {
2464
+ try {
2465
+ const allowance = await deps.readAllowance(wrapper, options.spender);
2466
+ approvalPending = allowance < requested;
2467
+ } catch (error) {
2468
+ debugLog(
2469
+ "owney-sdk",
2470
+ "wrap: allowance read failed, assuming an approve",
2471
+ {
2472
+ message: error instanceof Error ? error.message : String(error)
2473
+ }
2474
+ );
2475
+ }
2476
+ }
2462
2477
  const measured = await deps.getMaxFeePerGas();
2463
2478
  const gasPrice = measured > MIN_WALLET_GAS_PRICE ? measured : MIN_WALLET_GAS_PRICE;
2464
- const reserve = (wrapGas + APPROVE_GAS) * gasPrice * GAS_SAFETY;
2479
+ const reserve = (wrapGas + (approvalPending ? APPROVE_GAS : 0n)) * gasPrice * GAS_SAFETY;
2465
2480
  const spendable = balance > reserve ? balance - reserve : 0n;
2466
2481
  const wrapped = requested < spendable ? requested : spendable;
2467
2482
  debugLog("owney-sdk", "wrap: sizing", {
@@ -2469,6 +2484,7 @@ async function wrapNative(deps, options) {
2469
2484
  requested: requested.toString(),
2470
2485
  measuredGasPrice: measured.toString(),
2471
2486
  reservedAtGasPrice: gasPrice.toString(),
2487
+ approvalPending,
2472
2488
  reserve: reserve.toString(),
2473
2489
  wrapped: wrapped.toString()
2474
2490
  });
@@ -3072,7 +3088,7 @@ async function runSameChainFusion(deps, options, walletAddress) {
3072
3088
  const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
3073
3089
  const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
3074
3090
  const permit = await authoriseSpend(deps, options, amount, isNativeSource);
3075
- onStage?.("quoting");
3091
+ onStage?.("building");
3076
3092
  debugLog("owney-sdk", "swap: building same-chain fusion order");
3077
3093
  const built = await deps.api.buildOrder({
3078
3094
  from: {
@@ -3154,7 +3170,7 @@ async function runFusion(deps, options, walletAddress) {
3154
3170
  const amount = await affordableAmount(deps, BigInt(quote.src.amount));
3155
3171
  const permit = await authoriseSpend(deps, options, amount, false);
3156
3172
  const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
3157
- onStage?.("quoting");
3173
+ onStage?.("building");
3158
3174
  debugLog("owney-sdk", "swap: building fusion order", {
3159
3175
  secrets: secretHashes.length
3160
3176
  });
@@ -4993,13 +5009,25 @@ var OwneySDK = class {
4993
5009
  }
4994
5010
  return hash;
4995
5011
  },
4996
- ensureChain: (id) => this.ensureSwapChain(id)
5012
+ ensureChain: (id) => this.ensureSwapChain(id),
5013
+ // Read through the same public client as every other sizing input, so
5014
+ // a wallet sitting on the wrong chain cannot answer for this one.
5015
+ readAllowance: (token, spender) => publicClient.readContract({
5016
+ address: token,
5017
+ abi: ERC20_ALLOWANCE_ABI,
5018
+ functionName: "allowance",
5019
+ args: [walletAddress, spender]
5020
+ })
4997
5021
  },
4998
5022
  {
4999
5023
  chainId,
5000
5024
  wrapper: wrapper.address,
5001
5025
  nativeSymbol: quote.src.symbol,
5002
5026
  requested: BigInt(quote.src.amount),
5027
+ // Lets the reserve skip the approve's gas when one is already in
5028
+ // place. Absent on a quote that names no spender, which keeps the
5029
+ // full reserve (ROUT-242, comment 16234).
5030
+ ...quote.spender ? { spender: quote.spender } : {},
5003
5031
  onSubmitted: () => onStage?.("wrapped")
5004
5032
  }
5005
5033
  );
@@ -5180,6 +5208,64 @@ var OwneySDK = class {
5180
5208
  * the deposit's own check is the authority and refusing to even attempt
5181
5209
  * would strand funds we might have been able to move.
5182
5210
  */
5211
+ /** ERC20 balance of the connected wallet, read through the wallet's own node. */
5212
+ async readTokenBalance(chainId, token) {
5213
+ const state = this.requireState();
5214
+ return readErc20Balance(
5215
+ createPublicClient2({
5216
+ chain: swapChain(chainId),
5217
+ transport: custom(this.requireConnectedProvider())
5218
+ }),
5219
+ token,
5220
+ state.walletAddress
5221
+ );
5222
+ }
5223
+ /** How much of `token` the wallet has already approved Permit2 to move. */
5224
+ async readTokenPermit2Allowance(chainId, token) {
5225
+ const state = this.requireState();
5226
+ return readPermit2Allowance(
5227
+ createPublicClient2({
5228
+ chain: swapChain(chainId),
5229
+ transport: custom(this.requireConnectedProvider())
5230
+ }),
5231
+ token,
5232
+ state.walletAddress
5233
+ );
5234
+ }
5235
+ /**
5236
+ * How much of a landed swap to actually deposit.
5237
+ *
5238
+ * The swap reports what ARRIVED, and the deposit used to be handed that
5239
+ * figure directly. It is the entire arrival, so there is no slack: anything
5240
+ * that spends the target asset between the two fails the deposit outright.
5241
+ *
5242
+ * Something does. A first WETH deposit needs a one-time Permit2 approval,
5243
+ * and that approval is a transaction. A wallet holding no native gas token -
5244
+ * the normal state of someone who just swapped everything INTO WETH - pays
5245
+ * for it out of the WETH. The deposit then asked for ~2e12 wei more than was
5246
+ * left and refused with DEPOSIT_INSUFFICIENT_BALANCE, under a wrapper saying
5247
+ * the funds were safe but unadded (ROUT-242).
5248
+ *
5249
+ * So the approval happens here, before the amount is settled, and the amount
5250
+ * comes from a read taken after it. Two things this deliberately is not:
5251
+ *
5252
+ * - It is not a balance sweep. `received` is a DELTA over the pre-swap
5253
+ * balance, so a wallet already holding WETH reads far above it; depositing
5254
+ * the balance would move funds the user never offered. The arrival is the
5255
+ * ceiling and the balance is only ever allowed to lower it.
5256
+ * - It does not touch USDC, which moves on signed typed data and so spends
5257
+ * nothing of the user's in between, nor a paymaster-configured client,
5258
+ * where the sponsor covers the transfer and Permit2 never applies.
5259
+ */
5260
+ async settleSwapDepositAmount(chainId, token, symbol, received) {
5261
+ if (symbol !== "WETH" || this.paymasterServiceUrl) {
5262
+ return received.toString();
5263
+ }
5264
+ const allowance = await this.readTokenPermit2Allowance(chainId, token);
5265
+ if (allowance < received) await this.approvePermit2();
5266
+ const balance = await this.readTokenBalance(chainId, token);
5267
+ return (balance < received ? balance : received).toString();
5268
+ }
5183
5269
  async walletSeesBalance(chainId, token, amount) {
5184
5270
  const provider = this.requireConnectedProvider();
5185
5271
  const state = this.requireState();
@@ -5408,7 +5494,7 @@ var OwneySDK = class {
5408
5494
  */
5409
5495
  async getWalletBalances(chainIds) {
5410
5496
  const state = this.requireState();
5411
- const chains = chainIds ?? SUPPORTED_CHAIN_IDS;
5497
+ const chains = chainIds ?? SWAP_CHAIN_IDS;
5412
5498
  const failedChainIds = [];
5413
5499
  const results = await Promise.all(
5414
5500
  chains.map(async (chainId) => {
@@ -5514,12 +5600,18 @@ var OwneySDK = class {
5514
5600
  quote.dst.address,
5515
5601
  BigInt(swap.received)
5516
5602
  );
5603
+ const amount = await this.settleSwapDepositAmount(
5604
+ quote.dst.chainId,
5605
+ quote.dst.address,
5606
+ options.to.symbol,
5607
+ BigInt(swap.received)
5608
+ );
5517
5609
  const deposit = await this.deposit({
5518
- amount: swap.received,
5610
+ amount,
5519
5611
  asset: options.to.symbol,
5520
5612
  ...options.agentId ? { agentId: options.agentId } : {}
5521
5613
  });
5522
- return { swap, deposit };
5614
+ return { swap, deposit, deposited: amount };
5523
5615
  } catch (error) {
5524
5616
  const pending = {
5525
5617
  id: `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.26-beta.7",
3
+ "version": "0.7.26",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
- "@zyfai/sdk": "0.2.55",
26
+ "@zyfai/sdk": "0.3.1",
27
27
  "siwe": "^3.0.0",
28
28
  "viem": "^2.48.1"
29
29
  },