Skip to content

Commit

Permalink
Merge pull request #79 from DarkFlorist/separate-simulation-and-submi…
Browse files Browse the repository at this point in the history
…t-relays

separate simulation and submit relays
  • Loading branch information
KillariDev authored Jan 28, 2024
2 parents 6d426dd + 4b79bc0 commit 6013262
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 39 deletions.
18 changes: 10 additions & 8 deletions app/ts/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ export const Navbar = ({
}) => {
const switchNetwork = async (e: Event) => {
const elm = e.target as HTMLSelectElement
const relayEndpoint = elm.value
if (elm.value !== '1' && elm.value !== '5') return
const simulationRelayEndpoint = NETWORKS[elm.value].simulationRelay
const submissionRelayEndpoint = NETWORKS[elm.value].submissionRelay
if (!provider.value) {
appSettings.value = { ...appSettings.peek(), relayEndpoint }
appSettings.value = { ...appSettings.peek(), simulationRelayEndpoint, submissionRelayEndpoint }
} else {
provider.peek()?.provider.send('wallet_switchEthereumChain', [{ chainId: relayEndpoint === NETWORKS['1'].mevRelay ? '0x1' : '0x5' }])
provider.peek()?.provider.send('wallet_switchEthereumChain', [{ chainId: simulationRelayEndpoint === NETWORKS['1'].simulationRelay ? '0x1' : '0x5' }])
}
}

Expand All @@ -38,14 +40,14 @@ export const Navbar = ({
<span className='text-gray-500 text-md w-max flex gap-1 items-center'>
<svg width='1em' height='1em' viewBox='0 0 48 48' xmlns='http://www.w3.org/2000/svg' className='inline-block'><path fill='currentColor' d='M44 32h-2v-8a2 2 0 0 0-2-2H26v-6h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2v6H8a2 2 0 0 0-2 2v8H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-2v-6h12v6h-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-2v-6h12v6h-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2Zm-34 8H6v-4h4ZM22 8h4v4h-4Zm4 32h-4v-4h4Zm16 0h-4v-4h4Z' data-name='icons Q2'></path></svg>
<select
value={appSettings.value.relayEndpoint}
value={'1'}
onChange={switchNetwork}
className='px-2 py-1 bg-black'
>
<option value={NETWORKS['1'].mevRelay}>Ethereum</option>
<option value={NETWORKS['5'].mevRelay}>Goerli</option>
{appSettings.value.relayEndpoint !== NETWORKS['1'].mevRelay && appSettings.value.relayEndpoint !== NETWORKS['5'].mevRelay ?
<option value={appSettings.value.relayEndpoint}>Custom</option>
<option value={'1'}>Ethereum</option>
<option value={'5'}>Goerli</option>
{appSettings.value.simulationRelayEndpoint !== NETWORKS['1'].simulationRelay && appSettings.value.simulationRelayEndpoint !== NETWORKS['5'].simulationRelay ?
<option value={'custom'}>Custom</option>
: null}
</select>
</span>
Expand Down
58 changes: 41 additions & 17 deletions app/ts/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,26 @@ export const SettingsIcon = () => {
}

export const SettingsModal = ({ display, appSettings }: { display: Signal<boolean>, appSettings: Signal<AppSettings> }) => {
const relayEndpointInput = useSignal({ value: appSettings.peek().relayEndpoint, valid: true })
const simulationRelayEndpointInput = useSignal({ value: appSettings.peek().simulationRelayEndpoint, valid: true })
const submissionRelayEndpointInput = useSignal({ value: appSettings.peek().submissionRelayEndpoint, valid: true })
const priorityFeeInput = useSignal({ value: formatUnits(appSettings.peek().priorityFee, 'gwei'), valid: true })
const blocksInFutureInput = useSignal({ value: appSettings.peek().blocksInFuture.toString(10), valid: true })

const allValidInputs = useComputed(() => relayEndpointInput.value.valid && priorityFeeInput.value.valid && blocksInFutureInput.value.valid)
const allValidInputs = useComputed(() => submissionRelayEndpointInput.value.valid && simulationRelayEndpointInput.value.valid && priorityFeeInput.value.valid && blocksInFutureInput.value.valid)

function validateRelayEndpointInput(value: string) {
// https://urlregex.com/
const matchURL = value.match(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/g)
relayEndpointInput.value = { value, valid: !value || !matchURL || matchURL.length !== 1 }
// https://urlregex.com/
const matchUrl = (value: string) => value.match(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/g)

function validateSimulationRelayEndpointInput(value: string) {
const matched = matchUrl(value)
simulationRelayEndpointInput.value = { value, valid: !value || !matched || matched.length !== 1 }
}
function validateAndSetsubmissionRelayEndpointInput(value: string) {
const matched = matchUrl(value)
submissionRelayEndpointInput.value = { value, valid: !value || !matched || matched.length !== 1 }
}
function validatePriorityFeeInput(value: string) {

function validateAndSetPriorityFeeInput(value: string) {
if (!value) return priorityFeeInput.value = { value, valid: false }
try {
parseUnits(String(Number(value)), 'gwei');
Expand All @@ -58,24 +66,36 @@ export const SettingsModal = ({ display, appSettings }: { display: Signal<boolea
}
function saveSettings() {
if (allValidInputs.value) {
const newSettings: AppSettings = { relayEndpoint: relayEndpointInput.value.value, priorityFee: parseUnits(String(Number(priorityFeeInput.value.value)), 'gwei'), blocksInFuture: BigInt(blocksInFutureInput.value.value) }
const newSettings: AppSettings = { submissionRelayEndpoint: submissionRelayEndpointInput.value.value, simulationRelayEndpoint: simulationRelayEndpointInput.value.value, priorityFee: parseUnits(String(Number(priorityFeeInput.value.value)), 'gwei'), blocksInFuture: BigInt(blocksInFutureInput.value.value) }
appSettings.value = newSettings
localStorage.setItem('bouquetSettings', JSON.stringify({ priorityFee: newSettings.priorityFee.toString(), blocksInFuture: newSettings.blocksInFuture.toString(), relayEndpoint: newSettings.relayEndpoint }))
localStorage.setItem('bouquetSettings', JSON.stringify({
priorityFee: newSettings.priorityFee.toString(),
blocksInFuture: newSettings.blocksInFuture.toString(),
simulationRelayEndpoint: newSettings.simulationRelayEndpoint,
submissionRelayEndpoint: newSettings.submissionRelayEndpoint,
}))
close()
}
}
function resetSettings() {
batch(() => {
appSettings.value = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, relayEndpoint: NETWORKS['1'].mevRelay };
localStorage.setItem('bouquetSettings', JSON.stringify({ priorityFee: appSettings.value.priorityFee.toString(), blocksInFuture: appSettings.value.blocksInFuture.toString(), relayEndpoint: appSettings.value.relayEndpoint }))
relayEndpointInput.value = { value: appSettings.peek().relayEndpoint, valid: true }
appSettings.value = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, simulationRelayEndpoint: NETWORKS['1'].simulationRelay, submissionRelayEndpoint: NETWORKS['1'].submissionRelay };
localStorage.setItem('bouquetSettings', JSON.stringify({
priorityFee: appSettings.value.priorityFee.toString(),
blocksInFuture: appSettings.value.blocksInFuture.toString(),
simulationRelayEndpoint: appSettings.value.simulationRelayEndpoint,
submissionRelayEndpoint: appSettings.value.submissionRelayEndpoint,
}))
simulationRelayEndpointInput.value = { value: appSettings.peek().simulationRelayEndpoint, valid: true }
submissionRelayEndpointInput.value = { value: appSettings.peek().submissionRelayEndpoint, valid: true }
priorityFeeInput.value = { value: formatUnits(appSettings.peek().priorityFee, 'gwei'), valid: true }
blocksInFutureInput.value = { value: appSettings.peek().blocksInFuture.toString(10), valid: true }
})
}
function close() {
batch(() => {
relayEndpointInput.value = { value: appSettings.peek().relayEndpoint, valid: true }
simulationRelayEndpointInput.value = { value: appSettings.peek().simulationRelayEndpoint, valid: true }
submissionRelayEndpointInput.value = { value: appSettings.peek().submissionRelayEndpoint, valid: true }
priorityFeeInput.value = { value: formatUnits(appSettings.peek().priorityFee, 'gwei'), valid: true }
blocksInFutureInput.value = { value: appSettings.peek().blocksInFuture.toString(10), valid: true }
display.value = false
Expand All @@ -85,13 +105,17 @@ export const SettingsModal = ({ display, appSettings }: { display: Signal<boolea
<div onClick={close} className='bg-white/10 w-full h-full inset-0 fixed p-4 flex flex-col items-center md:pt-24'>
<div class='h-max px-8 py-4 w-full max-w-xl flex flex-col gap-4 bg-black' onClick={(e) => e.stopPropagation()}>
<h2 className='text-xl font-semibold'>App Settings</h2>
<div className={`flex flex-col justify-center border h-16 outline-none px-4 focus-within:bg-white/5 bg-transparent ${!relayEndpointInput.value.valid ? 'border-red-400' : 'border-white/50 focus-within:border-white/80'}`}>
<span className='text-sm text-gray-500'>MEV Relay URL</span>
<input onInput={(e: JSX.TargetedEvent<HTMLInputElement>) => validateRelayEndpointInput(e.currentTarget.value)} value={relayEndpointInput.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' />
<div className={`flex flex-col justify-center border h-16 outline-none px-4 focus-within:bg-white/5 bg-transparent ${!simulationRelayEndpointInput.value.valid ? 'border-red-400' : 'border-white/50 focus-within:border-white/80'}`}>
<span className='text-sm text-gray-500'>Bundle Simulation Relay URL</span>
<input onInput={(e: JSX.TargetedEvent<HTMLInputElement>) => validateSimulationRelayEndpointInput(e.currentTarget.value)} value={simulationRelayEndpointInput.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' />
</div>
<div className={`flex flex-col justify-center border h-16 outline-none px-4 focus-within:bg-white/5 bg-transparent ${!submissionRelayEndpointInput.value.valid ? 'border-red-400' : 'border-white/50 focus-within:border-white/80'}`}>
<span className='text-sm text-gray-500'>Bundle Submit Relay URL</span>
<input onInput={(e: JSX.TargetedEvent<HTMLInputElement>) => validateAndSetsubmissionRelayEndpointInput(e.currentTarget.value)} value={submissionRelayEndpointInput.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' />
</div>
<div className={`flex flex-col justify-center border h-16 outline-none px-4 focus-within:bg-white/5 bg-transparent ${!priorityFeeInput.value.valid ? 'border-red-400' : 'border-white/50 focus-within:border-white/80'}`}>
<span className='text-sm text-gray-500'>Priority Fee (GWEI)</span>
<input onInput={(e: JSX.TargetedEvent<HTMLInputElement>) => validatePriorityFeeInput(e.currentTarget.value)} value={priorityFeeInput.value.value} type='number' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='0.1' />
<input onInput={(e: JSX.TargetedEvent<HTMLInputElement>) => validateAndSetPriorityFeeInput(e.currentTarget.value)} value={priorityFeeInput.value.value} type='number' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='0.1' />
</div>
<div className={`flex flex-col justify-center border h-16 outline-none px-4 focus-within:bg-white/5 bg-transparent ${!blocksInFutureInput.value.valid ? 'border-red-400' : 'border-white/50 focus-within:border-white/80'}`}>
<span className='text-sm text-gray-500'>Target Blocks In Future For Bundle Confirmation</span>
Expand Down
2 changes: 1 addition & 1 deletion app/ts/components/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export const Submit = ({
<div className='flex flex-col w-full gap-4'>
<div>
<p><span className='font-bold'>Gas:</span> {formatUnits(getMaxBaseFeeInFutureBlock(blockInfo.value.baseFee, appSettings.value.blocksInFuture), 'gwei')} gwei + {formatUnits(appSettings.value.priorityFee.toString(), 'gwei')} gwei priority</p>
<p><span className='font-bold'>Network:</span> {appSettings.value.relayEndpoint} (Block {blockInfo.value.blockNumber.toString()})</p>
<p><span className='font-bold'>Relays:</span> simulation:{appSettings.value.simulationRelayEndpoint}, submit:{appSettings.value.submissionRelayEndpoint} (Block {blockInfo.value.blockNumber.toString()})</p>
<p>Transactions will be attempt to be included in the block {appSettings.value.blocksInFuture.toString()} blocks from now.</p>
<p>You can edit these settings <button className='font-bold underline' onClick={() => showSettings.value = true}>here</button>.</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/ts/components/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const Transactions = ({
const requests = await Promise.all(
uniqueAddresses.map((address) =>
fetch(
`https://api${appSettings.peek().relayEndpoint === NETWORKS['5'].mevRelay ? '-goerli' : ''
`https://api${appSettings.peek().simulationRelayEndpoint === NETWORKS['5'].simulationRelay ? '-goerli' : ''
}.etherscan.io/api?module=contract&action=getsourcecode&address=${getAddress(address.toLowerCase())}&apiKey=PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8`,
),
),
Expand All @@ -73,7 +73,7 @@ export const Transactions = ({
if (contract.success == false || contract.value.status !== '1') abis.push(undefined)
else {
if (contract.value.result[0].Proxy === '1' && contract.value.result[0].Implementation !== '') {
const implReq = await fetch(`https://api${appSettings.peek().relayEndpoint === NETWORKS['5'].mevRelay ? '-goerli' : ''}.etherscan.io/api?module=contract&action=getabi&address=${addressString(contract.value.result[0].Implementation)}&apiKey=PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8`)
const implReq = await fetch(`https://api${appSettings.peek().simulationRelayEndpoint === NETWORKS['5'].simulationRelay ? '-goerli' : ''}.etherscan.io/api?module=contract&action=getabi&address=${addressString(contract.value.result[0].Implementation)}&apiKey=PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8`)
const implResult = EtherscanGetABIResult.safeParse(await implReq.json())
abis.push(implResult.success && implResult.value.status === '1' ? implResult.value.result : undefined)
} else abis.push(contract.value.result[0].ABI && contract.value.result[0].ABI !== 'Contract source code not verified' ? contract.value.result[0].ABI : undefined)
Expand Down
6 changes: 3 additions & 3 deletions app/ts/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const NETWORKS: { [chainId: string]: { mevRelay: string, blockExplorer: string, rpcUrl: string }} = {
'1': { mevRelay: 'https://relay.dark.florist', blockExplorer: 'https://etherscan.io/', rpcUrl: 'https://rpc.dark.florist/flipcardtrustone' },
'5': { mevRelay: 'https://relay-goerli.dark.florist', blockExplorer: 'https://goerli.etherscan.io/', rpcUrl: 'https://rpc-goerli.dark.florist/flipcardtrustone' }
export const NETWORKS: { [chainId: string]: { simulationRelay: string, submissionRelay: string, blockExplorer: string, rpcUrl: string }} = {
'1': { simulationRelay: 'https://relay.dark.florist', submissionRelay: 'https://rpc.titanbuilder.xyz', blockExplorer: 'https://etherscan.io/', rpcUrl: 'https://rpc.dark.florist/flipcardtrustone' },
'5': { simulationRelay: 'https://relay-goerli.dark.florist', submissionRelay: 'https://relay-goerli.dark.florist', blockExplorer: 'https://goerli.etherscan.io/', rpcUrl: 'https://rpc-goerli.dark.florist/flipcardtrustone' }
}
4 changes: 2 additions & 2 deletions app/ts/library/flashbots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export async function simulateBundle(

const payload = JSON.stringify({ jsonrpc: '2.0', method: 'eth_callBundle', params: [{ txs, blockNumber: `0x${blockInfo.blockNumber.toString(16)}`, stateBlockNumber: 'latest' }] })
const flashbotsSig = `${await provider.authSigner.getAddress()}:${await provider.authSigner.signMessage(id(payload))}`
const request = await fetch(appSettings.relayEndpoint,
const request = await fetch(appSettings.simulationRelayEndpoint,
{ method: 'POST', body: payload, headers: { 'Content-Type': 'application/json', 'X-Flashbots-Signature': flashbotsSig } }
)
const response = await request.json()
Expand Down Expand Up @@ -107,7 +107,7 @@ export async function sendBundle(bundle: Bundle, targetBlock: bigint, fundingAmo

const payload = JSON.stringify({ jsonrpc: '2.0', method: 'eth_sendBundle', params: [{ txs, blockNumber: `0x${targetBlock.toString(16)}`, revertingTxHashes: [] }] })
const flashbotsSig = `${await provider.authSigner.getAddress()}:${await provider.authSigner.signMessage(id(payload))}`
const request = await fetch(appSettings.relayEndpoint,
const request = await fetch(appSettings.submissionRelayEndpoint,
{ method: 'POST', body: payload, headers: { 'Content-Type': 'application/json', 'X-Flashbots-Signature': flashbotsSig } }
)
const response = await request.json()
Expand Down
14 changes: 11 additions & 3 deletions app/ts/library/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ const addProvider = async (
if (!parsedAddress.success) throw new Error('Provider provided invalid address!')

if (![1n, 5n].includes(network.chainId)) {
await provider.send('wallet_switchEthereumChain', [{ chainId: appSettings.peek().relayEndpoint === NETWORKS['1'].mevRelay ? '0x1' : '0x5' }])
await provider.send('wallet_switchEthereumChain', [{ chainId: appSettings.peek().simulationRelayEndpoint === NETWORKS['1'].simulationRelay ? '0x1' : '0x5' }])
} else {
appSettings.value = { ...appSettings.peek(), relayEndpoint: network.chainId === 1n ? NETWORKS['1'].mevRelay : NETWORKS['5'].mevRelay }
appSettings.value = {
...appSettings.peek(),
simulationRelayEndpoint: network.chainId === 1n ? NETWORKS['1'].simulationRelay : NETWORKS['5'].simulationRelay,
submissionRelayEndpoint: network.chainId === 1n ? NETWORKS['1'].submissionRelay : NETWORKS['5'].submissionRelay,
}
}

store.value = {
Expand Down Expand Up @@ -89,7 +93,11 @@ export const connectBrowserProvider = async (
const chainChangedCallback = async (chainId: string) => {
if ([1n, 5n].includes(BigInt(chainId))) {
batch(() => {
appSettings.value = { ...appSettings.peek(), relayEndpoint: BigInt(chainId) === 1n ? NETWORKS['1'].mevRelay : NETWORKS['5'].mevRelay }
appSettings.value = {
...appSettings.peek(),
simulationRelayEndpoint: BigInt(chainId) === 1n ? NETWORKS['1'].simulationRelay : NETWORKS['5'].simulationRelay,
submissionRelayEndpoint: BigInt(chainId) === 1n ? NETWORKS['1'].submissionRelay : NETWORKS['5'].submissionRelay
}
store.value = store.value ? { ...store.value, chainId: BigInt(chainId) } : undefined
})
} else {
Expand Down
5 changes: 3 additions & 2 deletions app/ts/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ function fetchBundleFromStorage(): Bundle | undefined {
}

function fetchSettingsFromStorage() {
const defaultValues: AppSettings = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, relayEndpoint: NETWORKS['1'].mevRelay };
const defaultValues: AppSettings = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, simulationRelayEndpoint: NETWORKS['1'].simulationRelay, submissionRelayEndpoint: NETWORKS['1'].submissionRelay };
const custom = localStorage.getItem('bouquetSettings')
if (!custom) {
return defaultValues
} else {
try {
const parsed = JSON.parse(custom)
if ('relayEndpoint' in parsed) defaultValues.relayEndpoint = parsed.relayEndpoint
if ('simulationRelayEndpoint' in parsed) defaultValues.simulationRelayEndpoint = parsed.simulationRelayEndpoint
if ('submissionRelayEndpoint' in parsed) defaultValues.submissionRelayEndpoint = parsed.submissionRelayEndpoint
if ('priorityFee' in parsed) defaultValues.priorityFee = BigInt(parsed.priorityFee)
if ('blocksInFuture' in parsed) defaultValues.blocksInFuture = BigInt(parsed.blocksInFuture)
return defaultValues
Expand Down
2 changes: 1 addition & 1 deletion app/ts/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ declare global {

export type BlockInfo = { blockNumber: bigint; baseFee: bigint; priorityFee: bigint }
export type Bundle = { transactions: TransactionList; containsFundingTx: boolean; totalGas: bigint; inputValue: bigint; uniqueSigners: string[] }
export type AppSettings = { blocksInFuture: bigint; priorityFee: bigint; relayEndpoint: string }
export type AppSettings = { blocksInFuture: bigint; priorityFee: bigint; simulationRelayEndpoint: string; submissionRelayEndpoint: string }
export type Signers = { burner: Wallet | undefined; burnerBalance: bigint; bundleSigners: { [account: string]: Wallet } }

export type PromiseState = 'pending' | 'resolved' | 'rejected'
Expand Down

0 comments on commit 6013262

Please sign in to comment.