diff --git a/src/consts/config.ts b/src/consts/config.ts
index cd1e65d..82f6157 100644
--- a/src/consts/config.ts
+++ b/src/consts/config.ts
@@ -1,6 +1,9 @@
const isDevMode = process?.env?.NODE_ENV === 'development';
const version = process?.env?.NEXT_PUBLIC_VERSION ?? null;
const explorerApiKeys = JSON.parse(process?.env?.EXPLORER_API_KEYS || '{}');
+export const TENDERLY_USER = process?.env?.TENDERLY_USER;
+export const TENDERLY_PROJECT = process?.env?.TENDERLY_PROJECT;
+export const TENDERLY_ACCESS_KEY = process?.env?.TENDERLY_ACCESS_KEY;
interface Config {
debug: boolean;
diff --git a/src/features/messages/MessageDetails.tsx b/src/features/messages/MessageDetails.tsx
index 422f917..a54d813 100644
--- a/src/features/messages/MessageDetails.tsx
+++ b/src/features/messages/MessageDetails.tsx
@@ -119,6 +119,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
isStatusFetching={isDeliveryStatusFetching}
isPiMsg={message.isPiMsg}
blur={blur}
+ message={message}
/>
{!message.isPiMsg && }
diff --git a/src/features/messages/cards/GasDetailsCard.tsx b/src/features/messages/cards/GasDetailsCard.tsx
index 84e293e..7eb8656 100644
--- a/src/features/messages/cards/GasDetailsCard.tsx
+++ b/src/features/messages/cards/GasDetailsCard.tsx
@@ -1,5 +1,4 @@
import BigNumber from 'bignumber.js';
-import { utils } from 'ethers';
import Image from 'next/image';
import { useMemo, useState } from 'react';
@@ -10,10 +9,10 @@ import { links } from '../../../consts/links';
import FuelPump from '../../../images/icons/fuel-pump.svg';
import { Message } from '../../../types';
import { BigNumberMax, fromWei } from '../../../utils/amount';
-import { logger } from '../../../utils/logger';
import { toTitleCase } from '../../../utils/string';
import { GasPayment } from '../../debugger/types';
import { useMultiProvider } from '../../providers/multiProvider';
+import { computeAvgGasPrice } from '../utils';
import { KeyValueRow } from './KeyValueRow';
@@ -165,21 +164,6 @@ function IgpPaymentsTable({ payments }: { payments: Array
)}
-
+
);
} else if (status === MessageStatus.Pending) {
@@ -84,7 +89,7 @@ export function DestinationTransactionCard({
)}
-
+
);
@@ -207,10 +212,27 @@ function DeliveryStatus({ children }: PropsWithChildren) {
);
}
-function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) {
+function CallDataModal({
+ debugResult,
+ chainId,
+ message,
+}: {
+ debugResult?: MessageDebugResult;
+ chainId: ChainId;
+ message: Message;
+}) {
const [isOpen, setIsOpen] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [buttonText, setButtonText] = useState('Simulate call with Tenderly');
if (!debugResult?.calldataDetails) return null;
const { contract, handleCalldata } = debugResult.calldataDetails;
+ const handleClick = async () => {
+ setButtonText('Simulating');
+ setLoading(true);
+ await simulateCall({ contract, handleCalldata, chainId, message });
+ setButtonText('Simulate call with Tenderly');
+ setLoading(false); //using !loading is not setting the states properly and the state stays true
+ };
return (
<>
+
+ {loading && }
>
);
}
+async function simulateCall({
+ contract,
+ handleCalldata,
+ chainId,
+ message,
+}: {
+ contract: string;
+ handleCalldata: string;
+ chainId: ChainId;
+ message: Message;
+}) {
+ const gasPrice = computeAvgGasPrice('wei', message.totalGasAmount, message.totalPayment);
+ const data: SimulateBody = {
+ save: true,
+ save_if_fails: true,
+ simulation_type: 'full',
+ network_id: chainId,
+ from: '0x0000000000000000000000000000000000000000', //can be any address, doesn't matter
+ to: contract,
+ input: handleCalldata,
+ gas: BigNumEth.from(message.totalGasAmount).toNumber(),
+ gas_price: Number(gasPrice?.wei),
+ value: 0,
+ };
+ const resp = await fetch(`/api/simulation`, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+ const respMessage = await resp.json();
+ if (respMessage.success === true) {
+ const simulationId = respMessage.data;
+ window.open(`https://dashboard.tenderly.co/shared/simulation/${simulationId}`);
+ } else {
+ toast.error(respMessage.error);
+ }
+}
const helpText = {
origin: 'Info about the transaction that initiated the message placement into the outbox.',
diff --git a/src/features/messages/utils.ts b/src/features/messages/utils.ts
index 108fd51..7cb921f 100644
--- a/src/features/messages/utils.ts
+++ b/src/features/messages/utils.ts
@@ -1,5 +1,9 @@
+import { BigNumber } from 'bignumber.js';
+import { utils } from 'ethers/lib/ethers';
+
import { Message, MessageStub } from '../../types';
import { fromBase64, toBase64 } from '../../utils/base64';
+import { logger } from '../../utils/logger';
export function serializeMessage(msg: MessageStub | Message): string | undefined {
return toBase64(msg);
@@ -8,3 +12,22 @@ export function serializeMessage(msg: MessageStub | Message): string | undefined
export function deserializeMessage(data: string | string[]): M | undefined {
return fromBase64(data);
}
+
+export function computeAvgGasPrice(
+ unit: string,
+ gasAmount?: BigNumber.Value,
+ payment?: BigNumber.Value,
+) {
+ try {
+ if (!gasAmount || !payment) return null;
+ const gasBN = new BigNumber(gasAmount);
+ const paymentBN = new BigNumber(payment);
+ if (gasBN.isZero() || paymentBN.isZero()) return null;
+ const wei = paymentBN.div(gasAmount).toFixed(0);
+ const formatted = utils.formatUnits(wei, unit).toString();
+ return { wei, formatted };
+ } catch (error) {
+ logger.debug('Error computing avg gas price', error);
+ return null;
+ }
+}
diff --git a/src/pages/api/simulation.ts b/src/pages/api/simulation.ts
new file mode 100644
index 0000000..b9f9df0
--- /dev/null
+++ b/src/pages/api/simulation.ts
@@ -0,0 +1,35 @@
+import { TENDERLY_ACCESS_KEY, TENDERLY_PROJECT, TENDERLY_USER } from '../../consts/config';
+import { failureResult, successResult } from '../../features/api/utils';
+
+export default async function handler(req, res) {
+ const data = req.body;
+ if (!TENDERLY_ACCESS_KEY || !TENDERLY_PROJECT || !TENDERLY_USER) {
+ console.log('ENV not defined');
+ res.json(failureResult('Explorer Issues'));
+ }
+ try {
+ const resp = await fetch(
+ `https://api.tenderly.co/api/v1/account/${TENDERLY_USER}/project/${TENDERLY_PROJECT}/simulate`,
+ {
+ method: 'POST',
+ body: data,
+ headers: {
+ 'X-Access-Key': TENDERLY_ACCESS_KEY as string,
+ },
+ },
+ );
+ const simulationId = await resp.json().then((data) => data.simulation.id);
+ await fetch(
+ `https://api.tenderly.co/api/v1/account/${TENDERLY_USER}/project/${TENDERLY_PROJECT}/simulations/${simulationId}/share`,
+ {
+ method: 'POST',
+ headers: {
+ 'X-Access-Key': TENDERLY_ACCESS_KEY as string,
+ },
+ },
+ );
+ res.json(successResult(simulationId));
+ } catch (error) {
+ res.json(failureResult('Error preparing Tenderly simulation'));
+ }
+}
diff --git a/src/types.ts b/src/types.ts
index d9979a1..f350667 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -60,3 +60,17 @@ export interface ExtendedLog extends providers.Log {
from?: Address;
to?: Address;
}
+
+// Type of body for tenderly POST requests https://docs.tenderly.co/simulations-and-forks/simulation-api/using-simulation-api
+export interface SimulateBody {
+ save: boolean;
+ save_if_fails: boolean;
+ simulation_type: string;
+ network_id: ChainId;
+ from: Address; //can be any address, doesn't matter
+ to: Address;
+ input: string;
+ gas: number;
+ gas_price: number | null;
+ value: number;
+}