@augustdigital/sdk 8.19.0 → 8.21.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.
@@ -49,18 +49,114 @@ function describeSolanaError(e) {
49
49
  ? str
50
50
  : REASONLESS_SOLANA_ERROR;
51
51
  }
52
+ /**
53
+ * Pick which token account to hand the vault program for a given mint, and
54
+ * describe how to create it when the user has none.
55
+ *
56
+ * The vault program constrains these accounts by `token::mint` +
57
+ * `token::authority` (not `associated_token::…`), so any account the signer
58
+ * owns qualifies. We therefore keep an existing account when there is one —
59
+ * that is where the user's balance already lives — and fall back to the
60
+ * associated token account otherwise.
61
+ *
62
+ * Two deliberate choices:
63
+ *
64
+ * - **The create is returned, not sent.** Prepended to the deposit/redeem via
65
+ * `.preInstructions()` it is atomic with the transfer, so there is no window
66
+ * in which the vault instruction can reference an account that has not
67
+ * landed. Sent as its own transaction — as this module used to — it raced
68
+ * its own confirmation. Observed in production (AUGUST-7221, 2026-08-11):
69
+ * a deposit failed with `Simulation failed … Logs: []` while the share-ATA
70
+ * create was still in flight, then succeeded 17s later on retry. The empty
71
+ * log array means the transaction never reached program execution, so there
72
+ * was nothing on-chain to debug from.
73
+ * - **Idempotent.** `createAssociatedTokenAccountIdempotentInstruction` is a
74
+ * no-op when the account already exists, so a concurrent create — another
75
+ * tab, a wallet's auto-provisioning — cannot turn into an
76
+ * `SystemError::AccountAlreadyInUse` ("already in use") failure between our
77
+ * read and our submit.
78
+ *
79
+ * Which account wins when several exist is decided by
80
+ * {@link selectRichestTokenAccount} — the same rule every balance *read* in
81
+ * this adapter uses, so the account a UI reports and the account a transfer
82
+ * names are never different accounts.
83
+ *
84
+ * Classic SPL Token only: the ATA is derived under `TOKEN_PROGRAM_ID`, matching
85
+ * the `tokenProgram` both handlers pass, even though the program's
86
+ * `TokenInterface` would also accept Token-2022.
87
+ *
88
+ * @param owner - Wallet that owns the account and pays rent for any creation.
89
+ * @throws AugustSDKError when a candidate account's balance cannot be read
90
+ * (propagated from the shared selector), and `TokenOwnerOffCurveError` when
91
+ * `owner` is off-curve and an ATA has to be derived.
92
+ */
93
+ async function resolveUserTokenAccount({ connection, owner, mint, }) {
94
+ const existing = await connection.getParsedTokenAccountsByOwner(owner, {
95
+ mint,
96
+ });
97
+ const richest = (0, utils_1.selectRichestTokenAccount)(existing.value);
98
+ if (richest)
99
+ return {
100
+ address: richest.pubkey,
101
+ balance: richest.amount,
102
+ frozen: richest.frozen,
103
+ };
104
+ const ata = await (0, spl_token_1.getAssociatedTokenAddress)(mint, owner);
105
+ return {
106
+ address: ata,
107
+ balance: BigInt(0),
108
+ frozen: false,
109
+ createIx: (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(owner, // payer
110
+ ata, // associated token account
111
+ owner, // owner
112
+ mint),
113
+ };
114
+ }
115
+ /**
116
+ * Resolve the two token accounts a vault instruction needs, concurrently.
117
+ *
118
+ * The lookups are independent, so running them in sequence doubles the
119
+ * round-trip latency on the hot path for no benefit — both results are needed
120
+ * before anything is submitted either way.
121
+ *
122
+ * Rejections are surfaced in argument order rather than in whichever order the
123
+ * RPC happened to fail, so the error a user sees does not depend on network
124
+ * timing. `Promise.all` would reject with whichever call failed *first*, which
125
+ * on a malformed RPC response (both lookups throwing) would name a different
126
+ * account run to run.
127
+ *
128
+ * @param mints - `[first, second]`; the returned tuple matches this order.
129
+ */
130
+ async function resolveUserTokenAccountPair({ connection, owner, mints, }) {
131
+ const settled = await Promise.allSettled(mints.map((mint) => resolveUserTokenAccount({ connection, owner, mint })));
132
+ const resolved = settled.map((outcome) => {
133
+ if (outcome.status === 'rejected')
134
+ throw outcome.reason;
135
+ return outcome.value;
136
+ });
137
+ return [resolved[0], resolved[1]];
138
+ }
52
139
  /**
53
140
  * Deposit funds into a Solana August vault and mint share tokens.
54
141
  *
55
142
  * @param depositAmount `bigint` is treated as raw on-chain units (preferred
56
143
  * — no JS-float round-trip). `number` is treated as a UI amount and
57
144
  * scaled by the deposit mint's decimals via {@link uiAmountToRawBn}.
58
- * @throws AugustValidationError on missing wallet/programId or invalid amount.
145
+ * @throws AugustValidationError on missing wallet/programId or invalid amount,
146
+ * or when the wallet's deposit-mint balance is below `depositAmount`.
147
+ * @throws AugustSDKError when a candidate token account's balance cannot be
148
+ * read, or wrapping any downstream failure (`cause` preserved).
149
+ * @remarks **Side effects.** Submits exactly one transaction, signed by the
150
+ * provider's wallet. When the wallet has no share account, that transaction
151
+ * also creates one, and `publicKey` pays its rent — roughly 0.00204 SOL,
152
+ * non-refundable while the account stays open — on top of the network fee.
153
+ * Callers should ensure the wallet holds enough SOL for both before
154
+ * prompting the user to sign.
59
155
  * @remarks The on-chain `deposit` instruction has no `min_shares_out`
60
156
  * parameter; slippage cannot be enforced on-chain until the program ships
61
157
  * that argument.
62
158
  */
63
- async function handleSolanaDeposit({ provider, connection, network = constants_1.fallbackNetwork, vaultProgramId, vaultAddress, depositAmount, publicKey, sendTransaction, idl, }) {
159
+ async function handleSolanaDeposit({ provider, connection, network = constants_1.fallbackNetwork, vaultProgramId, vaultAddress, depositAmount, publicKey, idl, }) {
64
160
  try {
65
161
  if (!publicKey)
66
162
  throw new core_1.AugustValidationError('INVALID_INPUT', 'handleSolanaDeposit: wallet not connected');
@@ -119,50 +215,70 @@ async function handleSolanaDeposit({ provider, connection, network = constants_1
119
215
  }
120
216
  depositAmountRaw = (0, utils_1.uiAmountToRawBn)(depositAmount, decimals);
121
217
  }
122
- // Find user's token accounts
123
- const userTokenAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
124
- mint: _depositMint,
125
- });
126
- core_1.Logger.log.info('handleSolanaDeposit', 'User token accounts', userTokenAccounts);
127
- const userShareAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
128
- mint: shareMintAddr,
218
+ // Re-check *after* scaling. The guard above tests the caller's number, but
219
+ // a positive UI amount below one raw unit (1e-7 into a 6-decimal mint)
220
+ // truncates to zero here. Zero would then pass the balance gate below
221
+ // (`0n < 0n` is false) and submit a no-op transfer — from an account that
222
+ // may not even exist, since a zero balance is also what an absent account
223
+ // reports.
224
+ if (depositAmountRaw.lten(0)) {
225
+ throw new core_1.AugustValidationError('INVALID_INPUT', `Deposit amount is too small: ${depositAmount} is below one raw unit ` +
226
+ `of a ${decimals}-decimal mint.`);
227
+ }
228
+ // Match whatever this connection *reads* at. A `Connection` built without
229
+ // a commitment omits the parameter, so its reads take the RPC's default —
230
+ // `finalized`. Confirming writes at `confirmed` there would put reads
231
+ // behind writes again, which is the mismatch this whole change removes.
232
+ // `SolanaAdapter` always sets one explicitly, so this fallback only
233
+ // applies to callers reaching these handlers directly via `Solana.actions`.
234
+ const txCommitment = connection.commitment ?? 'finalized';
235
+ // `depositAccount` funds the transfer; `shareAccount` receives the minted
236
+ // shares. Independent lookups, so they run concurrently — but the guards
237
+ // below stay in a fixed order so the first problem a user hits is always
238
+ // reported the same way.
239
+ const [depositAccount, shareAccount] = await resolveUserTokenAccountPair({
240
+ connection,
241
+ owner: _publicKey,
242
+ mints: [_depositMint, shareMintAddr],
129
243
  });
130
- core_1.Logger.log.info('handleSolanaDeposit', 'User share accounts', userShareAccounts);
131
- if (userTokenAccounts.value.length === 0) {
132
- throw new core_1.AugustValidationError('INVALID_INPUT', 'No token account found for deposit mint. Please create a token account first.');
244
+ // Gate on the balance, not on whether an account exists: an empty leftover
245
+ // account is a real state, and letting it through only trades this error
246
+ // for an insufficient-funds revert the user pays a signature to discover.
247
+ // The balance is already in hand from the lookup, so this costs no extra
248
+ // RPC.
249
+ if (depositAccount.frozen) {
250
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'Your token account for the deposit mint is frozen by the token issuer, ' +
251
+ 'so the deposit cannot be transferred out of it.');
133
252
  }
134
- let senderShareAccount;
135
- if (userShareAccounts.value.length === 0) {
136
- core_1.Logger.log.info('handleSolanaDeposit', 'Creating share account');
137
- // Create share account if it doesn't exist
138
- senderShareAccount = await (0, spl_token_1.getAssociatedTokenAddress)(shareMintAddr, _publicKey);
139
- core_1.Logger.log.info('handleSolanaDeposit', 'Sender share account', senderShareAccount.toBase58());
140
- // Create the associated token account
141
- const createAtaIx = (0, spl_token_1.createAssociatedTokenAccountInstruction)(_publicKey, // payer
142
- senderShareAccount, // associated token account
143
- _publicKey, // owner
144
- shareMintAddr);
145
- core_1.Logger.log.info('handleSolanaDeposit', 'Create ATA Ix', createAtaIx.programId.toBase58());
146
- const transaction = new web3_js_1.Transaction().add(createAtaIx);
147
- const signature = await sendTransaction(transaction, connection);
148
- core_1.Logger.log.info('handleSolanaDeposit', 'Created share account:', signature.toString());
253
+ if (depositAccount.balance < BigInt(depositAmountRaw.toString())) {
254
+ throw new core_1.AugustValidationError('INVALID_INPUT', `Insufficient balance to deposit: this wallet holds ${depositAccount.balance} ` +
255
+ `raw units of the deposit mint, and the deposit requires ${depositAmountRaw.toString()}.`);
149
256
  }
150
- else {
151
- senderShareAccount = userShareAccounts.value[0]?.pubkey;
257
+ const senderTokenAccount = depositAccount.address;
258
+ // A missing share account is routine — every first-time depositor starts
259
+ // here, as does anyone whose account was closed after redeeming their full
260
+ // balance — so it is created inline rather than rejecting the deposit.
261
+ // Minting into a *frozen* one fails just as transferring out of one does,
262
+ // so this destination needs the same guard as the funding account above.
263
+ if (shareAccount.frozen) {
264
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'Your share account is frozen by the token issuer, so it cannot ' +
265
+ 'receive the shares this deposit would mint.');
152
266
  }
153
- const senderTokenAccount = userTokenAccounts.value[0]?.pubkey;
154
- core_1.Logger.log.info('handleSolanaDeposit', 'Sender token account', senderTokenAccount);
267
+ const senderShareAccount = shareAccount.address;
268
+ const preInstructions = shareAccount.createIx
269
+ ? [shareAccount.createIx]
270
+ : [];
271
+ core_1.Logger.log.info('handleSolanaDeposit', 'Sender token account', senderTokenAccount.toBase58());
272
+ core_1.Logger.log.info('handleSolanaDeposit', shareAccount.createIx
273
+ ? 'Sender share account (created in this tx)'
274
+ : 'Sender share account (existing)', senderShareAccount.toBase58());
155
275
  core_1.Logger.log.info('handleSolanaDeposit', '\n\nDeposit TX Params:\n');
156
276
  core_1.Logger.log.info('handleSolanaDeposit', 'Program', depositAmountRaw);
157
277
  core_1.Logger.log.info('handleSolanaDeposit', 'Vault state PDA', vaultStatePda.toBase58());
158
278
  core_1.Logger.log.info('handleSolanaDeposit', 'Vault token ATA PDA', vaultTokenAtaPda.toBase58());
159
- core_1.Logger.log.info('handleSolanaDeposit', 'Sender token account', senderTokenAccount?.toBase58());
160
- core_1.Logger.log.info('handleSolanaDeposit', 'Sender share account', senderShareAccount?.toBase58());
161
279
  core_1.Logger.log.info('handleSolanaDeposit', 'Share mint', shareMintAddr.toBase58());
162
280
  core_1.Logger.log.info('handleSolanaDeposit', 'Deposit mint', _depositMint?.toBase58());
163
281
  core_1.Logger.log.info('handleSolanaDeposit', 'Signer', _publicKey.toBase58());
164
- // Get a fresh blockhash to avoid "Blockhash not found" errors
165
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
166
282
  // TODO(slippage): pass `min_shares_out` here when the program ships it.
167
283
  const tx = await program.methods
168
284
  .deposit(depositAmountRaw)
@@ -172,21 +288,25 @@ async function handleSolanaDeposit({ provider, connection, network = constants_1
172
288
  senderTokenAccount,
173
289
  senderShareAccount,
174
290
  shareMint: shareMintAddr,
175
- depositMint: depositMint,
176
- signer: publicKey,
291
+ depositMint: _depositMint,
292
+ signer: _publicKey,
177
293
  tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
178
294
  })
295
+ .preInstructions(preInstructions)
296
+ // `.rpc()` builds, signs, submits and confirms in one step. The extra
297
+ // `getLatestBlockhash` + `confirmTransaction` this module used to run
298
+ // afterwards re-confirmed an already-confirmed signature against a
299
+ // blockhash the transaction never carried.
300
+ //
301
+ // Commitment is taken from the same `Connection` every read in this
302
+ // adapter uses, so a caller who asks for 'finalized' gets it on both
303
+ // sides rather than finalized reads racing confirmed writes.
179
304
  .rpc({
180
305
  skipPreflight: false,
181
- preflightCommitment: 'confirmed',
306
+ preflightCommitment: txCommitment,
307
+ commitment: txCommitment,
182
308
  });
183
309
  core_1.Logger.log.info('handleSolanaDeposit', 'Deposit successful:', tx);
184
- // Wait for confirmation with the blockhash we used
185
- await connection.confirmTransaction({
186
- signature: tx,
187
- blockhash,
188
- lastValidBlockHeight,
189
- }, 'confirmed');
190
310
  return tx;
191
311
  }
192
312
  catch (e) {
@@ -216,9 +336,18 @@ async function handleSolanaDeposit({ provider, connection, network = constants_1
216
336
  * @param redeemShares `bigint` is treated as raw share-token units;
217
337
  * `number` is treated as a UI amount. See {@link handleSolanaDeposit} for
218
338
  * the same caveat about the missing on-chain slippage guard.
219
- * @throws AugustValidationError on missing wallet/programId or invalid amount.
339
+ * @throws AugustValidationError on missing wallet/programId or invalid amount,
340
+ * or when the wallet holds fewer shares than `redeemShares`.
341
+ * @throws AugustSDKError when a candidate token account's balance cannot be
342
+ * read, or wrapping any downstream failure (`cause` preserved).
343
+ * @remarks **Side effects.** Submits exactly one transaction, signed by the
344
+ * provider's wallet. That transaction also creates the payout account when
345
+ * the wallet has none, and the vault's fee-recipient account when *it* has
346
+ * none — `publicKey` pays rent for both, roughly 0.00204 SOL each and
347
+ * non-refundable, on top of the network fee. Callers should ensure the
348
+ * wallet holds enough SOL for all of it before prompting the user to sign.
220
349
  */
221
- async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultAddress, publicKey, redeemShares, sendTransaction, idl, }) {
350
+ async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultAddress, publicKey, redeemShares, idl, }) {
222
351
  try {
223
352
  if (!publicKey)
224
353
  throw new core_1.AugustValidationError('INVALID_INPUT', 'handleSolanaRedeem: wallet not connected');
@@ -276,21 +405,48 @@ async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultA
276
405
  }
277
406
  redeemSharesRaw = (0, utils_1.uiAmountToRawBn)(redeemShares, shareDecimals);
278
407
  }
279
- // Find user's token accounts
280
- const userTokenAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
281
- mint: _depositMint,
282
- });
283
- const userShareAccounts = await connection.getParsedTokenAccountsByOwner(_publicKey, {
284
- mint: shareMintAddr,
408
+ // See `handleSolanaDeposit` a positive UI amount can truncate to zero,
409
+ // which would pass the share-balance gate below and burn nothing.
410
+ if (redeemSharesRaw.lten(0)) {
411
+ throw new core_1.AugustValidationError('INVALID_INPUT', `Redemption amount is too small: ${redeemShares} is below one raw unit ` +
412
+ `of a ${shareDecimals}-decimal share mint.`);
413
+ }
414
+ // Match whatever this connection *reads* at. A `Connection` built without
415
+ // a commitment omits the parameter, so its reads take the RPC's default —
416
+ // `finalized`. Confirming writes at `confirmed` there would put reads
417
+ // behind writes again, which is the mismatch this whole change removes.
418
+ // `SolanaAdapter` always sets one explicitly, so this fallback only
419
+ // applies to callers reaching these handlers directly via `Solana.actions`.
420
+ const txCommitment = connection.commitment ?? 'finalized';
421
+ // `shareAccount` is burned from; `payoutAccount` receives the underlying.
422
+ // Independent lookups, so they run concurrently — guards stay ordered.
423
+ const [shareAccount, payoutAccount] = await resolveUserTokenAccountPair({
424
+ connection,
425
+ owner: _publicKey,
426
+ mints: [shareMintAddr, _depositMint],
285
427
  });
286
- if (userTokenAccounts.value.length === 0) {
287
- throw new core_1.AugustValidationError('INVALID_INPUT', 'No token account found for deposit mint. Please create a token account first.');
428
+ // Gated on balance for the same reason as the deposit's funding account.
429
+ if (shareAccount.frozen) {
430
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'Your share account is frozen by the token issuer, so these shares ' +
431
+ 'cannot be redeemed.');
432
+ }
433
+ if (shareAccount.balance < BigInt(redeemSharesRaw.toString())) {
434
+ throw new core_1.AugustValidationError('INVALID_INPUT', `Insufficient shares to redeem: this wallet holds ${shareAccount.balance} ` +
435
+ `raw share units, and the redemption requires ${redeemSharesRaw.toString()}.`);
288
436
  }
289
- if (userShareAccounts.value.length === 0) {
290
- throw new core_1.AugustValidationError('INVALID_INPUT', 'No share account found. You need to deposit first to get shares before you can redeem.');
437
+ const senderShareAccount = shareAccount.address;
438
+ // Redeeming is precisely how a holder first receives the deposit mint, so
439
+ // requiring the payout account to pre-exist (as this module used to) left
440
+ // shares unredeemable through this SDK for everyone who acquired them any
441
+ // way other than depositing from this same wallet — a transfer, an
442
+ // airdrop, a market buy — as well as any depositor who later closed their
443
+ // deposit-mint account. A frozen account cannot receive either, so the
444
+ // payout would revert.
445
+ if (payoutAccount.frozen) {
446
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'Your token account for the deposit mint is frozen by the token issuer, ' +
447
+ 'so it cannot receive the redemption proceeds.');
291
448
  }
292
- const senderTokenAccount = userTokenAccounts?.value[0]?.pubkey;
293
- const senderShareAccount = userShareAccounts?.value[0]?.pubkey;
449
+ const senderTokenAccount = payoutAccount.address;
294
450
  // Get the fee recipient from vault state
295
451
  const readOnlyProvider = utils_1.SolanaUtils.getReadOnlyProvider({ connection });
296
452
  const readOnlyProgram = utils_1.SolanaUtils.getProgram({
@@ -301,22 +457,20 @@ async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultA
301
457
  const vaultStateData = await readOnlyProgram.account.vaultState.fetch(vaultStatePda);
302
458
  // Get the fee recipient's token account
303
459
  const feeRecipientTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(_depositMint, vaultStateData.feeRecipient);
304
- // Check if the fee recipient token account exists, if not create it
305
- const feeRecipientAccountInfo = await connection.getAccountInfo(feeRecipientTokenAccount);
306
- if (!feeRecipientAccountInfo) {
307
- const createFeeRecipientAtaIx = (0, spl_token_1.createAssociatedTokenAccountInstruction)(_publicKey, // payer (user pays for the account creation)
460
+ // Every account this handler might have to create is created in the same
461
+ // transaction that uses it. The fee recipient's ATA is created
462
+ // unconditionally: the idempotent instruction is a no-op when the account
463
+ // already exists (costing the user nothing), which buys us one fewer RPC
464
+ // round trip and removes the read-then-create window entirely. The user
465
+ // pays the rent when it is genuinely absent, as before.
466
+ const preInstructions = [
467
+ (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(_publicKey, // payer (user pays for the account creation)
308
468
  feeRecipientTokenAccount, // associated token account
309
469
  vaultStateData.feeRecipient, // owner (the actual fee recipient)
310
- _depositMint);
311
- const createAccountTx = new web3_js_1.Transaction().add(createFeeRecipientAtaIx);
312
- const createAccountSignature = await sendTransaction(createAccountTx, connection);
313
- core_1.Logger.log.info('handleSolanaRedeem', 'Fee recipient token account created:', createAccountSignature);
314
- // Wait a moment for the account to be created
315
- await new Promise((resolve) => setTimeout(resolve, 1000));
316
- }
317
- else {
318
- core_1.Logger.log.info('handleSolanaRedeem', 'Fee recipient token account already exists');
319
- }
470
+ _depositMint),
471
+ ];
472
+ if (payoutAccount.createIx)
473
+ preInstructions.push(payoutAccount.createIx);
320
474
  core_1.Logger.log.info('handleSolanaRedeem', '\n\nRedeem TX Params:\n');
321
475
  core_1.Logger.log.info('handleSolanaRedeem', 'Redeem shares (UI):', redeemShares);
322
476
  core_1.Logger.log.info('handleSolanaRedeem', 'Redeem shares (raw):', redeemSharesRaw);
@@ -329,8 +483,6 @@ async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultA
329
483
  core_1.Logger.log.info('handleSolanaRedeem', 'Share mint:', shareMintAddr.toBase58());
330
484
  core_1.Logger.log.info('handleSolanaRedeem', 'Deposit mint:', _depositMint?.toBase58());
331
485
  core_1.Logger.log.info('handleSolanaRedeem', 'Signer:', _publicKey.toBase58());
332
- // Get a fresh blockhash to avoid "Blockhash not found" errors
333
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
334
486
  const tx = await program.methods
335
487
  .redeem(redeemSharesRaw)
336
488
  .accounts({
@@ -344,17 +496,15 @@ async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultA
344
496
  signer: _publicKey,
345
497
  tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
346
498
  })
499
+ .preInstructions(preInstructions)
500
+ // See `handleSolanaDeposit` for why `.rpc()` owns the confirmation and
501
+ // why the commitment comes from the connection.
347
502
  .rpc({
348
503
  skipPreflight: false,
349
- preflightCommitment: 'confirmed',
504
+ preflightCommitment: txCommitment,
505
+ commitment: txCommitment,
350
506
  });
351
507
  core_1.Logger.log.info('handleSolanaRedeem', 'Redeem successful:', tx);
352
- // Wait for confirmation with the blockhash we used
353
- await connection.confirmTransaction({
354
- signature: tx,
355
- blockhash,
356
- lastValidBlockHeight,
357
- }, 'confirmed');
358
508
  return tx;
359
509
  }
360
510
  catch (e) {
@@ -1,4 +1,4 @@
1
- export declare const SUI_CHAIN_ID = 101;
1
+ export { SUI_CHAIN_ID } from '../../core/constants/web3';
2
2
  export declare const EMBER_API_BASE_URL = "https://vaults.api.sui-prod.bluefin.io/api/v1/vaults";
3
3
  export declare const EMBER_ENDPOINTS: {
4
4
  readonly VAULTS: "/";
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ALLOWED_SUI_VAULT_ADDRESSES = exports.EMBER_DEFAULTS = exports.EMBER_ENDPOINTS = exports.EMBER_API_BASE_URL = exports.SUI_CHAIN_ID = void 0;
4
- exports.SUI_CHAIN_ID = 101;
4
+ // Canonical definition lives in `core/constants/web3.ts` so `core/` can
5
+ // recognise the Sui chain ID without importing the adapter layer. Re-exported
6
+ // here to keep `adapters/sui/constants` import sites (and the published API)
7
+ // unchanged.
8
+ var web3_1 = require("../../core/constants/web3");
9
+ Object.defineProperty(exports, "SUI_CHAIN_ID", { enumerable: true, get: function () { return web3_1.SUI_CHAIN_ID; } });
5
10
  exports.EMBER_API_BASE_URL = 'https://vaults.api.sui-prod.bluefin.io/api/v1/vaults';
6
11
  exports.EMBER_ENDPOINTS = {
7
12
  VAULTS: '/',
@@ -1,6 +1,13 @@
1
1
  import type * as Sentry from '@sentry/browser';
2
2
  import type { IEnv } from '../../types';
3
3
  import type { IAnalyticsConfig } from './types';
4
+ /**
5
+ * Clear the error dedupe bookkeeping. Test-only — the rate limiter is process
6
+ * lifetime state and would otherwise leak between test cases.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare function resetErrorDedupe(): void;
4
11
  /**
5
12
  * Initialize Sentry with SDK-specific configuration. Idempotent: re-calls
6
13
  * only refresh user identity and the cached API-key hash.