Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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';
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
73 changes: 67 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 } from 'bignumber.js';
import { BigNumber as BigNumEth } from 'ethers';
import { PropsWithChildren, ReactNode, useState } from 'react';

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 { logger } from '../../../utils/logger';
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 { 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,20 @@ 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');
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 +249,59 @@ 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}){

Copy link
Contributor

Choose a reason for hiding this comment

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

Wrap the function contents here in a try/catch and show an error message with toast.error on failure

Copy link
Author

Choose a reason for hiding this comment

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

Didn't wrap this in try/catch, rather wrapped the backend call which returns failure call and checking if the call is success or failure here.


Copy link
Contributor

Choose a reason for hiding this comment

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

Spacing? Please run yarn prettier and the files will get auto-formatted :)

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(computeAvgGasPrice("wei",message.totalGasAmount,message.totalPayment)),
value: 0,
}
const resp=await fetch(
`/api/simulation`,{
method:'POST',
body:JSON.stringify(data),
}
)

const simulationId=await resp.json().then((data)=>data.data)
window.open(`https://dashboard.tenderly.co/shared/simulation/${simulationId}`)

}

function computeAvgGasPrice(unit: string, gasAmount?: BigNumber.Value, payment?: BigNumber.Value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you duplicate this function here? Please de-dupe with the copy in GasDetailsCard and move it to messages/utils.ts

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);
return wei;
} catch (error) {
logger.debug('Error computing avg gas price', error);
return null;
}
}


const helpText = {
origin: 'Info about the transaction that initiated the message placement into the outbox.',
Expand Down
31 changes: 31 additions & 0 deletions src/pages/api/simulation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TENDERLY_ACCESS_KEY, TENDERLY_PROJECT, TENDERLY_USER } from "../../consts/config"
import { 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")
return null
}
const resp = await fetch(
`https://api.tenderly.co/api/v1/account/${TENDERLY_USER}/project/${TENDERLY_PROJECT}/simulate`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Check that these env vars are defined and throw a helpful here at the top if they're not

{
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))
}
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,16 @@ export interface ExtendedLog extends providers.Log {
from?: Address;
to?: Address;
}

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,
}
Loading