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

Also don't forget to check Helpers.

Last updated