Skip to content

Commit 895040c

Browse files
refactor: add formatConfirmationError
1 parent b298950 commit 895040c

1 file changed

Lines changed: 62 additions & 25 deletions

File tree

src/utils/wallet/solana.ts

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
SendOptions,
1919
SignatureResult,
2020
Transaction,
21+
TransactionError,
2122
} from '@solana/web3.js';
2223
import { clusterApiUrl, Connection } from '@solana/web3.js';
2324

@@ -104,9 +105,16 @@ export function fetchFogoOptions() {
104105
};
105106
}
106107

107-
// This function signs and sends the transaction while constantly checking for confirmation
108-
// and resending the transaction if it hasn't been confirmed after the specified interval
109-
// See https://docs.triton.one/chains/solana/sending-txs for more information
108+
/**
109+
* This function signs and sends the transaction while constantly checking for confirmation
110+
* and resending the transaction if it hasn't been confirmed after the specified interval
111+
* See https://docs.triton.one/chains/solana/sending-txs for more information.
112+
*
113+
* @param request The unsigned transaction to sign and send
114+
* @param wallet The wallet to use for signing and sending the transaction
115+
* @param options Optional confirmation options
116+
* @returns The transaction signature
117+
*/
110118
export async function signAndSendTransactionWithResends(
111119
request: SolanaUnsignedTransaction<Network>,
112120
wallet: Wallet | undefined,
@@ -132,12 +140,15 @@ export async function signAndSendTransactionWithResends(
132140
commitment,
133141
);
134142

143+
const confirmationPromiseTimer = 1_000; // How long to wait for confirmation before resending
144+
135145
const signature = await signAndConfirmTransactionWhilstResending(
136146
{ serializedTransaction, sendOptions },
137147
connection,
138148
blockhash,
139149
lastValidBlockHeight,
140150
commitment,
151+
confirmationPromiseTimer,
141152
);
142153

143154
return signature;
@@ -152,9 +163,7 @@ async function signAndConfirmTransactionWhilstResending(
152163
blockHash: string,
153164
lastValidBlockHeight: number,
154165
commitment: Commitment,
155-
{
156-
initialDelay = 1000, // 1 second
157-
}: { retries?: number; initialDelay?: number; maxDelay?: number } = {},
166+
confirmationPromiseTimer: number,
158167
): Promise<string> {
159168
const { signature, confirmPromise } =
160169
await sendTransactionAndGetConfirmPromise(
@@ -169,7 +178,7 @@ async function signAndConfirmTransactionWhilstResending(
169178
signature,
170179
connection,
171180
transaction,
172-
initialDelay,
181+
confirmationPromiseTimer,
173182
confirmPromise,
174183
);
175184

@@ -225,9 +234,9 @@ async function resendTransactionUntilConfirmed(
225234
serializedTransaction: Uint8Array | Buffer | number[];
226235
sendOptions?: SendOptions;
227236
},
228-
txRetryInterval = 1000,
237+
confirmationPromiseTimer: number,
229238
confirmTransactionPromise: Promise<RpcResponseAndContext<SignatureResult>>,
230-
): Promise<void> {
239+
) {
231240
let isTransactionConfirmed: RpcResponseAndContext<SignatureResult> | null =
232241
null;
233242
try {
@@ -237,7 +246,7 @@ async function resendTransactionUntilConfirmed(
237246
new Promise<null>((resolve) =>
238247
setTimeout(() => {
239248
resolve(null);
240-
}, txRetryInterval),
249+
}, confirmationPromiseTimer),
241250
),
242251
]);
243252
if (isTransactionConfirmed) {
@@ -260,30 +269,42 @@ async function resendTransactionUntilConfirmed(
260269
}
261270
}
262271

263-
return;
272+
if (isTransactionConfirmed && isTransactionConfirmed.value.err) {
273+
const errorMessage = formatConfirmationError(
274+
isTransactionConfirmed.value.err,
275+
);
276+
throw new Error(`Transaction failed: ${errorMessage}`);
277+
}
264278
}
265279

280+
/**
281+
* Attempt to recover a transaction that exceeded its blockheight,
282+
* by polling until it appears on chain.
283+
*
284+
* @param connection Solana RPC connection
285+
* @param signature The transaction signature to look up
286+
* @param retries Number of retries (default: 5)
287+
* @param delay Delay between retries in ms (default: 2000)
288+
* @returns The signature once found, or throws if retries are exhausted
289+
*/
266290
async function recoverBlockheightExceededTransaction(
267291
e: unknown,
268292
connection: Connection,
269293
signature: string,
270294
{ retries = 5, delay = 2000 }: { retries?: number; delay?: number } = {},
271295
): Promise<string | null> {
272-
return retry(
273-
async () => {
274-
const tx = await connection.getTransaction(signature, {
275-
commitment: 'confirmed',
276-
maxSupportedTransactionVersion: 0,
277-
});
278-
279-
if (!tx) {
280-
throw new Error('Transaction not yet found on chain');
281-
}
296+
const findTransaction = async (): Promise<string> => {
297+
const tx = await connection.getTransaction(signature, {
298+
commitment: 'confirmed',
299+
maxSupportedTransactionVersion: 0,
300+
});
282301

283-
return signature;
284-
},
285-
{ retries, delay },
286-
);
302+
if (tx) return signature;
303+
304+
throw new Error('Transaction not yet found on chain');
305+
};
306+
307+
return retry(findTransaction, { retries, delay });
287308
}
288309

289310
async function createSolanaTransaction(
@@ -313,3 +334,19 @@ async function createSolanaTransaction(
313334
};
314335
return { serializedTransaction: serializedTx, sendOptions };
315336
}
337+
338+
function formatConfirmationError(err: TransactionError): string {
339+
if (!err) return 'Unknown error';
340+
341+
if (typeof err === 'object') {
342+
try {
343+
return JSON.stringify(err, (_key, value) =>
344+
typeof value === 'bigint' ? value.toString() : value,
345+
);
346+
} catch {
347+
return 'Unstringifiable error object';
348+
}
349+
}
350+
351+
return String(err);
352+
}

0 commit comments

Comments
 (0)