@reservoir0x/relay-sdk
Version:
Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.
287 lines • 11.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendTransactionSafely = void 0;
const logger_js_1 = require("./logger.js");
const axios_js_1 = require("../utils/axios.js");
const client_js_1 = require("../client.js");
const index_js_1 = require("../errors/index.js");
const repeatUntilOk_js_1 = require("../utils/repeatUntilOk.js");
const getTenderlyDetails_js_1 = require("../utils/getTenderlyDetails.js");
async function sendTransactionSafely(chainId, items, step, wallet, setTxHashes, setInternalTxHashes, request, headers, crossChainIntentChainId, isValidating, details) {
const client = (0, client_js_1.getClient)();
try {
await (0, repeatUntilOk_js_1.repeatUntilOk)(async () => {
const walletChainId = await wallet.getChainId();
return walletChainId === chainId;
}, 10, undefined, 250);
}
catch (e) {
const walletChainId = await wallet.getChainId();
throw `Current chain id: ${walletChainId} does not match expected chain id: ${chainId} `;
}
let receipt;
let transactionCancelled = false;
let confirmationError = false;
const pollingInterval = client.pollingInterval ?? 5000;
const maximumAttempts = client.maxPollingAttemptsBeforeTimeout ??
(2.5 * 60 * 1000) / pollingInterval;
let waitingForConfirmation = true;
let attemptCount = 0;
let txHash;
const isBatchTransaction = Boolean(Array.isArray(items) &&
items.length > 1 &&
wallet.handleBatchTransactionStep);
if (isBatchTransaction) {
txHash = await wallet.handleBatchTransactionStep?.(chainId, items);
}
else {
txHash = await wallet.handleSendTransactionStep(chainId, Array.isArray(items) ? items[0] : items, step);
}
if (txHash === 'null') {
throw 'User rejected the request';
}
const check = Array.isArray(items)
? items.find((item) => item.check)?.check
: items.check;
postTransactionToSolver({
txHash,
chainId,
step,
request,
headers
});
if (!isBatchTransaction &&
!Array.isArray(items) &&
chainId === details?.currencyOut?.currency?.chainId) {
postSameChainTransactionToSolver({
calldata: JSON.stringify({ ...items.data, txHash }),
chainId,
step,
request,
headers
});
}
if (!txHash) {
throw Error('Transaction hash not returned from handleSendTransactionStep method');
}
setTxHashes([
{ txHash: txHash, chainId: chainId, isBatchTx: isBatchTransaction }
]);
const validate = (res) => {
(0, client_js_1.getClient)()?.log(['Execute Steps: Polling for confirmation', res], logger_js_1.LogLevel.Verbose);
if (res.status === 200 && res.data && res.data.status === 'failure') {
throw Error('Transaction failed');
}
if (res.status === 200 && res.data && res.data.status === 'fallback') {
throw Error('Transaction failed: Refunded');
}
if (res.status === 200 && res.data && res.data.status === 'success') {
if (txHash) {
setInternalTxHashes([
{ txHash: txHash, chainId: chainId, isBatchTx: isBatchTransaction }
]);
}
const chainTxHashes = res.data?.txHashes?.map((hash) => {
return {
txHash: hash,
chainId: res?.data?.destinationChainId ?? crossChainIntentChainId
};
});
setTxHashes(chainTxHashes);
return true;
}
return false;
};
const pollForConfirmation = async () => {
isValidating?.();
while (waitingForConfirmation &&
attemptCount < maximumAttempts &&
!transactionCancelled &&
!confirmationError) {
let res;
if (check?.endpoint && !request?.data?.useExternalLiquidity) {
res = await axios_js_1.axios.request({
url: `${request.baseURL}${check?.endpoint}`,
method: check?.method,
headers: headers
});
}
if (!res || validate(res)) {
waitingForConfirmation = false;
}
else if (res) {
if (res.data.status !== 'pending') {
isValidating?.(res);
attemptCount++;
}
await new Promise((resolve) => setTimeout(resolve, pollingInterval));
}
}
if (attemptCount >= maximumAttempts) {
if (receipt) {
throw new index_js_1.SolverStatusTimeoutError(txHash, attemptCount);
}
else {
throw new index_js_1.DepositTransactionTimeoutError(txHash, attemptCount);
}
}
if (transactionCancelled) {
throw Error('Transaction was cancelled');
}
return true;
};
const waitForTransaction = () => {
const controller = new AbortController();
const signal = controller.signal;
return {
promise: wallet
.handleConfirmTransactionStep(txHash, chainId, (replacementTxHash) => {
if (signal.aborted) {
return;
}
setTxHashes([{ txHash: replacementTxHash, chainId: chainId }]);
txHash = replacementTxHash;
attemptCount = 0;
(0, client_js_1.getClient)()?.log(['Transaction replaced', replacementTxHash], logger_js_1.LogLevel.Verbose);
postTransactionToSolver({
txHash: replacementTxHash,
chainId,
step,
request,
headers
});
if (!isBatchTransaction &&
!Array.isArray(items) &&
chainId === details?.currencyOut?.currency?.chainId) {
postSameChainTransactionToSolver({
calldata: JSON.stringify({ ...items.data, replacementTxHash }),
chainId,
step,
request,
headers
});
}
}, () => {
if (signal.aborted) {
return;
}
transactionCancelled = true;
(0, client_js_1.getClient)()?.log(['Transaction cancelled'], logger_js_1.LogLevel.Verbose);
})
.then((data) => {
if (signal.aborted) {
return;
}
receipt = data;
if (receipt &&
typeof receipt === 'object' &&
'status' in receipt &&
receipt.status === 'reverted') {
throw 'Transaction Reverted';
}
(0, client_js_1.getClient)()?.log(['Transaction Receipt obtained', receipt], logger_js_1.LogLevel.Verbose);
})
.catch(async (error) => {
if (signal.aborted) {
return;
}
let tenderlyError = null;
if (receipt && receipt.transactionHash) {
tenderlyError = await (0, getTenderlyDetails_js_1.getTenderlyDetails)(receipt.transactionHash);
}
(0, client_js_1.getClient)()?.log(['Error in handleConfirmTransactionStep', error], logger_js_1.LogLevel.Error);
if (error.message === 'Transaction cancelled') {
transactionCancelled = true;
}
else {
confirmationError = true;
throw new index_js_1.TransactionConfirmationError(error, receipt, tenderlyError);
}
}),
controller
};
};
if (chainId === 8253038) {
return true;
}
if (isBatchTransaction) {
await pollForConfirmation();
}
else if (step.id === 'approve' ||
details?.currencyOut?.currency?.chainId === 8253038 ||
request?.data?.useExternalLiquidity) {
await waitForTransaction().promise;
if (details?.currencyOut?.currency?.chainId !== 8253038 &&
!request?.data?.useExternalLiquidity) {
await pollForConfirmation();
}
}
else {
const { promise: receiptPromise, controller: receiptController } = waitForTransaction();
const confirmationPromise = pollForConfirmation();
await Promise.race([receiptPromise, confirmationPromise]);
if (waitingForConfirmation) {
await confirmationPromise;
}
if (!receipt) {
if (!check) {
await receiptPromise;
}
else {
receiptController.abort();
}
}
}
return true;
}
exports.sendTransactionSafely = sendTransactionSafely;
const postSameChainTransactionToSolver = async ({ calldata, chainId, request, headers, step }) => {
if (calldata && step.requestId && chainId) {
(0, client_js_1.getClient)()?.log(['Posting same chain transaction to notify the solver'], logger_js_1.LogLevel.Verbose);
try {
const triggerData = {
tx: calldata,
chainId: chainId.toString(),
requestId: step.requestId
};
axios_js_1.axios
.request({
url: `${request.baseURL}/transactions/single`,
method: 'POST',
headers: headers,
data: triggerData
})
.then(() => {
(0, client_js_1.getClient)()?.log(['Same chain transaction notified to the solver'], logger_js_1.LogLevel.Verbose);
});
}
catch (e) {
(0, client_js_1.getClient)()?.log(['Failed to post same chain transaction to solver', e], logger_js_1.LogLevel.Warn);
}
}
};
const postTransactionToSolver = async ({ txHash, chainId, request, headers, step }) => {
if (step.id === 'deposit' && txHash) {
(0, client_js_1.getClient)()?.log(['Posting transaction to notify the solver'], logger_js_1.LogLevel.Verbose);
try {
const triggerData = {
txHash,
chainId: chainId.toString()
};
axios_js_1.axios
.request({
url: `${request.baseURL}/transactions/index`,
method: 'POST',
headers: headers,
data: triggerData
})
.then(() => {
(0, client_js_1.getClient)()?.log(['Transaction notified to the solver'], logger_js_1.LogLevel.Verbose);
});
}
catch (e) {
(0, client_js_1.getClient)()?.log(['Failed to post transaction to solver', e], logger_js_1.LogLevel.Warn);
}
}
};
//# sourceMappingURL=transaction.js.map