Skip to content

Commit

Permalink
Chore/axelarjs sdk GMP recover fixes (#1183)
Browse files Browse the repository at this point in the history
Co-authored-by: MC <[email protected]>
  • Loading branch information
benjamin852 and ffe9f8 authored Oct 6, 2024
1 parent 266c736 commit 582a634
Showing 1 changed file with 85 additions and 88 deletions.
173 changes: 85 additions & 88 deletions src/content/docs/dev/axelarjs-sdk/tx-status-query-recovery.mdx
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
# Query and recover GMP transactions
# Programmatically query and recover GMP transactions

Occasionally, transactions can get "stuck" in the pipeline from a source to destination chain (e.g. due to one-off issues that arise with relayers that operate on top of the network).
import { Callout } from "/src/components/callout"

The `AxelarGMPRecoveryAPI` module in the AxelarJS SDK can be used by your dApp to query the status of any General Message Passing (GMP) transaction (triggered by either `callContract` or `callContractWithToken`) on the gateway contract of a source chain and trigger a manual relay from source to destination if necessary. - The [GMP status tracker](/dev/general-message-passing/debug/transaction-recovery/) on Axelarscan makes use of this feature.
Transactions can occasionally get stuck in the pipeline from a source to destination chain, mostly for one of the two following reasons:

### Install the AxelarJS SDK module (`AxelarGMPRecoveryAPI`)
- The transaction is not relayed from the source chain into the Axelar network for processing.
- The transaction fails to get executed on the destination chain due to flawed contract logic.

The [`AxelarGMPRecoveryAPI`](https://github.com/axelarnetwork/axelarjs-sdk/blob/main/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts) module in the AxelarJS SDK can be used to troubleshoot:

- Query the status of any General Message Passing (GMP) transaction for either `callContract()` or `callContractWithToken()` on the gateway contract of a source chain.
- Retry a stuck transaction at any step including the gas payment step, relaying, execution step.
- Add gas to an underfunded transaction.

<Callout>
Transaction recovery can also be invoked through the [Axelarscan UI](/dev/general-message-passing/debug/transaction-recovery/).
</Callout>

## Install the `AxelarGMPRecoveryAPI` module

Install the AxelarJS SDK:

```bash
npm i @axelar-network/axelarjs-sdk
```

Instantiate the `AxelarGMPRecoveryAPI` module:
Instantiate the [`AxelarGMPRecoveryAPI`](https://github.com/axelarnetwork/axelarjs-sdk/blob/main/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts) module:

```ts
import {
Expand All @@ -25,38 +38,59 @@ const sdk = new AxelarGMPRecoveryAPI({
});
```

### Query transaction status by txHash
## Query transaction status by `txHash`

Invoke `queryTransactionStatus`:
See the status of a transaction by passing its `txHash` into [`queryTransactionStatus()`](https://github.com/axelarnetwork/axelarjs-sdk/blob/main/src/libs/TransactionRecoveryApi/AxelarRecoveryApi.ts#L226):

It takes two parameters
1. `txHash`: The transaction hash you are querying
2. `eventIndex`: This is an optional paramter, useful for separating multiple [internal transactions](https://docs.alchemy.com/docs/what-are-internal-transactions) from one another that may have the same transaction hash.

```ts
const txHash: string =
"0xfb6fb85f11496ef58b088116cb611497e87e9c72ff0c9333aa21491e4cdd397a";
const txStatus: GMPStatusResponse = await sdk.queryTransactionStatus(txHash);
const eventIndex: number = 97
const txStatus: GMPStatusResponse = await sdk.queryTransactionStatus(txHash, eventIndex);
```

Possible status responses for txStatus are outlined below:
The following are possible status responses:

```ts
interface GMPStatusResponse {
status: GMPStatus;
status: GMPStatus | string;
timeSpent?: Record<string, number>;
gasPaidInfo?: GasPaidInfo;
errors?: any;
callData?: any;
error?: GMPError;
callTx?: any;
executed?: any;
expressExecuted?: any;
approved?: any;
callback?: any;
}

enum GMPStatus {
SRC_GATEWAY_CALLED = "source_gateway_called",
DEST_GATEWAY_APPROVED = "destination_gateway_approved",
DEST_EXECUTED = "destination_executed",
DEST_EXECUTE_ERROR = "destination_execute_error",
EXPRESS_EXECUTED = "express_executed",
DEST_EXECUTE_ERROR = "error",
DEST_EXECUTING = "executing",
APPROVING = "approving",
FORECALLED = "forecalled",
FORECALLED_WITHOUT_GAS_PAID = "forecalled_without_gas_paid",
NOT_EXECUTED = "not_executed",
NOT_EXECUTED_WITHOUT_GAS_PAID = "not_executed_without_gas_paid",
INSUFFICIENT_FEE = "insufficient_fee",
UNKNOWN_ERROR = "unknown_error",
CANNOT_FETCH_STATUS = "cannot_fetch_status"
CANNOT_FETCH_STATUS = "cannot_fetch_status",
SRC_GATEWAY_CONFIRMED = "confirmed"
}

interface GasPaidInfo {
status: GasPaidStatus;
details?: any;
}

enum GasPaidStatus {
GAS_UNPAID = "gas_unpaid",
GAS_PAID = "gas_paid",
Expand All @@ -65,55 +99,68 @@ enum GasPaidStatus {
}
```

### Trigger manual relay of transaction through the Axelar network
## Manually relay the transaction through the Axelar network

The following method, once invoked, will:
Use [`manualRelayToDestChain()`](https://github.com/axelarnetwork/axelarjs-sdk/blob/main/src/libs/TransactionRecoveryApi/AxelarGMPRecoveryAPI.ts#L453) to dislodge a transaction [stuck at the **Confirm** step](https://testnet.axelarscan.io/gmp/0x111ff754b85538afcfea5aa59d01f976eb9a7b7b45b483c3fb2c48833bf0780d-4). This function will manually relay a transaction to the destination chain through the Axelar network, query the current transaction status, and recover from source to destination if needed.

1. Query the current status of the transaction to be in one of the states above.
2. Recover from source to destination if needed.
The only required parameter is:
1. `sourceTxHash`: The hash of the transaction that needs to be unblocked

Additional optional parameters are:
1. `txLogIndex`: Unique identifier for internal transactions.
1. `txEventIndex`: Used to confirm events on the network.
1. `evmWalletDetails`: Wallet that will sign the transaction.
1. `messageId`: Id used to recover transactions for GMP transactions from Cosmos source chains
```ts
const sourceTxHash = "0x..";
const provider = new ethers.providers.JsonRpcProvider(
"https://goerli.infura.io/v3/projectId"
"https://sepolia.infura.io/v3/projectId"
);

// Optional
// By default, The sdk uses `window.ethereum` wallet as a sender wallet e.g. MetaMask.
// This option allows caller to pass `privateKey` or `provider` to the sdk directly
const senderOptions = { privateKey: "0x", provider };


const response = await sdk.manualRelayToDestChain(
sourceTxHash,
senderOptions /* can be skipped */
senderOptions, /* can be skipped */
);
```

Possible response values are:

```ts
export interface ApproveGatewayResponse {
export interface GMPRecoveryResponse {
success: boolean;
error?: ApproveGatewayError | string;
confirmTx?: AxelarTxResponse;
createPendingTransferTx?: AxelarTxResponse;
signCommandTx?: AxelarTxResponse;
routeMessageTx?: AxelarTxResponse;
approveTx?: any;
infoLogs?: string[];
}
```

If `success == false` in the response above, there are two options to remediate (below).
If `success == false`, you can either execute the transaction manually or increase the gas payment.

### Execute manually OR increase gas payment
## Manually execute a transaction

#### 1. Execute manually
When invoking this method, you will manually execute and pay for the executable method on your specified contract on the destination chain of your cross-chain transaction.

The only required parameter is
1. `sourceTxHash`: The hash of the transaction that needs to be unblocked

Additional optional parameters are:
1. `srcTxLogIndex`: The log index of the transaction on the source chain.
1. `evmWalletDetails`: The wallet details to use for executing the transaction.

When invoking this method, you will manually execute (and pay for) the executable method on your specified contract on the destination chain of your cross-chain transaction.

```ts
const sourceTxHash = "0x..";
const provider = new ethers.providers.JsonRpcProvider(
"https://goerli.infura.io/v3/projectId"
"https://sepolia.infura.io/v3/projectId"
);

// Optional
Expand All @@ -137,13 +184,19 @@ Possible response values are:
}
```

#### 2. Increase gas payment
## Increase gas payment

Call `addNativeGas()` to increase the gas payment using the source chain's native token. The amount to be added will be automatically calculated based on factors such as the token price of the source and destination chains and the current gas price at the destination chain. This can be overridden by specifying the amount in the `options`.

There are two different functions to increase gas payment depending on type of the token.
The only required parameter is
1. `chain`: Source chain needing extra gas
1. `sourceTxHash`: The hash of the transaction that needs to be unblocked
1. `estimatedGasUsed`: Estimated gas used

An additional optional parameter is:
1. `options`: Consists of [AddGasOptions](https://github.com/axelarnetwork/axelarjs-sdk/blob/main/src/libs/types/index.ts#L133) to specify additional options

##### 2.1 Native gas payment

Invoking this method will execute the `addNativeGas` method on the Gas Receiver contract on the source chain of your cross-chain transaction to increase the amount of the gas payment, in the source chain native token. The amount to be added is automatically calculated based on many factors e.g. token price of the destination chain, token price of the source chain, current gas price at the destination chain, etc. However, it can be overridden by specifying amount in the `options`.

```ts
import {
Expand All @@ -164,6 +217,7 @@ const txHash: string = "0x...";
const { success, transaction, error } = await api.addNativeGas(
EvmChain.AVALANCHE,
txHash,
estimatedGasUsed,
options
);

Expand All @@ -183,60 +237,3 @@ Possible response values are:
error: string | undefined
}
```

##### 2.2 ERC-20 Gas Payment

This is similar to native gas payment except using **ERC-20 token** for gas payment. However, the supported ERC-20 tokens are limited. See the list of supported tokens here: [[Mainnet](/resources/contract-addresses/mainnet/) | [Testnet](/resources/contract-addresses/testnet/)]

```ts
import {
AxelarGMPRecoveryAPI,
Environment,
AddGasOptions,
EvmChain,
GAS_RECEIVER,
} from "@axelar-network/axelarjs-sdk";
import { ethers } from "ethers";

// Optional
const options: AddGasOptions = {
amount: "10000000", // The amount of gas to be added. If not specified, the sdk will calculate the amount to be paid.
refundAddress: "", // The address to get refunded gas. If not specified, the default value is the tx sender address.
estimatedGasUsed: 700000, // An amount of gas to execute `executeWithToken` or `execute` function of the custom destination contract. If not specified, the default value is 700000.
evmWalletDetails: { useWindowEthereum: true, privateKey: "0x" }, // A wallet to send an `addNativeGas` transaction. If not specified, the default value is { useWindowEthereum: true}.
};

const environment = Environment.TESTNET; // Can be `Environment.TESTNET` or `Environment.MAINNET`
const api = new AxelarGMPRecoveryAPI({ environment });

// Approve gas token to the Gas Receiver contract
const gasToken = "0xGasTokenAddress";
const erc20 = new ethers.Contract(gasToken, erc20Abi, gasPayer);
await erc20
.approve(GAS_RECEIVER[environment][EvmChain.AVALANCHE], amount)
.then((tx) => tx.wait());

// Send `addGas` transaction
const { success, transaction, error } = await api.addGas(
EvmChain.AVALANCHE,
"0xSourceTxHash",
gasToken,
options
);

if (success) {
console.log("Added gas tx:", transaction?.transactionHash);
} else {
console.log("Cannot add gas", error);
}
```

Possible response values are:

```ts
{
success: "success" | "failed",
data: ethers.ContractReceipt | undefined,
error: string | undefined
}
```

0 comments on commit 582a634

Please sign in to comment.