diff --git a/packages/router/src/handler.ts b/packages/router/src/handler.ts index 7033bc9f86..677615c9d6 100644 --- a/packages/router/src/handler.ts +++ b/packages/router/src/handler.ts @@ -346,6 +346,25 @@ export class Handler { messagingError: jsonifyError(err as Error), }), ); + }) + .orElse((err) => { + this.logger.info({ method, methodId, transactionHash: "" }, "Error relaying transaction"); + const _err = new HandlerError(HandlerError.reasons.TxServiceError, { + method, + methodId, + calling: "txManager.fulfill", + txServiceError: jsonifyError(err), + }); + return ResultAsync.fromPromise( + this.messagingService.publishMetaTxResponse(inbox, { chainId, transactionHash: "" }, jsonifyError(_err)), + (err) => + new HandlerError(HandlerError.reasons.MessagingError, { + method, + methodId, + calling: "messagingService.publishMetaTxResponse", + messagingError: jsonifyError(err as Error), + }), + ); }); if (res.isOk()) { @@ -428,21 +447,28 @@ export class Handler { transactionId: txData.transactionId, prepareError: jsonifyError(res.error), }, - "Could not prepare tx, cancelling", + "Could not prepare tx", ); - const cancelRes = await this.txManager.cancel(txData.sendingChainId, { - txData, - signature: "0x", - relayerFee: "0", - }); - if (cancelRes.isOk()) { - this.logger.warn( - { method, methodId, transactionHash: cancelRes.value.transactionHash }, - "Cancelled transaction", - ); - } else { - this.logger.error({ method, methodId }, "Could not cancel transaction after error!"); - } + this.logger.error( + { + method, + methodId, + }, + "Do not cancel ATM, figure out why we are in this case first", + ); + // const cancelRes = await this.txManager.cancel(txData.sendingChainId, { + // txData, + // signature: "0x", + // relayerFee: "0", + // }); + // if (cancelRes.isOk()) { + // this.logger.warn( + // { method, methodId, transactionHash: cancelRes.value.transactionHash }, + // "Cancelled transaction", + // ); + // } else { + // this.logger.error({ method, methodId }, "Could not cancel transaction after error!"); + // } } } } diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index d66bba54b1..aa72c1dad6 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -415,7 +415,7 @@ export class NxtpSdk { ); if (sendingAssetId !== constants.AddressZero) { - await this.transactionManager + const approveRes = await this.transactionManager .approveTokensIfNeeded(sendingChainId, sendingAssetId, amount, infiniteApprove) .andThen((approveTx) => { // handle optional approval @@ -460,20 +460,13 @@ export class NxtpSdk { }); } return okAsync(approveReceipt); - }) - .match( - () => { - this.logger.info({ method, methodId }, "Approval complete"); - }, - (err) => { - throw new NxtpSdkError(NxtpSdkError.reasons.ApprovalError, { - txError: jsonifyError(err as TransactionManagerError), - transactionId, - methodId, - method, - }); - }, - ); + }); + + if (approveRes.isOk()) { + this.logger.info({ method, methodId, approveRes: approveRes.value }, "Approval complete"); + } else { + throw approveRes.error; + } } // Prepare sender side tx @@ -499,28 +492,17 @@ export class NxtpSdk { amount, expiry, }; - return await this.transactionManager - .prepare(sendingChainId, params) - .andThen((prepareResponse) => { - this.evts.SenderTransactionPrepareSubmitted.post({ - prepareParams: params, - transactionResponse: prepareResponse, - }); - return okAsync(prepareResponse); - }) - .match( - (prepareResponse) => { - return { prepareResponse, transactionId }; - }, - (err) => { - throw new NxtpSdkError(NxtpSdkError.reasons.ApprovalError, { - txError: jsonifyError(err as TransactionManagerError), - transactionId, - methodId, - method, - }); - }, - ); + const prepareRes = await this.transactionManager.prepare(sendingChainId, params); + + if (prepareRes.isOk()) { + this.evts.SenderTransactionPrepareSubmitted.post({ + prepareParams: params, + transactionResponse: prepareRes.value, + }); + return { prepareResponse: prepareRes.value, transactionId }; + } else { + throw prepareRes.error; + } } public async finishTransfer( @@ -537,6 +519,7 @@ export class NxtpSdk { const signerAddress = await this.signer.getAddress(); this.logger.info({ method, methodId, transactionId: params.txData.transactionId }, "Generating fulfill signature"); + let signature: string; const prepareRes = ResultAsync.fromPromise( // Generate signature signFulfillTransactionPayload(txData.transactionId, relayerFee, this.signer), @@ -548,8 +531,9 @@ export class NxtpSdk { signerError: jsonifyError(err as Error), }), ) - .andThen((signature) => { - this.logger.info({ method, methodId }, "Generated signature"); + .andThen((_signature) => { + this.logger.info({ method, methodId, signature }, "Generated signature"); + signature = _signature; this.evts.ReceiverPrepareSigned.post({ signature, transactionId: txData.transactionId, signer: signerAddress }); if (!this.messaging.isConnected()) { return ResultAsync.fromPromise( @@ -563,9 +547,9 @@ export class NxtpSdk { }), ); } - return okAsync(signature); + return okAsync(undefined); }) - .andThen((signature) => { + .andThen(() => { // Submit fulfill to receiver chain this.logger.info({ method, methodId, transactionId: txData.transactionId, relayerFee }, "Preparing fulfill tx"); let callData = "0x"; @@ -592,97 +576,85 @@ export class NxtpSdk { if (useRelayers) { // send through messaging to metatx relayers - return await prepareRes - .andThen(({ callData, signature }) => { - const responseInbox = generateMessagingInbox(); + const metaTxRes = await prepareRes.andThen(({ callData, signature }) => { + const responseInbox = generateMessagingInbox(); - return ResultAsync.fromPromise( - new Promise<{ metaTxResponse?: MetaTxResponse; fulfillResponse?: providers.TransactionResponse }>( - async (resolve, reject) => { - if (!this.messaging.isConnected()) { - await this.messaging.connect(); - } - - await this.messaging.subscribeToMetaTxResponse(responseInbox, (data, err) => { - this.logger.info({ method, methodId, data, err }, "MetaTx response received"); - if (err || !data) { - return reject(err); - } - this.logger.info({ method, methodId, responseInbox, data }, "Fulfill metaTx response received"); - return resolve({ metaTxResponse: data, fulfillResponse: undefined }); - }); - - await this.messaging.publishMetaTxRequest( - { - type: "Fulfill", - relayerFee, - to: this.transactionManager.getTransactionManagerAddress(txData.receivingChainId), - chainId: txData.receivingChainId, - data: { - relayerFee, - signature, - txData, - callData, - }, - }, - responseInbox, - ); + return ResultAsync.fromPromise( + new Promise(async (resolve, reject) => { + if (!this.messaging.isConnected()) { + await this.messaging.connect(); + } + + await this.messaging.subscribeToMetaTxResponse(responseInbox, (data, err) => { + this.logger.info({ method, methodId, data, err }, "MetaTx response received"); + if (err || !data) { + return reject(err); + } + this.logger.info({ method, methodId, responseInbox, data }, "Fulfill metaTx response received"); + return resolve(data); + }); + + await this.messaging.publishMetaTxRequest( + { + type: "Fulfill", + relayerFee, + to: this.transactionManager.getTransactionManagerAddress(txData.receivingChainId), + chainId: txData.receivingChainId, + data: { + relayerFee, + signature, + txData, + callData, + }, }, - ), - (err) => - new NxtpSdkError(NxtpSdkError.reasons.MessagingError, { - method, - methodId, - transactionId: txData.transactionId, - messagingError: jsonifyError(err as Error), - }), - ); - }) - .match( - (res) => res, - (err) => { - throw err; - }, - ); - } else { - return await prepareRes - .andThen(({ callData, signature }) => { - return this.transactionManager - .fulfill(txData.receivingChainId, { - callData, - relayerFee, - signature, - txData, - }) - .map((t) => { - return { fulfillResponse: t, metaTxResponse: undefined }; - }) - .mapErr( - (err) => - new NxtpSdkError(NxtpSdkError.reasons.TxError, { - method, - methodId, - txError: jsonifyError(err), - transactionId: txData.transactionId, - }), + responseInbox, ); - }) - .match( - (res) => res, - (err) => { - throw err; - }, + }), + (err) => + new NxtpSdkError(NxtpSdkError.reasons.MessagingError, { + method, + methodId, + transactionId: txData.transactionId, + messagingError: jsonifyError(err as Error), + }), ); + }); + + if (metaTxRes.isOk()) { + this.logger.info({ method, methodId }, "Method complete"); + return { metaTxResponse: metaTxRes.value }; + } else { + throw metaTxRes.error; + } + } else { + const fulfillRes = await prepareRes.andThen(({ callData, signature }) => { + return this.transactionManager.fulfill(txData.receivingChainId, { + callData, + relayerFee, + signature, + txData, + }); + }); + if (fulfillRes.isOk()) { + this.logger.info({ method, methodId }, "Method complete"); + return { fulfillResponse: fulfillRes.value }; + } else { + throw fulfillRes.error; + } } } public async cancelExpired(cancelParams: CancelParams, chainId: number): Promise { - return await this.transactionManager.cancel(chainId, cancelParams).match( - (res) => res, - (err) => { - throw err; - }, - ); + const method = this.cancelExpired.name; + const methodId = getRandomBytes32(); + this.logger.info({ method, methodId, cancelParams, chainId }, "Method started"); + const cancelRes = await this.transactionManager.cancel(chainId, cancelParams); + if (cancelRes.isOk()) { + this.logger.info({ method, methodId }, "Method complete"); + return cancelRes.value; + } else { + throw cancelRes.error; + } } public changeInjectedSigner(signer: Signer) { diff --git a/packages/txservice/src/transaction.ts b/packages/txservice/src/transaction.ts index 130fa182e8..70cfc6a743 100644 --- a/packages/txservice/src/transaction.ts +++ b/packages/txservice/src/transaction.ts @@ -1,5 +1,5 @@ import { jsonifyError } from "@connext/nxtp-utils"; -import { BigNumber, providers } from "ethers"; +import { BigNumber, providers, errors } from "ethers"; import { BaseLogger } from "pino"; import hyperid from "hyperid"; import { Logger } from "ethers/lib/utils"; @@ -85,11 +85,13 @@ export class Transaction { if ( this.responses.length >= 1 && (error.message.includes("nonce has already been used") || + (error as any).reason.includes("nonce has already been used") || + (error as any).code === errors.NONCE_EXPIRED || // If we get a 'nonce is too low' message, a previous tx has been mined, and ethers thought // we were making another tx attempt with the same nonce. - error.message.includes("Transaction nonce is too low.") || + error.message.includes("Transaction nonce is too low") || // Another ethers message that we could potentially be getting back. - error.message.includes("There is another transaction with same nonce in the queue.")) + error.message.includes("There is another transaction with same nonce in the queue")) ) { this.nonceExpired = true; await this.logInfo("Tx reverted: nonce already used.", method, { error: error.message });