Full Example
// node LTS (v18.12.1)
// consider node-fetch to replace native fetch for lower
const https = require('https');
const { ethers } = require('ethers');
const { utils } = ethers;
// Find me at: https://github.com/FastLane-Labs/auction/blob/main/contracts/abis/FastLaneAuctionHandlerAbi.json
const abi = require('./FastLaneAuctionHandlerAbi.json');
const RELAY = 'https://beta-rpc.fastlane-labs.xyz/';
const PRIVATE_KEY = '0xPrivateK3Y';
// Only used for Gas Price and Nonces
const ALCHEMY_KEY = 'alchemy-apikey';
const provider = new ethers.providers.AlchemyProvider('matic', ALCHEMY_KEY);
const signer = new ethers.Wallet(PRIVATE_KEY);
console.log(`Address: ${signer.address}`);
const fastGasPrice = 10 * 1e9; // 10 gwei
const searcherContract = '0xeA26974363EC1dBc132C99cF9A29273B17254aE3';
const auctionHandler = '0xf5DF545113DeE4DF10f8149090Aa737dDC05070a';
// Assuming as a searcher I intend to use _searcherCallDataBytes
// when received on my searcher contract `fastLaneCall(` callback
// to then trigger searcherContract.doMEV(uint256,string);
// See: Searcher Call Data section of the docs for more informations
// 0x2FEf5C22a63CC8FE424bd83af9D1955A5c2d9e2E contract will not use it, just an example
const searcherAbi = [`function doMEV(uint256, string)`];
const iface = new ethers.utils.Interface(searcherAbi);
// Grab bytes for doMEV(uint256, string)
const searcherCallDataBytes = iface.encodeFunctionData('doMEV', [
ethers.utils.parseEther('1.0'),
'unused'
]);
const AuctionHandlerContract = new ethers.Contract(auctionHandler, abi, signer);
async function createKairosTransaction() {
const tx = await AuctionHandlerContract.populateTransaction.submitFastBid(
fastGasPrice,
searcherContract,
searcherCallDataBytes
);
tx.chainId = 137;
const signedSearcherTx = await signer.signTransaction(tx);
console.log(
'Precomputed searcherTxHash:',
ethers.utils.keccak256(signedSearcherTx)
);
console.log(signedSearcherTx);
return signedSearcherTx;
}
const agent = new https.Agent({
keepAlive: true
});
const ping = () =>
fetch(`${RELAY}/ping`, { agent }).then((r) =>
console.log(`PONG : ${r.statusText}`)
);
const sendTransaction = (postData) =>
fetch(RELAY, {
method: 'POST',
agent,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
});
const keepWarm = async () => {
setInterval(ping, 1000 * 60 * 10);
return ping();
};
async function main() {
// Warm it up.
await keepWarm();
const kairosTx = await createKairosTransaction();
const postData = {
jsonrpc: '2.0',
method: 'pfl_addSearcherFastBid',
params: [kairosTx],
id: 1
};
console.log(postData);
// Whenever needed. Fire a transaction.
const start = +Date.now();
await sendTransaction(postData)
// See https://fastlane-labs.gitbook.io/polygon-fastlane/reference/relay-json-rpc-api
// as response can still have a .error field
.then(async (response) => {
const elapsed = +Date.now() - start;
console.log('Success:', response.statusText, elapsed);
const json = await response.json();
console.log(json);
if (json.error) throw json.error.message;
})
.catch((error) => {
console.error('Error:', error);
});
}
const getAuthor = async (blockNum) => {
const response = await fetch(
`https://polygon-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: 1,
jsonrpc: '2.0',
method: 'bor_getAuthor',
params: ['0x' + blockNum.toString(16)]
})
}
);
const json = await response.json();
return json.result;
};
let waitBlock = 0;
provider.on('block', async (block) => {
try {
console.log(`Block: ${block}`);
if (block < waitBlock) return;
const author = await getAuthor(block);
console.log(author);
const validators = [
'0x127685D6dD6683085Da4B6a041eFcef1681E5C9C',
'0x02f70172f7f490653665c9bfac0666147c8af1f5',
'0xd2e4bE547B21E99A668f81EEcF0298471A19808f',
'0xb9EDE6f94D192073D8eaF85f8db677133d483249',
'0x9eaD03F7136Fc6b4bDb0780B00a1c14aE5A8B6d0'
].map((x) => x.toLowerCase());
if (author && validators.includes(author.toLowerCase())) {
console.log('Validator span');
// Will shoot one demo transaction and close
await main();
console.log('Closing');
process.exit(0);
} else {
waitBlock = block + 10;
}
} catch (err) {
console.error(err);
}
});
Nodejs Version
import json
import requests
from web3 import Web3
from hexbytes import HexBytes
import eth_abi
# setup requests session
s = requests.Session()
# setup keepalive connection
s.get('https://beta-rpc.fastlane-labs.xyz/ping')
_searcherToAddress = '0xeA26974363EC1dBc132C99cF9A29273B17254aE3'
# Get the _searcherCallData
with open("searcher_abi.json") as _searcher_ABI:
searcher_ABI = json.load(_searcher_ABI)
searcher_contract = web3.eth.contract(
address=_searcherToAddress,
abi=searcher_ABI
)
searcherTx = searcher_contract.functions.doExampleStuff(
Web3.toChecksumAddress(an_example_address),
int(an_amount),
).buildTransaction()
_searcherCallData = HexBytes(searcherTx['data'])
# make sure the fast gas price is in gwei
_fastGasPrice = 10 * 10**9 # 10 gwei
# create and sign the submitFlashBid transaction
with open("FastLaneAuctionHandlerAbi.json") as _fast_lane_ABI:
fast_lane_ABI = json.load(_fast_lane_ABI)
pfl_contract = web3.eth.contract(address=PFL_CONTRACT_ADDRESS, abi=fast_lane_ABI)
unsignedSearcherFastBidTx = pfl_contract.functions.submitFastBid(
_fastGasPrice,
_searcherToAddress,
_searcherCallData
).buildTransaction()
signedSearcherFastBidTx = web3.eth.account.sign_transaction(
unsignedSearcherFastBidTx,
searcher_private_key
)
# submit the FastLane bundle to the relay
rsp = s.post('https://beta-rpc.fastlane-labs.xyz', json={
"jsonrpc": "2.0",
"method": "pfl_addSearcherFastBid",
"params": [signedSearcherFastBidTx],
"id": 1,
})
Python Version
Also don't forget to check Helpers.
Last updated