diff --git a/app/ts/components/Navbar.tsx b/app/ts/components/Navbar.tsx index e2f08bb..f5a6143 100644 --- a/app/ts/components/Navbar.tsx +++ b/app/ts/components/Navbar.tsx @@ -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' }]) } } @@ -38,14 +40,14 @@ export const Navbar = ({ diff --git a/app/ts/components/Settings.tsx b/app/ts/components/Settings.tsx index aa5837a..dfe6f8f 100644 --- a/app/ts/components/Settings.tsx +++ b/app/ts/components/Settings.tsx @@ -27,18 +27,26 @@ export const SettingsIcon = () => { } export const SettingsModal = ({ display, appSettings }: { display: Signal, appSettings: Signal }) => { - 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'); @@ -58,24 +66,36 @@ export const SettingsModal = ({ display, appSettings }: { display: Signal { - 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 @@ -85,13 +105,17 @@ export const SettingsModal = ({ display, appSettings }: { display: Signal
e.stopPropagation()}>

App Settings

-
- MEV Relay URL - ) => validateRelayEndpointInput(e.currentTarget.value)} value={relayEndpointInput.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' /> +
+ Bundle Simulation Relay URL + ) => validateSimulationRelayEndpointInput(e.currentTarget.value)} value={simulationRelayEndpointInput.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' /> +
+
+ Bundle Submit Relay URL + ) => validateAndSetsubmissionRelayEndpointInput(e.currentTarget.value)} value={submissionRelayEndpointInput.value.value} type='text' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='https://' />
Priority Fee (GWEI) - ) => validatePriorityFeeInput(e.currentTarget.value)} value={priorityFeeInput.value.value} type='number' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='0.1' /> + ) => validateAndSetPriorityFeeInput(e.currentTarget.value)} value={priorityFeeInput.value.value} type='number' className='bg-transparent outline-none placeholder:text-gray-600' placeholder='0.1' />
Target Blocks In Future For Bundle Confirmation diff --git a/app/ts/components/Submit.tsx b/app/ts/components/Submit.tsx index 927e571..87754b9 100644 --- a/app/ts/components/Submit.tsx +++ b/app/ts/components/Submit.tsx @@ -269,7 +269,7 @@ export const Submit = ({

Gas: {formatUnits(getMaxBaseFeeInFutureBlock(blockInfo.value.baseFee, appSettings.value.blocksInFuture), 'gwei')} gwei + {formatUnits(appSettings.value.priorityFee.toString(), 'gwei')} gwei priority

-

Network: {appSettings.value.relayEndpoint} (Block {blockInfo.value.blockNumber.toString()})

+

Relays: simulation:{appSettings.value.simulationRelayEndpoint}, submit:{appSettings.value.submissionRelayEndpoint} (Block {blockInfo.value.blockNumber.toString()})

Transactions will be attempt to be included in the block {appSettings.value.blocksInFuture.toString()} blocks from now.

You can edit these settings .

diff --git a/app/ts/components/Transactions.tsx b/app/ts/components/Transactions.tsx index 44c3aae..5300177 100644 --- a/app/ts/components/Transactions.tsx +++ b/app/ts/components/Transactions.tsx @@ -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`, ), ), @@ -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) diff --git a/app/ts/constants.ts b/app/ts/constants.ts index cbd00f9..ca3b083 100644 --- a/app/ts/constants.ts +++ b/app/ts/constants.ts @@ -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' } } diff --git a/app/ts/library/flashbots.ts b/app/ts/library/flashbots.ts index d1c3fcf..141d19f 100644 --- a/app/ts/library/flashbots.ts +++ b/app/ts/library/flashbots.ts @@ -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() @@ -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() diff --git a/app/ts/library/provider.ts b/app/ts/library/provider.ts index 4c624b8..50b27f1 100644 --- a/app/ts/library/provider.ts +++ b/app/ts/library/provider.ts @@ -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 = { @@ -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 { diff --git a/app/ts/stores.ts b/app/ts/stores.ts index cd99946..163f759 100644 --- a/app/ts/stores.ts +++ b/app/ts/stores.ts @@ -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 diff --git a/app/ts/types/types.ts b/app/ts/types/types.ts index aec13e9..7f9a3aa 100644 --- a/app/ts/types/types.ts +++ b/app/ts/types/types.ts @@ -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'