@augustdigital/sdk 8.16.1 → 8.19.0
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/lib/adapters/evm/index.js +4 -2
- package/lib/adapters/stellar/soroban.d.ts +8 -3
- package/lib/adapters/stellar/soroban.js +9 -4
- package/lib/core/analytics/constants.d.ts +1 -1
- package/lib/core/analytics/constants.js +1 -1
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/attribution.d.ts +111 -0
- package/lib/core/attribution.js +142 -0
- package/lib/core/base.class.d.ts +17 -1
- package/lib/core/base.class.js +6 -1
- package/lib/core/fetcher.js +10 -1
- package/lib/core/helpers/chain-error.d.ts +140 -0
- package/lib/core/helpers/chain-error.js +412 -0
- package/lib/core/helpers/signer.d.ts +21 -0
- package/lib/core/helpers/signer.js +52 -0
- package/lib/core/helpers/web3.d.ts +152 -2
- package/lib/core/helpers/web3.js +307 -45
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.js +1 -0
- package/lib/evm/methods/crossChainVault.js +4 -0
- package/lib/modules/vaults/getters.js +6 -2
- package/lib/modules/vaults/utils.js +4 -0
- package/lib/modules/vaults/write.actions.d.ts +41 -1
- package/lib/modules/vaults/write.actions.js +302 -86
- package/lib/sdk.d.ts +461 -3
- package/lib/services/subgraph/vaults.js +85 -14
- package/lib/types/vaults.d.ts +12 -0
- package/lib/types/webserver.d.ts +11 -0
- package/package.json +1 -1
|
@@ -97,25 +97,233 @@ function isNonceParsing(error) {
|
|
|
97
97
|
return ((code === 'INVALID_ARGUMENT' || code === 'BAD_DATA') &&
|
|
98
98
|
msg.includes('nonce'));
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Own-properties stamped onto an error describing a transaction that already
|
|
102
|
+
* reached the network. Prefixed to avoid colliding with the `hash` /
|
|
103
|
+
* `transaction` / `receipt` fields ethers puts on its own errors.
|
|
104
|
+
*
|
|
105
|
+
* These are an internal wire format between the tx-wait helpers and the write
|
|
106
|
+
* paths' catch blocks — consumers read the *sanitized* form
|
|
107
|
+
* (`txHash` / `broadcast` / `confirmationUnknown`) off `AugustSDKError.context`,
|
|
108
|
+
* never these.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
const BROADCAST_HASH_KEY = 'augustBroadcastTxHash';
|
|
100
112
|
/** @internal */
|
|
113
|
+
const BROADCAST_UNKNOWN_KEY = 'augustConfirmationUnknown';
|
|
114
|
+
/**
|
|
115
|
+
* Stamp "this transaction was broadcast" onto an error, in place.
|
|
116
|
+
*
|
|
117
|
+
* Mutating rather than wrapping is deliberate: callers up the stack compare
|
|
118
|
+
* error identity (and ethers' own typed fields matter for downstream
|
|
119
|
+
* classification), so replacing the error object would lose information.
|
|
120
|
+
*
|
|
121
|
+
* Annotating is strictly best-effort. A frozen or sealed error, or one with a
|
|
122
|
+
* getter-only property of the same name, must never turn a *recoverable
|
|
123
|
+
* transport failure* into a `TypeError` thrown from inside the error handler —
|
|
124
|
+
* that would be a strictly worse outcome than losing the hash. Each write is
|
|
125
|
+
* therefore attempted independently and failures are swallowed; the caller then
|
|
126
|
+
* simply reports the transaction as un-annotated (no `txHash` in the error
|
|
127
|
+
* context), which is the same conservative state as "never broadcast".
|
|
128
|
+
*
|
|
129
|
+
* @param error - The error about to be thrown. Non-objects pass through
|
|
130
|
+
* untouched — there is nowhere to put the marker.
|
|
131
|
+
* @param txHash - Hash of the transaction that reached the network.
|
|
132
|
+
* @param confirmationUnknown - `true` when the transaction is on the wire but
|
|
133
|
+
* its outcome could not be determined (transport retries exhausted, or the
|
|
134
|
+
* wait timed out). `false` when the chain gave a definitive answer, e.g. a
|
|
135
|
+
* receipt with `status === 0`.
|
|
136
|
+
* @returns The same error instance (identity preserved), for
|
|
137
|
+
* `throw markBroadcast(...)`.
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
function markBroadcast(error, txHash, confirmationUnknown) {
|
|
141
|
+
if (!error || typeof error !== 'object')
|
|
142
|
+
return error;
|
|
143
|
+
// `defineProperty` with configurable/writable keeps the marker invisible to
|
|
144
|
+
// JSON.stringify and to spreads of the error, so it can't leak into a
|
|
145
|
+
// serialized crash report as a mystery field.
|
|
146
|
+
const stamp = (key, value) => {
|
|
147
|
+
try {
|
|
148
|
+
Object.defineProperty(error, key, {
|
|
149
|
+
value,
|
|
150
|
+
enumerable: false,
|
|
151
|
+
writable: true,
|
|
152
|
+
configurable: true,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Frozen/sealed error, or a non-configurable same-named property. The
|
|
157
|
+
// hash is a nice-to-have; never let annotation failure mask the real
|
|
158
|
+
// transport error we are in the middle of reporting.
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
stamp(BROADCAST_HASH_KEY, txHash);
|
|
162
|
+
stamp(BROADCAST_UNKNOWN_KEY, confirmationUnknown);
|
|
163
|
+
return error;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Read back the markers {@link markBroadcast} stamped, if any.
|
|
167
|
+
*
|
|
168
|
+
* @param error - The caught value, of unknown type.
|
|
169
|
+
* @returns The recovered hash and confirmation state; both `undefined` when the
|
|
170
|
+
* error never passed through a tx-wait helper.
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
function readBroadcastMarkers(error) {
|
|
174
|
+
if (!error || typeof error !== 'object')
|
|
175
|
+
return {};
|
|
176
|
+
const e = error;
|
|
177
|
+
const txHash = e[BROADCAST_HASH_KEY];
|
|
178
|
+
const confirmationUnknown = e[BROADCAST_UNKNOWN_KEY];
|
|
179
|
+
return {
|
|
180
|
+
txHash: typeof txHash === 'string' ? txHash : undefined,
|
|
181
|
+
confirmationUnknown: typeof confirmationUnknown === 'boolean'
|
|
182
|
+
? confirmationUnknown
|
|
183
|
+
: undefined,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build the broadcast half of a failed write's structured error context, for a
|
|
188
|
+
* path that holds its main transaction in a local variable.
|
|
189
|
+
*
|
|
190
|
+
* The distinction this encodes, and which consumers branch on:
|
|
191
|
+
*
|
|
192
|
+
* - **no `txHash` key at all** — the write never reached the network. Nothing
|
|
193
|
+
* is pending; it is safe to tell the user it failed and safe to retry.
|
|
194
|
+
* - **`txHash` + `broadcast: true` + `confirmationUnknown: true`** — the
|
|
195
|
+
* transaction *is* on-chain (or in the mempool) and only the confirmation was
|
|
196
|
+
* lost. Render a pending state against `txHash`; **do not** prompt a retry,
|
|
197
|
+
* that is exactly how users double-spend a redeem.
|
|
198
|
+
* - **`txHash` + `broadcast: true` + `confirmationUnknown: false`** — the
|
|
199
|
+
* transaction reached the chain and definitively failed (reverted). A real
|
|
200
|
+
* error, with a hash the user can inspect on an explorer.
|
|
201
|
+
*
|
|
202
|
+
* `tx` is the gate on purpose. Several write paths broadcast an inner approval
|
|
203
|
+
* before the main call; if that approval's wait fails, the caught error carries
|
|
204
|
+
* *its* marker. Falling back to the error's marker here would make
|
|
205
|
+
* "Request redeem failed" report the approve hash and claim the redeem is
|
|
206
|
+
* pending when it was never sent.
|
|
207
|
+
*
|
|
208
|
+
* @param tx - The path's main transaction, or `undefined`/`null` when it was
|
|
209
|
+
* never sent.
|
|
210
|
+
* @param error - The caught value, used only for the confirmation state.
|
|
211
|
+
* @returns A context fragment to spread into the thrown error's `context`.
|
|
212
|
+
* Empty when nothing was broadcast.
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
function localTxBroadcastContext(tx, error) {
|
|
216
|
+
const txHash = tx?.hash;
|
|
217
|
+
if (!txHash)
|
|
218
|
+
return {};
|
|
219
|
+
const markers = readBroadcastMarkers(error);
|
|
220
|
+
return {
|
|
221
|
+
txHash,
|
|
222
|
+
broadcast: true,
|
|
223
|
+
// Only trust the marker when it describes *this* transaction.
|
|
224
|
+
confirmationUnknown: markers.txHash === txHash && markers.confirmationUnknown === true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Same contract as {@link localTxBroadcastContext}, for write paths whose only
|
|
229
|
+
* transaction is sent through {@link safeSendTx} — there the hash never comes
|
|
230
|
+
* back to the caller on the failure path, so the error's own marker is the only
|
|
231
|
+
* source. Safe precisely because those paths send exactly one transaction, so
|
|
232
|
+
* the marker cannot belong to a different one.
|
|
233
|
+
*
|
|
234
|
+
* @param error - The caught value, of unknown type.
|
|
235
|
+
* @returns A context fragment to spread into the thrown error's `context`.
|
|
236
|
+
* Empty when nothing was broadcast.
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
239
|
+
function errorTxBroadcastContext(error) {
|
|
240
|
+
const { txHash, confirmationUnknown } = readBroadcastMarkers(error);
|
|
241
|
+
if (!txHash)
|
|
242
|
+
return {};
|
|
243
|
+
return {
|
|
244
|
+
txHash,
|
|
245
|
+
broadcast: true,
|
|
246
|
+
confirmationUnknown: confirmationUnknown === true,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Await a broadcast transaction's receipt, surviving both malformed-nonce RPCs
|
|
251
|
+
* and transient transport faults while still reporting genuine on-chain
|
|
252
|
+
* failures.
|
|
253
|
+
*
|
|
254
|
+
* `tx.wait()` is tried first because it preserves ethers' replacement-tx
|
|
255
|
+
* detection and revert checks. Two recoverable failure modes are handled:
|
|
256
|
+
*
|
|
257
|
+
* 1. **Malformed nonce** ({@link isNonceParsing}) — Monad-style RPCs return
|
|
258
|
+
* `nonce: "undefined"` on the pending tx and ethers throws while parsing.
|
|
259
|
+
* One direct `provider.waitForTransaction()` recovers it (unchanged
|
|
260
|
+
* behaviour).
|
|
261
|
+
* 2. **Transient transport fault** ({@link isRetryableRpcError}) — the provider
|
|
262
|
+
* hiccups while polling `eth_getTransactionReceipt` and ethers surfaces
|
|
263
|
+
* `could not coalesce error (… "code": -32603 …)`. This used to propagate
|
|
264
|
+
* and make every write path report a **successfully mined** transaction as
|
|
265
|
+
* failed, driving users to retry into `ERC20InsufficientBalance`. The hash
|
|
266
|
+
* is already on the wire and receipt polling is idempotent, so we re-poll
|
|
267
|
+
* with bounded exponential backoff instead of rethrowing.
|
|
268
|
+
*
|
|
269
|
+
* Everything else — notably a real revert — is rethrown untouched.
|
|
270
|
+
*
|
|
271
|
+
* Every error this function throws is stamped by {@link markBroadcast}: by the
|
|
272
|
+
* time `tx.wait()` can fail, the transaction is already on the wire, so the
|
|
273
|
+
* caller must never report it as "never sent". See
|
|
274
|
+
* {@link localTxBroadcastContext} for how that reaches
|
|
275
|
+
* `AugustSDKError.context`.
|
|
276
|
+
*
|
|
277
|
+
* @param tx - The broadcast transaction response to await.
|
|
278
|
+
* @returns The mined receipt (never `null` on the fallback paths; `tx.wait()`
|
|
279
|
+
* itself may return `null` when the caller configured 0 confirmations).
|
|
280
|
+
* @throws {Error} `Transaction <hash> reverted on-chain` when the receipt
|
|
281
|
+
* reports `status === 0`. Marked `confirmationUnknown: false` — the chain
|
|
282
|
+
* answered.
|
|
283
|
+
* @throws {Error} `Transaction <hash> was not confirmed within <n>s` when the
|
|
284
|
+
* overall wait times out with no receipt. Marked
|
|
285
|
+
* `confirmationUnknown: true`.
|
|
286
|
+
* @throws The underlying transport error once receipt-poll retries are
|
|
287
|
+
* exhausted, marked `confirmationUnknown: true`.
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
101
290
|
async function safeWaitForTx(tx) {
|
|
102
291
|
try {
|
|
103
292
|
return await tx.wait();
|
|
104
293
|
}
|
|
105
294
|
catch (error) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
295
|
+
const nonceParse = isNonceParsing(error);
|
|
296
|
+
// Not a shape we know how to recover from — surface it unchanged, but the
|
|
297
|
+
// tx is still on-chain (ethers reports a revert here), so keep the hash.
|
|
298
|
+
if (!nonceParse && !(0, core_1.isRetryableRpcError)(error)) {
|
|
299
|
+
throw markBroadcast(error, tx.hash, false);
|
|
300
|
+
}
|
|
301
|
+
const msg = error instanceof Error ? error.message : '';
|
|
302
|
+
core_1.Logger.log.warn(nonceParse
|
|
303
|
+
? 'safeWaitForTx:fallback'
|
|
304
|
+
: 'safeWaitForTx:transport-fallback', msg, { hash: tx.hash });
|
|
305
|
+
// The nonce path keeps its original single-shot behaviour: the RPC is
|
|
306
|
+
// responsive, it just mis-serialized the pending tx. The transport path
|
|
307
|
+
// re-polls, because the provider itself is the thing that failed.
|
|
308
|
+
let receipt;
|
|
309
|
+
try {
|
|
310
|
+
receipt = nonceParse
|
|
311
|
+
? await tx.provider.waitForTransaction(tx.hash, 1, SAFE_WAIT_TIMEOUT_MS)
|
|
312
|
+
: await (0, core_1.retryOnTransientRpc)('safeWaitForTx:transport-retry', () => tx.provider.waitForTransaction(tx.hash, 1, SAFE_WAIT_TIMEOUT_MS), { hash: tx.hash });
|
|
313
|
+
}
|
|
314
|
+
catch (pollError) {
|
|
315
|
+
// Retries exhausted. The transaction is on the wire and very likely
|
|
316
|
+
// mined; we simply could not read its receipt.
|
|
317
|
+
throw markBroadcast(pollError, tx.hash, true);
|
|
318
|
+
}
|
|
319
|
+
if (!receipt) {
|
|
320
|
+
throw markBroadcast(new Error(`Transaction ${tx.hash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), tx.hash, true);
|
|
321
|
+
}
|
|
322
|
+
if (receipt.status === 0) {
|
|
323
|
+
// Definitive: the chain executed it and it failed. Not pending.
|
|
324
|
+
throw markBroadcast(new Error(`Transaction ${tx.hash} reverted on-chain`), tx.hash, false);
|
|
117
325
|
}
|
|
118
|
-
|
|
326
|
+
return receipt;
|
|
119
327
|
}
|
|
120
328
|
}
|
|
121
329
|
/**
|
|
@@ -148,10 +356,10 @@ async function safeSendTx(contractCall, provider, shouldWait) {
|
|
|
148
356
|
const signerProvider = 'provider' in provider ? provider.provider : provider;
|
|
149
357
|
const receipt = await signerProvider.waitForTransaction(txHash, 1, SAFE_WAIT_TIMEOUT_MS);
|
|
150
358
|
if (!receipt) {
|
|
151
|
-
throw new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`);
|
|
359
|
+
throw markBroadcast(new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), txHash, true);
|
|
152
360
|
}
|
|
153
361
|
if (receipt.status === 0) {
|
|
154
|
-
throw new Error(`Transaction ${txHash} reverted on-chain`);
|
|
362
|
+
throw markBroadcast(new Error(`Transaction ${txHash} reverted on-chain`), txHash, false);
|
|
155
363
|
}
|
|
156
364
|
}
|
|
157
365
|
return { tx: null, hash: txHash };
|
|
@@ -177,10 +385,10 @@ async function tryRecoverTxHash(error, signer, wait) {
|
|
|
177
385
|
const provider = 'provider' in signer ? signer.provider : signer;
|
|
178
386
|
const receipt = await provider.waitForTransaction(txHash, 1, SAFE_WAIT_TIMEOUT_MS);
|
|
179
387
|
if (!receipt) {
|
|
180
|
-
throw new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`);
|
|
388
|
+
throw markBroadcast(new Error(`Transaction ${txHash} was not confirmed within ${SAFE_WAIT_TIMEOUT_MS / 1000}s`), txHash, true);
|
|
181
389
|
}
|
|
182
390
|
if (receipt.status === 0) {
|
|
183
|
-
throw new Error(`Transaction ${txHash} reverted on-chain`);
|
|
391
|
+
throw markBroadcast(new Error(`Transaction ${txHash} reverted on-chain`), txHash, false);
|
|
184
392
|
}
|
|
185
393
|
}
|
|
186
394
|
return txHash;
|
|
@@ -252,23 +460,20 @@ async function approveCore(signer, options) {
|
|
|
252
460
|
if (isMultiAssetVault) {
|
|
253
461
|
const [asset, receiptTokenAddr] = await Promise.all([
|
|
254
462
|
poolContract.asset(),
|
|
255
|
-
|
|
463
|
+
// Retried on transport blips only; a misroute still throws. See
|
|
464
|
+
// getReceiptTokenAddressOrThrow.
|
|
465
|
+
(0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultApprove:receiptToken'),
|
|
256
466
|
]);
|
|
257
467
|
underlyingAsset = asset;
|
|
258
|
-
|
|
259
|
-
address: receiptTokenAddr,
|
|
260
|
-
provider: signer,
|
|
261
|
-
abi: abis_1.ABI_ERC20,
|
|
262
|
-
});
|
|
263
|
-
vaultDecimals = Number(await receiptContract.decimals());
|
|
468
|
+
vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultApprove:receiptDecimals');
|
|
264
469
|
}
|
|
265
470
|
else {
|
|
266
471
|
const [asset, rawDecimals] = await Promise.all([
|
|
267
472
|
poolContract.asset(),
|
|
268
|
-
|
|
473
|
+
(0, core_1.getDecimalsOrThrow)(signer, target, 'vaultApprove:poolDecimals'),
|
|
269
474
|
]);
|
|
270
475
|
underlyingAsset = asset;
|
|
271
|
-
vaultDecimals =
|
|
476
|
+
vaultDecimals = rawDecimals;
|
|
272
477
|
}
|
|
273
478
|
const actualDepositAsset = (depositAsset || underlyingAsset);
|
|
274
479
|
const isNativeToken = actualDepositAsset === ethers_1.ZeroAddress ||
|
|
@@ -289,14 +494,7 @@ async function approveCore(signer, options) {
|
|
|
289
494
|
isNativeToken: false,
|
|
290
495
|
isMultiAssetVault,
|
|
291
496
|
vaultDecimals,
|
|
292
|
-
readErc20Decimals: async (addr) =>
|
|
293
|
-
const erc20 = (0, core_1.createContract)({
|
|
294
|
-
address: addr,
|
|
295
|
-
provider: signer,
|
|
296
|
-
abi: abis_1.ABI_ERC20,
|
|
297
|
-
});
|
|
298
|
-
return Number(await erc20.decimals());
|
|
299
|
-
},
|
|
497
|
+
readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'vaultApprove:depositTokenDecimals'),
|
|
300
498
|
});
|
|
301
499
|
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
|
|
302
500
|
const needed = BigInt(normalizedAmt.raw);
|
|
@@ -324,18 +522,23 @@ async function approveCore(signer, options) {
|
|
|
324
522
|
catch (e) {
|
|
325
523
|
if (e instanceof core_1.AugustSDKError)
|
|
326
524
|
throw e;
|
|
525
|
+
// Only one tx is sent on this path, so any broadcast marker on the error
|
|
526
|
+
// necessarily describes the approval itself.
|
|
527
|
+
const broadcast = errorTxBroadcastContext(e);
|
|
327
528
|
// A user declining the approval in their wallet is product behaviour, not
|
|
328
529
|
// an SDK fault — demote it to a breadcrumb so it doesn't bill as an issue.
|
|
329
530
|
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
330
531
|
(0, core_1.logChainError)('vaultApprove', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
331
532
|
target,
|
|
332
533
|
amount,
|
|
534
|
+
...broadcast,
|
|
333
535
|
});
|
|
334
536
|
throwIfInsufficientFunds(insufficientFunds, 'Approval', e, {
|
|
335
537
|
target,
|
|
336
538
|
amount,
|
|
539
|
+
...broadcast,
|
|
337
540
|
});
|
|
338
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Approval failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount } });
|
|
541
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Approval failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, ...broadcast } });
|
|
339
542
|
}
|
|
340
543
|
}
|
|
341
544
|
/**
|
|
@@ -457,6 +660,10 @@ async function vaultDeposit(signer, options) {
|
|
|
457
660
|
throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultDeposit: amount is required');
|
|
458
661
|
}
|
|
459
662
|
validateAmountPrecision(amount);
|
|
663
|
+
// Hoisted out of the try so the catch can tell "the deposit was broadcast and
|
|
664
|
+
// we lost the confirmation" from "the deposit never left". Any inner approval
|
|
665
|
+
// tx is deliberately NOT tracked here — see localTxBroadcastContext.
|
|
666
|
+
let depositTx;
|
|
460
667
|
try {
|
|
461
668
|
const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
|
|
462
669
|
const vaultVersion = (0, core_1.getVaultVersionV2)(tokenizedVault);
|
|
@@ -474,15 +681,12 @@ async function vaultDeposit(signer, options) {
|
|
|
474
681
|
});
|
|
475
682
|
const [asset, receiptTokenAddr] = await Promise.all([
|
|
476
683
|
poolContract.asset(),
|
|
477
|
-
|
|
684
|
+
// Retried on transport blips only; a misroute still throws. See
|
|
685
|
+
// getReceiptTokenAddressOrThrow.
|
|
686
|
+
(0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultDeposit:receiptToken'),
|
|
478
687
|
]);
|
|
479
688
|
underlyingAsset = asset;
|
|
480
|
-
|
|
481
|
-
address: receiptTokenAddr,
|
|
482
|
-
provider: signer,
|
|
483
|
-
abi: abis_1.ABI_ERC20,
|
|
484
|
-
});
|
|
485
|
-
vaultDecimals = Number(await receiptContract.decimals());
|
|
689
|
+
vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultDeposit:receiptDecimals');
|
|
486
690
|
}
|
|
487
691
|
else {
|
|
488
692
|
poolContract = (0, core_1.createContract)({
|
|
@@ -492,9 +696,9 @@ async function vaultDeposit(signer, options) {
|
|
|
492
696
|
});
|
|
493
697
|
const [fetchedUnderlyingAsset, rawDecimals] = await Promise.all([
|
|
494
698
|
poolContract.asset(),
|
|
495
|
-
|
|
699
|
+
(0, core_1.getDecimalsOrThrow)(signer, target, 'vaultDeposit:poolDecimals'),
|
|
496
700
|
]);
|
|
497
|
-
vaultDecimals =
|
|
701
|
+
vaultDecimals = rawDecimals;
|
|
498
702
|
underlyingAsset = fetchedUnderlyingAsset;
|
|
499
703
|
}
|
|
500
704
|
const actualDepositAsset = depositAsset || underlyingAsset;
|
|
@@ -515,14 +719,7 @@ async function vaultDeposit(signer, options) {
|
|
|
515
719
|
isNativeToken,
|
|
516
720
|
isMultiAssetVault,
|
|
517
721
|
vaultDecimals,
|
|
518
|
-
readErc20Decimals: async (addr) =>
|
|
519
|
-
const erc20 = (0, core_1.createContract)({
|
|
520
|
-
address: addr,
|
|
521
|
-
provider: signer,
|
|
522
|
-
abi: abis_1.ABI_ERC20,
|
|
523
|
-
});
|
|
524
|
-
return Number(await erc20.decimals());
|
|
525
|
-
},
|
|
722
|
+
readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'vaultDeposit:depositTokenDecimals'),
|
|
526
723
|
});
|
|
527
724
|
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
|
|
528
725
|
if (!isNativeToken) {
|
|
@@ -546,7 +743,6 @@ async function vaultDeposit(signer, options) {
|
|
|
546
743
|
core_1.Logger.log.info('approve:tx_hash', approveHash);
|
|
547
744
|
}
|
|
548
745
|
}
|
|
549
|
-
let depositTx;
|
|
550
746
|
// Route to appropriate deposit method
|
|
551
747
|
if (isMultiAssetVault) {
|
|
552
748
|
if (options?.isDepositWithPermit) {
|
|
@@ -638,19 +834,29 @@ async function vaultDeposit(signer, options) {
|
|
|
638
834
|
}
|
|
639
835
|
if (e instanceof core_1.AugustSDKError)
|
|
640
836
|
throw e;
|
|
837
|
+
// depositTx is only ever unset here when the depositWithPermit sub-path
|
|
838
|
+
// threw — that branch sends its single main tx through safeSendTx and
|
|
839
|
+
// never assigns depositTx, so the error's own markBroadcast marker is the
|
|
840
|
+
// only place the broadcast hash survives. Every other sub-path assigns
|
|
841
|
+
// depositTx before this catch can run.
|
|
842
|
+
const broadcast = depositTx
|
|
843
|
+
? localTxBroadcastContext(depositTx, e)
|
|
844
|
+
: errorTxBroadcastContext(e);
|
|
641
845
|
// User-cancelled deposits are normal; only genuine failures stay at error.
|
|
642
846
|
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
643
847
|
(0, core_1.logChainError)('deposit', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
644
848
|
target,
|
|
645
849
|
amount,
|
|
646
850
|
depositAsset,
|
|
851
|
+
...broadcast,
|
|
647
852
|
});
|
|
648
853
|
throwIfInsufficientFunds(insufficientFunds, 'Deposit', e, {
|
|
649
854
|
target,
|
|
650
855
|
amount,
|
|
651
856
|
depositAsset,
|
|
857
|
+
...broadcast,
|
|
652
858
|
});
|
|
653
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Deposit failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, depositAsset } });
|
|
859
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Deposit failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, depositAsset, ...broadcast } });
|
|
654
860
|
}
|
|
655
861
|
}
|
|
656
862
|
/**
|
|
@@ -702,6 +908,11 @@ async function vaultRequestRedeem(signer, options) {
|
|
|
702
908
|
throw new core_1.AugustValidationError('INVALID_INPUT', 'vaultRequestRedeem: amount is required');
|
|
703
909
|
}
|
|
704
910
|
validateAmountPrecision(amount);
|
|
911
|
+
// Hoisted out of the try so the catch can tell "the redeem was broadcast and
|
|
912
|
+
// we lost the confirmation" from "the redeem never left". The EVM-2 receipt
|
|
913
|
+
// token approval sent earlier in the try is deliberately NOT tracked here:
|
|
914
|
+
// a failed approval means the redeem itself was never sent.
|
|
915
|
+
let requestRedeemTx;
|
|
705
916
|
try {
|
|
706
917
|
// Get vault version to determine correct ABI
|
|
707
918
|
const tokenizedVault = (await (0, core_1.fetchTokenizedVault)(target))?.[0];
|
|
@@ -717,14 +928,16 @@ async function vaultRequestRedeem(signer, options) {
|
|
|
717
928
|
provider: signer,
|
|
718
929
|
abi: abis_1.ABI_TOKENIZED_VAULT_V2,
|
|
719
930
|
});
|
|
720
|
-
// Get receipt token for decimals
|
|
721
|
-
|
|
931
|
+
// Get receipt token for decimals. Retried on transport blips only; a
|
|
932
|
+
// misroute (a non-evm-2 vault reaching this branch) still throws the
|
|
933
|
+
// original error. See getReceiptTokenAddressOrThrow.
|
|
934
|
+
const receiptTokenAddr = await (0, core_1.getReceiptTokenAddressOrThrow)(signer, target, 'vaultRequestRedeem:receiptToken');
|
|
722
935
|
const receiptContract = (0, core_1.createContract)({
|
|
723
936
|
address: receiptTokenAddr,
|
|
724
937
|
provider: signer,
|
|
725
938
|
abi: abis_1.ABI_ERC20,
|
|
726
939
|
});
|
|
727
|
-
decimals =
|
|
940
|
+
decimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'vaultRequestRedeem:receiptDecimals');
|
|
728
941
|
// Convert amount to bignumber
|
|
729
942
|
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
|
|
730
943
|
// EVM-2 lp/receipt token is a separate ERC-20: vault.transferFrom(lp)
|
|
@@ -752,12 +965,11 @@ async function vaultRequestRedeem(signer, options) {
|
|
|
752
965
|
provider: signer,
|
|
753
966
|
abi: abis_1.ABI_LENDING_POOLS,
|
|
754
967
|
});
|
|
755
|
-
decimals = await
|
|
968
|
+
decimals = await (0, core_1.getDecimalsOrThrow)(signer, target, 'vaultRequestRedeem:poolDecimals');
|
|
756
969
|
}
|
|
757
970
|
// Convert amount to bignumber (for non-evm-2 vaults)
|
|
758
971
|
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, decimals);
|
|
759
972
|
// Withdraw from vault - EVM-2 uses 2 params, others use 3 params
|
|
760
|
-
let requestRedeemTx;
|
|
761
973
|
if (options.isInstantRedeem) {
|
|
762
974
|
if (vaultVersion === 'evm-2') {
|
|
763
975
|
// EVM-2: instantRedeem(uint256 shares, address receiverAddr) - 2 params
|
|
@@ -797,17 +1009,20 @@ async function vaultRequestRedeem(signer, options) {
|
|
|
797
1009
|
}
|
|
798
1010
|
if (e instanceof core_1.AugustSDKError)
|
|
799
1011
|
throw e;
|
|
1012
|
+
const broadcast = localTxBroadcastContext(requestRedeemTx, e);
|
|
800
1013
|
// User-cancelled redeem requests are normal; only genuine failures stay at error.
|
|
801
1014
|
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
802
1015
|
(0, core_1.logChainError)('requestRedeem', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
803
1016
|
target,
|
|
804
1017
|
amount,
|
|
1018
|
+
...broadcast,
|
|
805
1019
|
});
|
|
806
1020
|
throwIfInsufficientFunds(insufficientFunds, 'Request redeem', e, {
|
|
807
1021
|
target,
|
|
808
1022
|
amount,
|
|
1023
|
+
...broadcast,
|
|
809
1024
|
});
|
|
810
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Request redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount } });
|
|
1025
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Request redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, amount, ...broadcast } });
|
|
811
1026
|
}
|
|
812
1027
|
}
|
|
813
1028
|
/**
|
|
@@ -849,6 +1064,8 @@ async function vaultRedeem(signer, options) {
|
|
|
849
1064
|
}
|
|
850
1065
|
if (e instanceof core_1.AugustSDKError)
|
|
851
1066
|
throw e;
|
|
1067
|
+
// Single tx on this path, so the error's own marker can only be the redeem.
|
|
1068
|
+
const broadcast = errorTxBroadcastContext(e);
|
|
852
1069
|
// User-cancelled redeems are normal; only genuine failures stay at error.
|
|
853
1070
|
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
854
1071
|
(0, core_1.logChainError)('redeem', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
@@ -857,6 +1074,7 @@ async function vaultRedeem(signer, options) {
|
|
|
857
1074
|
month,
|
|
858
1075
|
day,
|
|
859
1076
|
receiverIndex,
|
|
1077
|
+
...broadcast,
|
|
860
1078
|
});
|
|
861
1079
|
throwIfInsufficientFunds(insufficientFunds, 'Redeem', e, {
|
|
862
1080
|
target,
|
|
@@ -864,8 +1082,12 @@ async function vaultRedeem(signer, options) {
|
|
|
864
1082
|
month,
|
|
865
1083
|
day,
|
|
866
1084
|
receiverIndex,
|
|
1085
|
+
...broadcast,
|
|
1086
|
+
});
|
|
1087
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, {
|
|
1088
|
+
cause: e,
|
|
1089
|
+
context: { target, year, month, day, receiverIndex, ...broadcast },
|
|
867
1090
|
});
|
|
868
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, year, month, day, receiverIndex } });
|
|
869
1091
|
}
|
|
870
1092
|
}
|
|
871
1093
|
/**
|
|
@@ -906,6 +1128,9 @@ async function depositNative(signer, options) {
|
|
|
906
1128
|
throw new core_1.AugustValidationError('INVALID_INPUT', 'depositNative: amount is required');
|
|
907
1129
|
}
|
|
908
1130
|
validateAmountPrecision(amount);
|
|
1131
|
+
// Hoisted so the catch can distinguish "broadcast, confirmation lost" from
|
|
1132
|
+
// "never sent" — see localTxBroadcastContext.
|
|
1133
|
+
let depositTx;
|
|
909
1134
|
try {
|
|
910
1135
|
// Create wrapper contract instance
|
|
911
1136
|
const wrapperContract = (0, core_1.createContract)({
|
|
@@ -920,7 +1145,6 @@ async function depositNative(signer, options) {
|
|
|
920
1145
|
: typeof amount === 'string'
|
|
921
1146
|
? BigInt(amount)
|
|
922
1147
|
: BigInt(amount);
|
|
923
|
-
let depositTx;
|
|
924
1148
|
// Connect the contract first, then get the function to disambiguate between overloads
|
|
925
1149
|
const connectedContract = wrapperContract.connect(signer);
|
|
926
1150
|
// Call depositNative with or without receiver parameter
|
|
@@ -955,19 +1179,22 @@ async function depositNative(signer, options) {
|
|
|
955
1179
|
}
|
|
956
1180
|
if (e instanceof core_1.AugustSDKError)
|
|
957
1181
|
throw e;
|
|
1182
|
+
const broadcast = localTxBroadcastContext(depositTx, e);
|
|
958
1183
|
// User-cancelled native deposits are normal; only genuine failures stay at error.
|
|
959
1184
|
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
960
1185
|
(0, core_1.logChainError)('depositNative', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
961
1186
|
wrapperAddress,
|
|
962
1187
|
receiver,
|
|
963
1188
|
amount,
|
|
1189
|
+
...broadcast,
|
|
964
1190
|
});
|
|
965
1191
|
throwIfInsufficientFunds(insufficientFunds, 'Deposit native', e, {
|
|
966
1192
|
wrapperAddress,
|
|
967
1193
|
receiver,
|
|
968
1194
|
amount,
|
|
1195
|
+
...broadcast,
|
|
969
1196
|
});
|
|
970
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Deposit native failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { wrapperAddress, receiver, amount } });
|
|
1197
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Deposit native failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { wrapperAddress, receiver, amount, ...broadcast } });
|
|
971
1198
|
}
|
|
972
1199
|
}
|
|
973
1200
|
/**
|
|
@@ -1040,6 +1267,8 @@ async function rwaRedeemAsset(signer, options) {
|
|
|
1040
1267
|
}
|
|
1041
1268
|
if (e instanceof core_1.AugustSDKError)
|
|
1042
1269
|
throw e;
|
|
1270
|
+
// Single tx on this path, so the error's own marker can only be the redeem.
|
|
1271
|
+
const broadcast = errorTxBroadcastContext(e);
|
|
1043
1272
|
// User-cancelled RWA redeems are normal; only genuine failures stay at error.
|
|
1044
1273
|
const insufficientFunds = (0, core_1.isInsufficientFundsError)(e);
|
|
1045
1274
|
(0, core_1.logChainError)('rwaRedeemAsset', e, (0, core_1.isUserRejectionError)(e) || insufficientFunds, {
|
|
@@ -1047,14 +1276,16 @@ async function rwaRedeemAsset(signer, options) {
|
|
|
1047
1276
|
asset,
|
|
1048
1277
|
amount,
|
|
1049
1278
|
minOut,
|
|
1279
|
+
...broadcast,
|
|
1050
1280
|
});
|
|
1051
1281
|
throwIfInsufficientFunds(insufficientFunds, 'RWA redeem', e, {
|
|
1052
1282
|
target,
|
|
1053
1283
|
asset,
|
|
1054
1284
|
amount,
|
|
1055
1285
|
minOut,
|
|
1286
|
+
...broadcast,
|
|
1056
1287
|
});
|
|
1057
|
-
throw new core_1.AugustSDKError('UNKNOWN', `RWA redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, asset, amount, minOut } });
|
|
1288
|
+
throw new core_1.AugustSDKError('UNKNOWN', `RWA redeem failed: ${e instanceof Error ? e.message : 'Unknown error'}`, { cause: e, context: { target, asset, amount, minOut, ...broadcast } });
|
|
1058
1289
|
}
|
|
1059
1290
|
}
|
|
1060
1291
|
/** @internal */
|
|
@@ -1099,12 +1330,7 @@ async function dispatchViaSwapRouter(args) {
|
|
|
1099
1330
|
// (e.g. an 18-decimal share token over an 8-decimal WBTC underlying on
|
|
1100
1331
|
// a multi-asset v2 vault). The swap quote must use the underlying's
|
|
1101
1332
|
// on-chain decimals so Paraswap's /prices interprets the route correctly.
|
|
1102
|
-
const
|
|
1103
|
-
address: args.underlyingAsset,
|
|
1104
|
-
provider: args.signer,
|
|
1105
|
-
abi: abis_1.ABI_ERC20,
|
|
1106
|
-
});
|
|
1107
|
-
const underlyingDecimals = Number(await underlyingErc20.decimals());
|
|
1333
|
+
const underlyingDecimals = await (0, core_1.getDecimalsOrThrow)(args.signer, args.underlyingAsset, 'dispatchViaSwapRouter:underlyingDecimals');
|
|
1108
1334
|
// The on-chain SwapRouter only authorizes a specific (router, selector) pair
|
|
1109
1335
|
// per chain (see SWAP_ROUTER_DEX_AGGREGATOR). Pin the aggregator to that
|
|
1110
1336
|
// router's single generic method so the calldata selector is deterministic,
|
|
@@ -1470,15 +1696,12 @@ async function swapRouterDeposit(signer, options) {
|
|
|
1470
1696
|
});
|
|
1471
1697
|
const [asset, receiptTokenAddr] = await Promise.all([
|
|
1472
1698
|
poolContract.asset(),
|
|
1473
|
-
|
|
1699
|
+
// Retried on transport blips only; a misroute still throws. See
|
|
1700
|
+
// getReceiptTokenAddressOrThrow.
|
|
1701
|
+
(0, core_1.getReceiptTokenAddressOrThrow)(signer, vault, 'swapRouterDeposit:receiptToken'),
|
|
1474
1702
|
]);
|
|
1475
1703
|
underlyingAsset = asset;
|
|
1476
|
-
|
|
1477
|
-
address: receiptTokenAddr,
|
|
1478
|
-
provider: signer,
|
|
1479
|
-
abi: abis_1.ABI_ERC20,
|
|
1480
|
-
});
|
|
1481
|
-
vaultDecimals = Number(await receiptContract.decimals());
|
|
1704
|
+
vaultDecimals = await (0, core_1.getDecimalsOrThrow)(signer, receiptTokenAddr, 'swapRouterDeposit:receiptDecimals');
|
|
1482
1705
|
}
|
|
1483
1706
|
else {
|
|
1484
1707
|
const poolContract = (0, core_1.createContract)({
|
|
@@ -1488,10 +1711,10 @@ async function swapRouterDeposit(signer, options) {
|
|
|
1488
1711
|
});
|
|
1489
1712
|
const [asset, rawDecimals] = await Promise.all([
|
|
1490
1713
|
poolContract.asset(),
|
|
1491
|
-
|
|
1714
|
+
(0, core_1.getDecimalsOrThrow)(signer, vault, 'swapRouterDeposit:poolDecimals'),
|
|
1492
1715
|
]);
|
|
1493
1716
|
underlyingAsset = asset;
|
|
1494
|
-
vaultDecimals =
|
|
1717
|
+
vaultDecimals = rawDecimals;
|
|
1495
1718
|
}
|
|
1496
1719
|
const isNativeToken = depositAsset === ethers_1.ZeroAddress ||
|
|
1497
1720
|
depositAsset === '0x0000000000000000000000000000000000000000';
|
|
@@ -1501,14 +1724,7 @@ async function swapRouterDeposit(signer, options) {
|
|
|
1501
1724
|
isNativeToken,
|
|
1502
1725
|
isMultiAssetVault,
|
|
1503
1726
|
vaultDecimals,
|
|
1504
|
-
readErc20Decimals: async (addr) =>
|
|
1505
|
-
const erc20 = (0, core_1.createContract)({
|
|
1506
|
-
address: addr,
|
|
1507
|
-
provider: signer,
|
|
1508
|
-
abi: abis_1.ABI_ERC20,
|
|
1509
|
-
});
|
|
1510
|
-
return Number(await erc20.decimals());
|
|
1511
|
-
},
|
|
1727
|
+
readErc20Decimals: async (addr) => (0, core_1.getDecimalsOrThrow)(signer, addr, 'swapRouterDeposit:depositTokenDecimals'),
|
|
1512
1728
|
});
|
|
1513
1729
|
const normalizedAmt = (0, core_1.toNormalizedBn)(amount, depositTokenDecimals);
|
|
1514
1730
|
if (BigInt(normalizedAmt.raw) === 0n) {
|