Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mevblocker tutorial #37

Merged
merged 9 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Connect to RPC
---

In this tutorial we are going to add the MEV Blocker RPC into your browsers Web3 provider (Metamask, Rabby, etc.) and switch to it.

Connecting to MEV Blocker is as as simple as setting the right URL to connect to the RPC.

Different endpoints with different protections are available:

| | Frontrunning | Backrunning | Revert |
| --------------- | ----------------- | ----------- | ---------------- |
| /fast (default) | ✅ Protected | 💰 Refund | ❌ Not Protected |
| /noreverts | ✅ Protected | 💰 Refund | ✅ Protected |
| /fullprivacy | ✅ Max protection | ⦰ No refund | ✅ Protected |

Additionally, there are a few request parameters you can set to fine-tune the behavior of the RPC:

- `refundRecipient`: Which address should receive searcher rebates (defaults to target `tx.origin`)
- `softcancel`: Whether 0 value transactions to self should be broadcasted or interpreted as cancellation of other pending transactions at that nonce. Not available in `fast` endpoint.
- `referrer`: Allows for order-flow attribution

Depending on your choice you may end up with a URL like:

```
https://rpc.mevblocker.io?refundRecipient=0x76bA9825A5F707F133124E4608F1F2Dd1EF4006a&softcancel=1&referrer=learn.cow.fi
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Web3Provider } from '@ethersproject/providers';

export async function run(provider: Web3Provider): Promise<unknown> {
const networkConfig = {
chainId: '0x1', // Mainnet
chainName: 'MEV Blocker',
nativeCurrency: {
name: 'ETH',
symbol: 'ETH',
decimals: 18
},
rpcUrls: [
/* IMPLEMENT */
],
blockExplorerUrls: ['https://etherscan.io']
};

return await provider.send('wallet_addEthereumChain', [networkConfig]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Web3Provider } from '@ethersproject/providers';

export async function run(provider: Web3Provider): Promise<unknown> {
const networkConfig = {
chainId: '0x1', // Mainnet
chainName: 'MEV Blocker',
nativeCurrency: {
name: 'ETH',
symbol: 'ETH',
decimals: 18
},
rpcUrls: [
'https://rpc.mevblocker.io?refundRecipient=0x76bA9825A5F707F133124E4608F1F2Dd1EF4006a&softcancel=1&referrer=learn.cow.fi'
],
blockExplorerUrls: ['https://etherscan.io']
};

return await provider.send('wallet_addEthereumChain', [networkConfig]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Sending Transactions
---

Now that we are connected to MEVBlocker, we can send MEV protected transactions.

Sending transactions works exactly like with any other RPC, using the `eth_sendRawTransaction` method.
Since we are using the injected provider here, we are going to use `signer.sendTransaction` which will create a signature request using your browser's Web3 provider.

### Craft the transaction

For demonstration purposes we are simply going to send 0.01 ETH to ourselves.
For this, we need to craft the transaction object containing our address as `to` and 0.01ETH as `value`:

```typescript
/// file: run.ts
const tx = {
to: await signer.getAddress(),
value: utils.parseEther('0.01')
};
```

### Send the transaction

To request signature and submission of the transaction object through your injected web3 provider simply call

```typescript
/// file: run.ts
const transactionResponse = await signer.sendTransaction(tx);
```

You can access and log the pending transaction hash (open the Developer Console to see the logs) via `transactionResponse.hash`.

### Wait for the transaction to be included

It may take a few seconds for the transaction to be confirmed. We can await the response object and once included return the transactions hash

```typescript
/// file: run.ts
await transactionResponse.wait();
return transactionResponse.hash;
```

To view your transaction you can either load it via the MEV Blocker status endpoint (`https://rpc.mevblocker.io/tx/<tx_hash>`) or once mined on Etherscan (`https://etherscan.io/tx/<tx_hash>`)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { utils } from 'ethers';
import type { Web3Provider } from '@ethersproject/providers';

export async function run(provider: Web3Provider): Promise<unknown> {
const signer = provider.getSigner();

// IMPLEMENT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { utils } from 'ethers';
import type { Web3Provider } from '@ethersproject/providers';

export async function run(provider: Web3Provider): Promise<unknown> {
const signer = provider.getSigner();

const tx = {
to: await signer.getAddress(),
value: utils.parseEther('0.01')
};

// Send the transaction
const transactionResponse = await signer.sendTransaction(tx);
console.log(`Transaction sent! https://rpc.mevblocker.io/tx/${transactionResponse.hash}`);

// Wait for the transaction to be included
await transactionResponse.wait();
console.log('Transaction confirmed');

return `https://etherscan.io/tx/${transactionResponse.hash}`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Cancelling transactions
---

If you want to cancel a pending MEV Blocker transaction without paying gas, you need to be connected to either the `noreverts` or `fullprivacy` endpoint and enable the `softcancel` feature flag (cf. [previous tutorial](/tutorial/connect-mevblocker)).

Even when soft cancellations are disabled, you can cancel a transaction by sending another transaction with equal nonce but higher priority fee.
However, this will cost you transaction fees.

Soft cancellations are an optimistic feature where MEV Blocker will stop broadcasting your transaction and thus may prevent it from being included if it's not yet too late.

### Preparing the transaction we want to cancel

To increase our chances of our target transaction not getting immediately included we will submit it with no priority fee:

```typescript
/// file: run.ts
const nonce = await signer.getTransactionCount();
const tx = {
to: await signer.getAddress(),
value: utils.parseEther('0.01'),
maxPriorityFeePerGas: 0,
maxFeePerGas: utils.parseUnits('100', 'gwei'),
nonce
};
```

### Preparing the cancellation tx

Cancellation transactions are calls to `self` without any value or calldata, that use the same nonce as the target transaction in order to invalidate it:

```typescript
/// file: run.ts
const cancellation = {
to: await signer.getAddress(),
nonce
};
```

MEV Blocker doesn't broadcast such transactions but instead takes it as a signal to stop broadcasting any other transactions that have the same nonce allowing for "gasless" cancellations.

### Sending and awaiting target and cancellation

We need to send both transactions in short concession in order for the cancellation to take effect.
There is always a chance that due to latency the cancellation arrives too late and builders end up including the target transaction.
It might therefore take multiple attempts to demonstrate the exact behavior.

> Metamask may sometimes ask you to sign the second transaction first. Make sure you sign the transactions with non-zero value first!

To sign both transactions using the same popup we await for both `sendTransaction` calls in parallel and return the target transaction hash to observe its status via the API:

```typescript
/// file: run.ts
const [transactionResponse, _] = await Promise.all([
signer.sendTransaction(tx),
signer.sendTransaction(cancellation)
]);
return `Cancellation sent! Check https://rpc.mevblocker.io/tx/${transactionResponse.hash}`;
```

If all goes well the target transaction should show its status as `FAILED`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { utils } from 'ethers';
import type { Web3Provider } from '@ethersproject/providers';

export async function run(provider: Web3Provider): Promise<unknown> {
const signer = provider.getSigner();

const tx = {
to: await signer.getAddress(),
value: utils.parseEther('0.01')
};

// Implement cancellation

const [transactionResponse] = await Promise.all([signer.sendTransaction(tx)]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { utils } from 'ethers';
import type { Web3Provider } from '@ethersproject/providers';

export async function run(provider: Web3Provider): Promise<unknown> {
const signer = provider.getSigner();

const nonce = await signer.getTransactionCount();
const tx = {
to: await signer.getAddress(),
value: utils.parseEther('0.01'),
nonce
};

const cancellation = {
to: await signer.getAddress(),
nonce
};

const [transactionResponse, _] = await Promise.all([
signer.sendTransaction(tx),
signer.sendTransaction(cancellation)
]);
return `Cancellation sent! Check https://rpc.mevblocker.io/tx/${transactionResponse.hash}`;
}
8 changes: 8 additions & 0 deletions content/tutorial/05-mevblocker/01-users/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"title": "Users",
"scope": {
"prefix": "/src/lib/",
"name": "src"
},
"focus": "/src/lib/run.ts"
}
8 changes: 8 additions & 0 deletions content/tutorial/05-mevblocker/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"title": "MEV Blocker",
"scope": {
"prefix": "/src/lib/",
"name": "src"
},
"focus": "/src/lib/run.ts"
}
10 changes: 10 additions & 0 deletions content/tutorial/05-mevblocker/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"../common/node_modules/*"
]
}
}
}
Loading