@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 +7 -0
- package/dist/index.cjs +101 -9
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +101 -9
- package/package.json +2 -2
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 (
|
|
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.
|
|
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?.("
|
|
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?.("
|
|
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 ??
|
|
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
|
|
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 (
|
|
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.
|
|
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?.("
|
|
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?.("
|
|
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 ??
|
|
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
|
|
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
|
|
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.
|
|
26
|
+
"@zyfai/sdk": "0.3.1",
|
|
27
27
|
"siwe": "^3.0.0",
|
|
28
28
|
"viem": "^2.48.1"
|
|
29
29
|
},
|