Skip to content

Commit

Permalink
Merge pull request #72 from DarkFlorist/withdraw-rpcs
Browse files Browse the repository at this point in the history
Withdraw rpcs
  • Loading branch information
0xjimmy authored Sep 14, 2023
2 parents 2dc2c8b + 9ece422 commit aaa2b14
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 41 deletions.
65 changes: 49 additions & 16 deletions app/ts/components/ConfigureFunding.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { batch, ReadonlySignal, Signal, useComputed, useSignal, useSignalEffect } from '@preact/signals'
import { EtherSymbol, formatEther, getAddress, id, Wallet } from 'ethers'
import { EtherSymbol, formatEther, getAddress, JsonRpcProvider, Wallet } from 'ethers'
import { JSX } from 'preact/jsx-runtime'
import { NETWORKS } from '../constants.js'
import { useAsyncState } from '../library/asyncState.js'
import { getMaxBaseFeeInFutureBlock, signBundle } from '../library/bundleUtils.js'
import { getMaxBaseFeeInFutureBlock } from '../library/bundleUtils.js'
import { ProviderStore } from '../library/provider.js'
import { addressString } from '../library/utils.js'
import { EthereumAddress } from '../types/ethereumTypes.js'
Expand Down Expand Up @@ -116,7 +117,7 @@ export const ConfigureFunding = ({
)
}

const WithdrawModal = ({ display, blockInfo, signers, appSettings, provider }: { display: Signal<boolean>, provider: Signal<ProviderStore | undefined>, appSettings: Signal<AppSettings>, signers: Signal<Signers>, blockInfo: Signal<BlockInfo> }) => {
const WithdrawModal = ({ display, blockInfo, signers, provider }: { display: Signal<boolean>, provider: Signal<ProviderStore | undefined>, signers: Signal<Signers>, blockInfo: Signal<BlockInfo> }) => {
if (!display.value) return null

const recipientAddress = useSignal<{ input: string, address?: EthereumAddress }>({ input: '' })
Expand All @@ -127,31 +128,63 @@ const WithdrawModal = ({ display, blockInfo, signers, appSettings, provider }: {
}

const withdrawAmount = useComputed(() => {
let fee = (blockInfo.value.baseFee + blockInfo.value.priorityFee) * 11n / 10n * 21000n
let maxFeePerGas = getMaxBaseFeeInFutureBlock(blockInfo.value.baseFee, 5n) + blockInfo.value.priorityFee;
let fee = maxFeePerGas * 21000n
let amount = signers.value.burnerBalance - fee
return { amount, fee }
return { amount, fee, maxFeePerGas }
})

const { value: signedMessage, waitFor } = useAsyncState<string>()

// Default check if we know the network, can also switch to true if sending to known RPC fails
const useBrowserProvider = useSignal<boolean>(provider.value && !(provider.value.chainId.toString(10) in NETWORKS) ? true : false)

const blockExplorer = useComputed<string | undefined>(() => {
if (provider.value) {
const chainId = provider.value.chainId.toString(10)
return chainId in NETWORKS ? NETWORKS[chainId].blockExplorer : undefined

Check failure on line 145 in app/ts/components/ConfigureFunding.tsx

View workflow job for this annotation

GitHub Actions / unit-tests

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly '1': { readonly mevRelay: "https://relay.dark.florist"; readonly blockExplorer: "https://etherscan.io/"; readonly rpcUrl: "https://rpc.dark.florist/flipcardtrustone"; }; readonly '5': { ...; }; }'.
}
return undefined
})

function withdraw() {
waitFor(async () => {
if (withdrawAmount.value.amount <= 0n) throw 'Funding account\'s balance is to small to withdraw'
if (!signers.value.burner) throw 'No funding account found'
if (!provider.value) throw 'User not connected'
if (!appSettings.value.relayEndpoint) throw 'No Flashbots RPC provided'
if (!recipientAddress.value.address) throw 'No recipient provided'

const [tx] = await signBundle([{ signer: signers.value.burner, transaction: { chainId: provider.value.chainId, from: signers.value.burner.address, to: addressString(recipientAddress.value.address), value: withdrawAmount.value.amount, gasLimit: 21000, type: 2 }}], provider.value.provider, blockInfo.value, getMaxBaseFeeInFutureBlock(blockInfo.value.baseFee, 10n))
const payload = JSON.stringify({ jsonrpc: '2.0', method: 'eth_sendPrivateTransaction', params: [{ tx }] })
const flashbotsSig = `${await provider.value.authSigner.getAddress()}:${await provider.value.authSigner.signMessage(id(payload))}`
const request = await fetch(appSettings.value.relayEndpoint, { method: 'POST', body: payload, headers: { 'Content-Type': 'application/json', 'X-Flashbots-Signature': flashbotsSig } })
const response = await request.json()
if (response.error !== undefined && response.error !== null) {
throw Error(response.error.message)
// Worst case scenario, attempt to send via browser wallet if no NETWORK config for chainId or previous error sending to known RPC
if (useBrowserProvider.value === true) {
try {
const burnerWithBrowserProvider = signers.value.burner.connect(provider.value.provider)
const txInput = await burnerWithBrowserProvider.populateTransaction({ chainId: provider.value.chainId, from: signers.value.burner.address, to: addressString(recipientAddress.value.address), gasLimit: 21000, type: 2, value: withdrawAmount.value.amount, maxFeePerGas: withdrawAmount.value.maxFeePerGas })
const tx = await burnerWithBrowserProvider.signTransaction(txInput)
const txHash = await provider.value.provider.send('eth_sendRawTransaction', [tx])
return txHash as string
} catch (error) {
throw error
}
}

// If user is on network that is in NETWORK, send via ethRpc
const chainId = provider.value.chainId.toString(10)
if (!(chainId in NETWORKS)) {
useBrowserProvider.value = true
throw 'Unknown network! If you have Interceptor installed and simulation mode on please switch to signing mode and try again.'
}

const fundingWithProvider = signers.value.burner.connect(new JsonRpcProvider(NETWORKS[chainId].rpcUrl))

Check failure on line 177 in app/ts/components/ConfigureFunding.tsx

View workflow job for this annotation

GitHub Actions / unit-tests

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly '1': { readonly mevRelay: "https://relay.dark.florist"; readonly blockExplorer: "https://etherscan.io/"; readonly rpcUrl: "https://rpc.dark.florist/flipcardtrustone"; }; readonly '5': { ...; }; }'.
try {
const tx = await fundingWithProvider.sendTransaction({ chainId: provider.value.chainId, from: signers.value.burner.address, to: addressString(recipientAddress.value.address), gasLimit: 21000, type: 2, value: withdrawAmount.value.amount, maxFeePerGas: withdrawAmount.value.maxFeePerGas })
fundingWithProvider.provider?.destroy()
return tx.hash
} catch (error) {
console.warn('Error sending burner withdraw tx to known RPC:', error)
fundingWithProvider.provider?.destroy()
useBrowserProvider.value = true
throw 'Unknown network! If you have Interceptor installed and simulation mode on please switch to signing mode and try again.'
}
if ('result' in response && typeof response.result === 'string') return response.result
else throw 'Flashbots RPC returned invalid data'
})
}

Expand All @@ -178,7 +211,7 @@ const WithdrawModal = ({ display, blockInfo, signers, appSettings, provider }: {
<Button onClick={withdraw} variant='primary'>Withdraw</Button>
</div>
<p>{signedMessage.value.state === 'rejected' ? <SingleNotice variant='error' description={signedMessage.value.error.message} title='Error Withdrawing' /> : ''}</p>
<p>{signedMessage.value.state === 'resolved' ? <SingleNotice variant='success' description={`Transaction submitted with TX Hash ${signedMessage.value.value}`} title='Transaction Submitted' /> : ''}</p>
<p>{signedMessage.value.state === 'resolved' ? <SingleNotice variant='success' description={blockExplorer.value ? <span>Transaction submitted with TX Hash <a className='hover:underline' href={`${blockExplorer.value}tx/${signedMessage.value.value}`} target='_blank'>{signedMessage.value.value}</a></span> : <span>Transaction submitted with TX Hash {signedMessage.value.value}</span>} title='Transaction Submitted' /> : ''}</p>
</div>
</div>
)
Expand Down
10 changes: 5 additions & 5 deletions app/ts/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Signal, useComputed, useSignal } from '@preact/signals'
import { MEV_RELAY_GOERLI, MEV_RELAY_MAINNET } from '../constants.js'
import { NETWORKS } from '../constants.js'
import { ProviderStore } from '../library/provider.js'
import { EthereumAddress } from '../types/ethereumTypes.js'
import { AppSettings, serialize } from '../types/types.js'
Expand All @@ -19,7 +19,7 @@ export const Navbar = ({
if (!provider.value) {
appSettings.value = { ...appSettings.peek(), relayEndpoint }
} else {
provider.peek()?.provider.send('wallet_switchEthereumChain', [{ chainId: relayEndpoint === MEV_RELAY_MAINNET ? '0x1' : '0x5' }])
provider.peek()?.provider.send('wallet_switchEthereumChain', [{ chainId: relayEndpoint === NETWORKS['1'].mevRelay ? '0x1' : '0x5' }])
}
}

Expand All @@ -42,9 +42,9 @@ export const Navbar = ({
onChange={switchNetwork}
className='px-2 py-1 bg-black'
>
<option value={MEV_RELAY_MAINNET}>Ethereum</option>
<option value={MEV_RELAY_GOERLI}>Goerli</option>
{appSettings.value.relayEndpoint !== MEV_RELAY_MAINNET && appSettings.value.relayEndpoint !== MEV_RELAY_GOERLI ?
<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>
: null}
</select>
Expand Down
4 changes: 2 additions & 2 deletions app/ts/components/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { batch, Signal, useComputed, useSignal } from '@preact/signals'
import { formatUnits, parseUnits } from 'ethers'
import { JSX } from 'preact/jsx-runtime'
import { MEV_RELAY_MAINNET } from '../constants.js'
import { NETWORKS } from '../constants.js'
import { AppSettings } from '../types/types.js'
import { Button } from './Button.js'

Expand Down Expand Up @@ -66,7 +66,7 @@ export const SettingsModal = ({ display, appSettings }: { display: Signal<boolea
}
function resetSettings() {
batch(() => {
appSettings.value = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, relayEndpoint: MEV_RELAY_MAINNET };
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 }
priorityFeeInput.value = { value: formatUnits(appSettings.peek().priorityFee, 'gwei'), valid: true }
Expand Down
4 changes: 2 additions & 2 deletions app/ts/components/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SettingsModal } from './Settings.js'
import { useAsyncState, AsyncProperty } from '../library/asyncState.js'
import { simulateBundle, sendBundle, checkBundleInclusion, RelayResponseError, SimulationResponseSuccess } from '../library/flashbots.js'
import { SingleNotice } from './Warns.js'
import { BLOCK_EXPLORERS } from '../constants.js'
import { NETWORKS } from '../constants.js'

type PendingBundle = {
bundles: {
Expand Down Expand Up @@ -81,7 +81,7 @@ export const Bundles = ({
if (outstandingBundles.value.error) return <SingleNotice variant='error' title='Error Sending Bundle' description={<p class='font-medium w-full break-all'>{outstandingBundles.value.error.message}</p>} />

const chainIdString = provider.value ? provider.value.chainId.toString(10) : '-1'
const blockExplorerBaseUrl = BLOCK_EXPLORERS[chainIdString] ?? null
const blockExplorerBaseUrl = NETWORKS[chainIdString].blockExplorer ?? null

Check failure on line 84 in app/ts/components/Submit.tsx

View workflow job for this annotation

GitHub Actions / unit-tests

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly '1': { readonly mevRelay: "https://relay.dark.florist"; readonly blockExplorer: "https://etherscan.io/"; readonly rpcUrl: "https://rpc.dark.florist/flipcardtrustone"; }; readonly '5': { ...; }; }'.

return (
<div class='flex flex-col-reverse gap-4'>
Expand Down
6 changes: 3 additions & 3 deletions app/ts/components/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReadonlySignal, Signal, useSignal, useSignalEffect } from '@preact/sign
import { EtherSymbol, formatEther, getAddress, Interface, parseEther, TransactionDescription } from 'ethers'
import { JSXInternal } from 'preact/src/jsx.js'
import { AppSettings, BlockInfo, Bundle, serialize, Signers } from '../types/types.js'
import { MEV_RELAY_GOERLI } from '../constants.js'
import { NETWORKS } from '../constants.js'
import { ProviderStore } from '../library/provider.js'
import { Button } from './Button.js'
import { useAsyncState } from '../library/asyncState.js'
Expand Down Expand Up @@ -60,7 +60,7 @@ export const Transactions = ({
const requests = await Promise.all(
uniqueAddresses.map((address) =>
fetch(
`https://api${appSettings.peek().relayEndpoint === MEV_RELAY_GOERLI ? '-goerli' : ''
`https://api${appSettings.peek().relayEndpoint === NETWORKS['5'].mevRelay ? '-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 === MEV_RELAY_GOERLI ? '-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().relayEndpoint === NETWORKS['5'].mevRelay ? '-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
11 changes: 4 additions & 7 deletions app/ts/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
export const MEV_RELAY_MAINNET = 'https://relay.dark.florist/'
export const MEV_RELAY_GOERLI = 'https://relay-goerli.dark.florist/'

export const BLOCK_EXPLORERS: { [chainId: string]: string } = {
'1': 'https://etherscan.io/',
'5': 'https://goerli.etherscan.io/'
}
export const NETWORKS = {
'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' }
} as const
8 changes: 4 additions & 4 deletions app/ts/library/provider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { batch, Signal } from '@preact/signals'
import { Block, BrowserProvider, getAddress, HDNodeWallet, Wallet } from 'ethers'
import { MEV_RELAY_GOERLI, MEV_RELAY_MAINNET } from '../constants.js'
import { NETWORKS } from '../constants.js'
import { AddressParser, EthereumAddress } from '../types/ethereumTypes.js'
import { AppSettings, BlockInfo, Signers } from '../types/types.js'

Expand Down Expand Up @@ -28,9 +28,9 @@ 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 === MEV_RELAY_MAINNET ? '0x1' : '0x5' }])
await provider.send('wallet_switchEthereumChain', [{ chainId: appSettings.peek().relayEndpoint === NETWORKS['1'].mevRelay ? '0x1' : '0x5' }])
} else {
appSettings.value = { ...appSettings.peek(), relayEndpoint: network.chainId === 1n ? MEV_RELAY_MAINNET : MEV_RELAY_GOERLI }
appSettings.value = { ...appSettings.peek(), relayEndpoint: network.chainId === 1n ? NETWORKS['1'].mevRelay : NETWORKS['5'].mevRelay }
}

store.value = {
Expand Down Expand Up @@ -89,7 +89,7 @@ export const connectBrowserProvider = async (
const chainChangedCallback = async (chainId: string) => {
if ([1n, 5n].includes(BigInt(chainId))) {
batch(() => {
appSettings.value = { ...appSettings.peek(), relayEndpoint: BigInt(chainId) === 1n ? MEV_RELAY_MAINNET : MEV_RELAY_GOERLI }
appSettings.value = { ...appSettings.peek(), relayEndpoint: BigInt(chainId) === 1n ? NETWORKS['1'].mevRelay : NETWORKS['5'].mevRelay }
store.value = store.value ? { ...store.value, chainId: BigInt(chainId) } : undefined
})
} else {
Expand Down
4 changes: 2 additions & 2 deletions app/ts/stores.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useComputed, useSignal } from '@preact/signals'
import { Wallet } from 'ethers'
import { MEV_RELAY_MAINNET } from './constants.js'
import { NETWORKS } from './constants.js'
import { getMaxBaseFeeInFutureBlock } from './library/bundleUtils.js'
import { EthereumAddress } from './types/ethereumTypes.js'
import { ProviderStore } from './library/provider.js'
Expand Down Expand Up @@ -38,7 +38,7 @@ function fetchBundleFromStorage(): Bundle | undefined {
}

function fetchSettingsFromStorage() {
const defaultValues: AppSettings = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, relayEndpoint: MEV_RELAY_MAINNET };
const defaultValues: AppSettings = { blocksInFuture: 3n, priorityFee: 10n ** 9n * 3n, relayEndpoint: NETWORKS['1'].mevRelay };
const custom = localStorage.getItem('bouquetSettings')
if (!custom) {
return defaultValues
Expand Down

0 comments on commit aaa2b14

Please sign in to comment.