Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/consts/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const isDevMode = process?.env?.NODE_ENV === 'development';
Comment thread
jmrossy marked this conversation as resolved.
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;
Expand Down
1 change: 1 addition & 0 deletions src/features/messages/MessageDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
isStatusFetching={isDeliveryStatusFetching}
isPiMsg={message.isPiMsg}
blur={blur}
message={message}
/>
{!message.isPiMsg && <TimelineCard message={message} blur={blur} />}
<ContentDetailsCard message={message} blur={blur} />
Expand Down
19 changes: 1 addition & 18 deletions src/features/messages/cards/GasDetailsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import BigNumber from 'bignumber.js';
import { utils } from 'ethers';
import Image from 'next/image';
import { useMemo, useState } from 'react';

Expand All @@ -10,11 +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';

interface Props {
Expand Down Expand Up @@ -165,21 +163,6 @@ function IgpPaymentsTable({ payments }: { payments: Array<GasPayment & { contrac
);
}

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;
}
}

const style = {
th: 'p-1 md:p-2 text-sm text-gray-500 font-normal text-left border border-gray-200 rounded',
td: 'p-1 md:p-2 text-xs md:text-sm text-gray-700 text-left border border-gray-200 rounded',
Expand Down
60 changes: 54 additions & 6 deletions src/features/messages/cards/TransactionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { BigNumber as BigNumEth } from 'ethers';
import { PropsWithChildren, ReactNode, useState } from 'react';

import { toast } from 'react-toastify';
import { Spinner } from '../../../components/animations/Spinner';
import { ChainLogo } from '../../../components/icons/ChainLogo';
import { HelpIcon } from '../../../components/icons/HelpIcon';
import { Card } from '../../../components/layout/Card';
import { Modal } from '../../../components/layout/Modal';
import { links } from '../../../consts/links';
import { MessageStatus, MessageTx } from '../../../types';
import { Message, MessageStatus, MessageTx, SimulateBody } from '../../../types';
import { getDateTimeString, getHumanReadableTimeString } from '../../../utils/time';
import { getChainDisplayName } from '../../chains/utils';
import { debugStatusToDesc } from '../../debugger/strings';
import { MessageDebugResult } from '../../debugger/types';
import { useMultiProvider } from '../../providers/multiProvider';

import { computeAvgGasPrice } from '../utils';
import { LabelAndCodeBlock } from './CodeBlock';
import { KeyValueRow } from './KeyValueRow';

Expand Down Expand Up @@ -40,6 +41,7 @@ export function DestinationTransactionCard({
isStatusFetching,
isPiMsg,
blur,
message
}: {
chainId: ChainId;
status: MessageStatus;
Expand All @@ -48,6 +50,7 @@ export function DestinationTransactionCard({
isStatusFetching: boolean;
isPiMsg?: boolean;
blur: boolean;
message:Message;
}) {
let content: ReactNode;
if (transaction) {
Expand All @@ -70,7 +73,7 @@ export function DestinationTransactionCard({
{debugResult.description}
</div>
)}
<CallDataModal debugResult={debugResult} />
<CallDataModal debugResult={debugResult} chainId={chainId} message={message} />
</DeliveryStatus>
);
} else if (status === MessageStatus.Pending) {
Expand All @@ -84,7 +87,7 @@ export function DestinationTransactionCard({
</div>
)}
<Spinner classes="my-4 scale-75" />
<CallDataModal debugResult={debugResult} />
<CallDataModal debugResult={debugResult} chainId={chainId} message={message}/>
</div>
</DeliveryStatus>
);
Expand Down Expand Up @@ -207,10 +210,19 @@ function DeliveryStatus({ children }: PropsWithChildren<unknown>) {
);
}

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');
Comment thread
jmrossy marked this conversation as resolved.
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 (
<>
<button onClick={() => setIsOpen(true)} className={`mt-5 ${styles.textLink}`}>
Expand All @@ -236,11 +248,47 @@ function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) {
</p>
<LabelAndCodeBlock label="Recipient contract address:" value={contract} />
<LabelAndCodeBlock label="Handle function input calldata:" value={handleCalldata} />
<button onClick={handleClick}
disabled={loading}
className='underline text-blue-400'
>
{buttonText}
</button>
{loading && <Spinner classes="mt-4 scale-75 self-center" />}
</div>
</Modal>
</>
);
}
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.',
Expand Down
18 changes: 18 additions & 0 deletions src/features/messages/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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);
Expand All @@ -8,3 +11,18 @@ export function serializeMessage(msg: MessageStub | Message): string | undefined
export function deserializeMessage<M extends MessageStub>(data: string | string[]): M | undefined {
return fromBase64<M>(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;
}
}
36 changes: 36 additions & 0 deletions src/pages/api/simulation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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
Comment thread
jmrossy marked this conversation as resolved.
Outdated
if(!TENDERLY_ACCESS_KEY || !TENDERLY_PROJECT || !TENDERLY_USER){
console.log("ENV not defined")
res.json(failureResult("Explorer Issues"))
return null
}
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("Could not simulate"))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change to "Error preparing Tenderly simluation"

}
}
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Comment thread
jmrossy marked this conversation as resolved.
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,
}
Loading