<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Chuck Bergeron - Generation Software</title>
        <link>https://paragraph.com/@chuck-bergeron-generation-software</link>
        <description>Father. @PoolTogether_ co-founder. 3D Art, Programming &amp; Design</description>
        <lastBuildDate>Fri, 10 Apr 2026 04:36:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Chuck Bergeron - Generation Software</title>
            <url>https://storage.googleapis.com/papyrus_images/61c681fc772847befadeb5fa4b2f224114049ee82ae5b7c26ad95947060606fa.png</url>
            <link>https://paragraph.com/@chuck-bergeron-generation-software</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Part 3. Creating a PoolTogether Draw Auction bot]]></title>
            <link>https://paragraph.com/@chuck-bergeron-generation-software/part-3-creating-a-pooltogether-draw-auction-bot</link>
            <guid>eNspfU4XVIrAmCHSl3K3</guid>
            <pubDate>Tue, 19 Sep 2023 23:35:46 GMT</pubDate>
            <description><![CDATA[Tutorial duration: ~25 minutes UPDATE: This is the old method of running bots. There is a lot of good information in these blog posts but they may differ from the new bot code. See the Cabana docs for the latest on G9’s PoolTogether bots: https://docs.cabana.fi/protocol/botsWhat is the Draw Auction bot?The hyperstructure — PoolTogether’s latest protocol update introduces a new way to ensure the protocol lasts for generations to come. Built-in to the protocol is the ability for you to make pro...]]></description>
            <content:encoded><![CDATA[<p><strong><em>Tutorial duration:</em></strong> ~25 minutes</p><p><strong>UPDATE:</strong> This is the old method of running bots. There is a lot of good information in these blog posts but they may differ from the new bot code. See the Cabana docs for the latest on G9’s PoolTogether bots:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.cabana.fi/protocol/bots">https://docs.cabana.fi/protocol/bots</a></p><h2 id="h-what-is-the-draw-auction-bot" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is the Draw Auction bot?</h2><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://jacob.energy/hyperstructures.html"><strong>hyperstructure</strong></a> — PoolTogether’s latest protocol update introduces a new way to ensure the protocol lasts for generations to come. Built-in to the protocol is the ability for you to make profit by running core functions necessary for PoolTogether to operate day-to-day.</p><p>We’ve learned about the Arbitrage Liquidator bot in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/ES-IJduktYPb0X_sBikfqL-PVFRweNpoPrlr01zcVX8"><strong>Part 1</strong></a>, and the Prize Claiming bot in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/xPSEh1pfjV2IT1yswcsjN2gBBrVf548V8q9W23xxA8U"><strong>Part 2</strong></a>. This tutorial is about the Draw Auction bot, which does two things:</p><ol><li><p>Requests the RNG (random number generator) to create a random number — used to determine the prizes — every day, and</p></li><li><p>Bridges the provably fair and verifiable random number obtained in step 1 to the various chain’s prize pools.</p></li></ol><p>Similar to the Prize Claiming in Part 2 of this tutorial series, the Draw Auctions use a gradual dutch auction to ramp up the rewards you can earn in exchange for assisting with the RNG flow over time. More info on how the auction works behind the scenes can be found in the docs here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dev.pooltogether.com/protocol/next/design/draw-auction"><strong>https://dev.pooltogether.com/protocol/next/design/draw-auction</strong></a></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/37f3449bb975f823129c923b9fab257ed1989b7152bee14be7e9430c7ed9602a.gif" alt="A gradual dutch auction determines the rewards you can earn from the draw auctions." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A gradual dutch auction determines the rewards you can earn from the draw auctions.</figcaption></figure><p>This tutorial will go over the steps required to create profitable RNG Request and Relay (bridge) transactions.</p><p>—</p><p><strong><em>Note:</em></strong> We have created a bot (which runs on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.openzeppelin.com/defender"><strong>OpenZeppelin Defender</strong></a>, but you can use whichever cron/relay service you like) which you can use as a reference here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/draw-auction"><strong>https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/draw-auction</strong></a></p><p>These tutorials expect you have intermediate knowledge of programming concepts and JavaScript/TypeScript. We’ll use <code>ethers.js</code>, but you could write these bots using <code>web3.js</code>, <code>Web3.py</code>, etc.</p><p>Also, there is a JavaScript utils library for interacting with the <code>PrizePool</code> and associated contracts here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-utils-js"><strong>https://github.com/GenerationSoftware/pt-v5-utils-js</strong></a></p><h2 id="h-rng-service" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">RNG Service</h2><p>The RNG service used in Step 1 is currently set to <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/vrf/v2/introduction"><strong>Chainlink’s VRF</strong></a>, however any other random number source (such as RANDAO) could be plugged in at a later date.</p><h2 id="h-steps" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Steps:</h2><ol><li><p><strong>Create a main() function and get setup</strong></p></li><li><p><strong>Gather information about the state of the RNG and Relay Auctions</strong></p></li><li><p><strong>Compare RNG service fee and gas costs against the current reward you could earn to determine profitability</strong></p></li><li><p><strong>Execute transaction (when profitable) to start the RNG request or relay (bridge) the number to other chain’s prize pools</strong></p></li></ol><p><em>Caveat:</em> The term relay in this tutorial is referencing the <code>RngRelayAuction</code> contract and function, not the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.openzeppelin.com/defender/v1/relay"><strong>OpenZeppelin Defender Relayer</strong></a> unless otherwise stated.</p><h2 id="h-1-create-a-main-and-do-setup" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">1. Create a main() and do setup</h2><p>Working with the RNG and Relay contracts requires us to initialize a bunch of ethers.js Contract objects, as well as work across multiple chains.</p><p>During the first <code>RngAuction</code>phase we will need to run <code>startRngRequest</code> to get a random number. The RNG currently uses Chainlink’s VRF on the Ethereum L1 chain. Therefore, the <code>RngAuction</code>, the <code>RngAuctionRelayerRemoteOwner</code>and the <code>ChainlinkVRFV2DirectRngAuctionHelper</code>contracts all live on Ethereum.</p><p>In the second phase <code>RngRelayAuction</code>, the random number needs to be broadcast from the L1 (Ethereum) to the prize pool on the L2 (currently Optimism, but could be a number of prize pools on various L2 chains). This is also initiated on the L1 using the <code>RngAuctionRelayerRemoteOwner</code> contract.</p><p>We will use the following contracts to query data from for finding out the state of the auctions: <code>PrizePoolContract</code>, <code>RngAuctionContract</code>, and <code>RngRelayAuctionContract</code>.</p><p>As well, we’ll need these two helper contracts for sending transactions: <code>ChainlinkVRFV2DirectRngAuctionHelper</code> and <code>RngAuctionRelayerRemoteOwner</code>.</p><p>These contract objects are all collected in the code below:</p><pre data-type="codeBlock" text="import { ethers, Contract } from &apos;ethers&apos;;
import { Provider } from &apos;@ethersproject/providers&apos;;
import {
  ContractsBlob,
  downloadContractsBlob,
  getContract,
} from &apos;@generationsoftware/pt-v5-utils-js&apos;;

export interface AuctionContracts {
  prizePoolContract: Contract;
  chainlinkVRFV2DirectRngAuctionHelperContract: Contract;
  remoteOwnerContract: Contract;
  rngAuctionContract: Contract;
  rngRelayAuctionContract: Contract;
  rngAuctionRelayerRemoteOwnerContract: Contract;
}

const CONTRACTS_VERSION = {
  major: 1,
  minor: 0,
  patch: 0,
};

const ERC_5164_MESSAGE_DISPATCHER_ADDRESS = {
  1: &apos;0xa8f85bab964d7e6be938b54bf4b29a247a88cd9d&apos;, // mainnet -&gt; optimism
};

const RNG_CHAIN_ID = 1; // eth mainnet
const RELAY_CHAIN_ID = 10; // optimism
const RNG_JSON_RPC_URI = &apos;&apos;; // your Alchemy, Infura, etc. RPC URI for target RNG chain
const RELAY_JSON_RPC_URI = &apos;&apos;; // your Alchemy, Infura, etc. RPC URI for target RELAY chain

const REWARD_RECIPIENT = &apos;&apos; // address the rewards should be sent to

const main = async () =&gt; {
  const helpers = await getHelpers();
  const context = await getDrawAuctionContext(helpers);

  if (!context.state) {
    console.warn(`Currently no Rng or RngRelay auctions to complete. Exiting ...`);
    return;
  }

  if (context.state === DrawAuctionState.RngStartVrfHelper) {
    await checkBalance(context);
    await increaseRngFeeAllowance(signer, context, helpers.auctionContracts);
  }

  // Get estimate for gas cost ...

  const profitable = await calculateProfit(gasCostUsd, context);

  if (profitable) {
    // We can simply pass the rngRelayer and rngReadProvider here since
    // both transactions we create will be on the L1
    await sendTransaction(rngRelayer, auctionContracts, context);
  }
};
main();

const getHelpers = async () =&gt; {
  const rngReadProvider = new ethers.providers.JsonRpcProvider(RNG_JSON_RPC_URI, RNG_CHAIN_ID);
  const relayReadProvider = new ethers.providers.JsonRpcProvider(
    RELAY_JSON_RPC_URI,
    RELAY_CHAIN_ID,
  );

  const rngContracts = await downloadContractsBlob(RNG_CHAIN_ID);
  const relayContracts = await downloadContractsBlob(RELAY_CHAIN_ID);

  const auctionContracts = getAuctionContracts(
    rngReadProvider,
    relayReadProvider,
    rngContracts,
    relayContracts,
  );

  return { auctionContracts, rngReadProvider, relayReadProvider };
};

const getAuctionContracts = (
  rngReadProvider: Provider,
  relayReadProvider: Provider,
  rngContractsBlob: ContractsBlob,
  relayContractsBlob: ContractsBlob,
): AuctionContracts =&gt; {
  const rngContracts = getRngContracts(rngReadProvider, rngContractsBlob);
  const relayContracts = getRelayContracts(relayReadProvider, relayContractsBlob);

  return {
    ...rngContracts,
    ...relayContracts,
  };
};

const getRngContracts = (provider: Provider, contracts: ContractsBlob) =&gt; {
  const chainId = RNG_CHAIN_ID;
  const version = CONTRACTS_VERSION;

  const rngAuctionContract = getContract(&apos;RngAuction&apos;, chainId, provider, contracts, version);
  const chainlinkVRFV2DirectRngAuctionHelperContract = getContract(
    &apos;ChainlinkVRFV2DirectRngAuctionHelper&apos;,
    chainId,
    provider,
    contracts,
    version,
  );
  const rngAuctionRelayerRemoteOwnerContract = getContract(
    &apos;RngAuctionRelayerRemoteOwner&apos;,
    chainId,
    provider,
    contracts,
    version,
  );

  return {
    chainlinkVRFV2DirectRngAuctionHelperContract,
    rngAuctionContract,
    rngAuctionRelayerRemoteOwnerContract,
  };
};

const getRelayContracts = (provider: Provider, contracts: ContractsBlob) =&gt; {
  const chainId = RELAY_CHAIN_ID;
  const version = CONTRACTS_VERSION;

  const prizePoolContract = getContract(&apos;PrizePool&apos;, chainId, provider, contracts, version);
  const rngRelayAuctionContract = getContract(
    &apos;RngRelayAuction&apos;,
    chainId,
    provider,
    contracts,
    version,
  );
  const remoteOwnerContract = getContract(&apos;RemoteOwner&apos;, chainId, provider, contracts, version);

  return {
    prizePoolContract,
    remoteOwnerContract,
    rngRelayAuctionContract,
  };
};
"><code>import { ethers, Contract } from 'ethers'<span class="hljs-comment">;</span>
import { Provider } from '@ethersproject/providers'<span class="hljs-comment">;</span>
import {
  ContractsBlob,
  downloadContractsBlob,
  getContract,
} from '@generationsoftware/pt-v5-utils-js'<span class="hljs-comment">;</span>

export interface AuctionContracts {
  prizePoolContract: Contract<span class="hljs-comment">;</span>
  chainlinkVRFV2DirectRngAuctionHelperContract: Contract<span class="hljs-comment">;</span>
  remoteOwnerContract: Contract<span class="hljs-comment">;</span>
  rngAuctionContract: Contract<span class="hljs-comment">;</span>
  rngRelayAuctionContract: Contract<span class="hljs-comment">;</span>
  rngAuctionRelayerRemoteOwnerContract: Contract<span class="hljs-comment">;</span>
}

const <span class="hljs-attr">CONTRACTS_VERSION</span> = {
  major: 1,
  minor: 0,
  patch: 0,
}<span class="hljs-comment">;</span>

const <span class="hljs-attr">ERC_5164_MESSAGE_DISPATCHER_ADDRESS</span> = {
  1: '0xa8f85bab964d7e6be938b54bf4b29a247a88cd9d', // mainnet -> optimism
}<span class="hljs-comment">;</span>

const <span class="hljs-attr">RNG_CHAIN_ID</span> = <span class="hljs-number">1</span><span class="hljs-comment">; // eth mainnet</span>
const <span class="hljs-attr">RELAY_CHAIN_ID</span> = <span class="hljs-number">10</span><span class="hljs-comment">; // optimism</span>
const <span class="hljs-attr">RNG_JSON_RPC_URI</span> = <span class="hljs-string">''</span><span class="hljs-comment">; // your Alchemy, Infura, etc. RPC URI for target RNG chain</span>
const <span class="hljs-attr">RELAY_JSON_RPC_URI</span> = <span class="hljs-string">''</span><span class="hljs-comment">; // your Alchemy, Infura, etc. RPC URI for target RELAY chain</span>

const <span class="hljs-attr">REWARD_RECIPIENT</span> = <span class="hljs-string">''</span> // address the rewards should be sent to

const <span class="hljs-attr">main</span> = async () => {
  const <span class="hljs-attr">helpers</span> = await getHelpers()<span class="hljs-comment">;</span>
  const <span class="hljs-attr">context</span> = await getDrawAuctionContext(helpers)<span class="hljs-comment">;</span>

  if (!context.state) {
    console.warn(`Currently no Rng or RngRelay auctions to complete. Exiting ...`)<span class="hljs-comment">;</span>
    return<span class="hljs-comment">;</span>
  }

  if (<span class="hljs-attr">context.state</span> === DrawAuctionState.RngStartVrfHelper) {
    await checkBalance(context)<span class="hljs-comment">;</span>
    await increaseRngFeeAllowance(signer, context, helpers.auctionContracts)<span class="hljs-comment">;</span>
  }

  // Get estimate for gas cost ...

  const <span class="hljs-attr">profitable</span> = await calculateProfit(gasCostUsd, context)<span class="hljs-comment">;</span>

  if (profitable) {
    // We can simply pass the rngRelayer and rngReadProvider here since
    // both transactions we create will be on the L1
    await sendTransaction(rngRelayer, auctionContracts, context)<span class="hljs-comment">;</span>
  }
}<span class="hljs-comment">;</span>
main()<span class="hljs-comment">;</span>

const <span class="hljs-attr">getHelpers</span> = async () => {
  const <span class="hljs-attr">rngReadProvider</span> = new ethers.providers.JsonRpcProvider(RNG_JSON_RPC_URI, RNG_CHAIN_ID)<span class="hljs-comment">;</span>
  const <span class="hljs-attr">relayReadProvider</span> = new ethers.providers.JsonRpcProvider(
    RELAY_JSON_RPC_URI,
    RELAY_CHAIN_ID,
  )<span class="hljs-comment">;</span>

  const <span class="hljs-attr">rngContracts</span> = await downloadContractsBlob(RNG_CHAIN_ID)<span class="hljs-comment">;</span>
  const <span class="hljs-attr">relayContracts</span> = await downloadContractsBlob(RELAY_CHAIN_ID)<span class="hljs-comment">;</span>

  const <span class="hljs-attr">auctionContracts</span> = getAuctionContracts(
    rngReadProvider,
    relayReadProvider,
    rngContracts,
    relayContracts,
  )<span class="hljs-comment">;</span>

  return { auctionContracts, rngReadProvider, relayReadProvider }<span class="hljs-comment">;</span>
}<span class="hljs-comment">;</span>

const <span class="hljs-attr">getAuctionContracts</span> = (
  rngReadProvider: Provider,
  relayReadProvider: Provider,
  rngContractsBlob: ContractsBlob,
  relayContractsBlob: ContractsBlob,
): <span class="hljs-attr">AuctionContracts</span> => {
  const <span class="hljs-attr">rngContracts</span> = getRngContracts(rngReadProvider, rngContractsBlob)<span class="hljs-comment">;</span>
  const <span class="hljs-attr">relayContracts</span> = getRelayContracts(relayReadProvider, relayContractsBlob)<span class="hljs-comment">;</span>

  return {
    ...rngContracts,
    ...relayContracts,
  }<span class="hljs-comment">;</span>
}<span class="hljs-comment">;</span>

const <span class="hljs-attr">getRngContracts</span> = (provider: Provider, contracts: ContractsBlob) => {
  const <span class="hljs-attr">chainId</span> = RNG_CHAIN_ID<span class="hljs-comment">;</span>
  const <span class="hljs-attr">version</span> = CONTRACTS_VERSION<span class="hljs-comment">;</span>

  const <span class="hljs-attr">rngAuctionContract</span> = getContract(<span class="hljs-string">'RngAuction'</span>, chainId, provider, contracts, version)<span class="hljs-comment">;</span>
  const <span class="hljs-attr">chainlinkVRFV2DirectRngAuctionHelperContract</span> = getContract(
    'ChainlinkVRFV2DirectRngAuctionHelper',
    chainId,
    provider,
    contracts,
    version,
  )<span class="hljs-comment">;</span>
  const <span class="hljs-attr">rngAuctionRelayerRemoteOwnerContract</span> = getContract(
    'RngAuctionRelayerRemoteOwner',
    chainId,
    provider,
    contracts,
    version,
  )<span class="hljs-comment">;</span>

  return {
    chainlinkVRFV2DirectRngAuctionHelperContract,
    rngAuctionContract,
    rngAuctionRelayerRemoteOwnerContract,
  }<span class="hljs-comment">;</span>
}<span class="hljs-comment">;</span>

const <span class="hljs-attr">getRelayContracts</span> = (provider: Provider, contracts: ContractsBlob) => {
  const <span class="hljs-attr">chainId</span> = RELAY_CHAIN_ID<span class="hljs-comment">;</span>
  const <span class="hljs-attr">version</span> = CONTRACTS_VERSION<span class="hljs-comment">;</span>

  const <span class="hljs-attr">prizePoolContract</span> = getContract(<span class="hljs-string">'PrizePool'</span>, chainId, provider, contracts, version)<span class="hljs-comment">;</span>
  const <span class="hljs-attr">rngRelayAuctionContract</span> = getContract(
    'RngRelayAuction',
    chainId,
    provider,
    contracts,
    version,
  )<span class="hljs-comment">;</span>
  const <span class="hljs-attr">remoteOwnerContract</span> = getContract(<span class="hljs-string">'RemoteOwner'</span>, chainId, provider, contracts, version)<span class="hljs-comment">;</span>

  return {
    prizePoolContract,
    remoteOwnerContract,
    rngRelayAuctionContract,
  }<span class="hljs-comment">;</span>
}<span class="hljs-comment">;</span>
</code></pre><h2 id="h-2-gather-contract-state" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">2. Gather Contract State</h2><p>Once we have our main function set up, and the various <code>AuctionContracts</code> collected we can gain intel on the current auction state.</p><p>We start with the basic get function <code>getDrawAuctionContext</code>, this will call both <code>getRng</code> and <code>getRelay</code> as well, then combine that data into the <code>context</code>object:</p><pre data-type="codeBlock" text="import { BigNumber, ethers } from &apos;ethers&apos;;
import { formatUnits } from &apos;@ethersproject/units&apos;;
import { Provider } from &apos;@ethersproject/providers&apos;;
import {
  AuctionContracts,
  DrawAuctionContext,
  DrawAuctionRelayerContext,
  RelayDrawAuctionContext,
  RngDrawAuctionContext,
  TokenWithRate,
} from &apos;@generationsoftware/pt-v5-autotasks-library&apos;;

export enum DrawAuctionState {
  RngStartVrfHelper = &apos;RngVrfHelper&apos;,
  RngRelayBridge = &apos;RngRelayBridge&apos;,
}

import { ERC20Abi } from &apos;../abis/ERC20Abi&apos;;
import { VrfRngAbi } from &apos;../abis/VrfRngAbi&apos;;

const RELAYER_ADDRESS = &apos;&apos;; // account address of your signer / relayer
const ZERO_ADDRESS = &apos;0x0000000000000000000000000000000000000000&apos;;

const RNG_FEE_TOKEN_MARKET_RATE_USD = 6.14
const REWARD_TOKEN_MARKET_RATE_USD = 0.62
const ETH_MARKET_RATE_USD = 1636.27

const getDrawAuctionContext = async (
  rngReadProvider: Provider,
  relayReadProvider: Provider,
  auctionContracts: AuctionContracts,
): Promise&lt;DrawAuctionContext&gt; =&gt; {
  const prizePoolReserve = await auctionContracts.prizePoolContract.reserve();
  const prizePoolReserveForOpenDraw = await auctionContracts.prizePoolContract.reserveForOpenDraw();
  const reserve = prizePoolReserve.add(prizePoolReserveForOpenDraw);

  const rngContext = await getRng(rngReadProvider, auctionContracts, reserve);
  const relayContext = await getRelay(relayReadProvider, auctionContracts, rngContext);

  const rngNativeTokenMarketRateUsd = await getNativeTokenMarketRateUsd(RNG_CHAIN_ID);
  const relayNativeTokenMarketRateUsd = await getNativeTokenMarketRateUsd(RELAY_CHAIN_ID);

  const rngExpectedRewardUsd = rngContext.rngExpectedReward * relayContext.rewardToken.assetRateUsd;

  const context = {
    ...rngContext,
    ...relayContext,
    rngNativeTokenMarketRateUsd,
    relayNativeTokenMarketRateUsd,
    rngExpectedRewardUsd,
  };

  return {
    ...context,
    state: getState(context),
  };
};

const getState = (context: DrawAuctionContext): DrawAuctionState =&gt; {
  if (context.rngIsAuctionOpen &amp;&amp; context.rngFeeTokenIsSet &amp;&amp; context.rngFeeUsd &gt; 0) {
    return DrawAuctionState.RngStartVrfHelper;
  } else if (context.rngRelayIsAuctionOpen) {
    return DrawAuctionState.RngRelayBridge;
  }
};
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">BigNumber</span>, <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">formatUnits</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@ethersproject/units'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">Provider</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@ethersproject/providers'</span>;
<span class="hljs-keyword">import</span> {
  <span class="hljs-title">AuctionContracts</span>,
  <span class="hljs-title">DrawAuctionContext</span>,
  <span class="hljs-title">DrawAuctionRelayerContext</span>,
  <span class="hljs-title">RelayDrawAuctionContext</span>,
  <span class="hljs-title">RngDrawAuctionContext</span>,
  <span class="hljs-title">TokenWithRate</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@generationsoftware/pt-v5-autotasks-library'</span>;

export <span class="hljs-keyword">enum</span> <span class="hljs-title">DrawAuctionState</span> {
  RngStartVrfHelper <span class="hljs-operator">=</span> <span class="hljs-string">'RngVrfHelper'</span>,
  RngRelayBridge <span class="hljs-operator">=</span> <span class="hljs-string">'RngRelayBridge'</span>,
}

<span class="hljs-keyword">import</span> { <span class="hljs-title">ERC20Abi</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'../abis/ERC20Abi'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">VrfRngAbi</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'../abis/VrfRngAbi'</span>;

const RELAYER_ADDRESS <span class="hljs-operator">=</span> <span class="hljs-string">''</span>; <span class="hljs-comment">// account address of your signer / relayer</span>
const ZERO_ADDRESS <span class="hljs-operator">=</span> <span class="hljs-string">'0x0000000000000000000000000000000000000000'</span>;

const RNG_FEE_TOKEN_MARKET_RATE_USD <span class="hljs-operator">=</span> <span class="hljs-number">6.14</span>
const REWARD_TOKEN_MARKET_RATE_USD <span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.62</span>
const ETH_MARKET_RATE_USD <span class="hljs-operator">=</span> <span class="hljs-number">1636.27</span>

const getDrawAuctionContext <span class="hljs-operator">=</span> async (
  rngReadProvider: Provider,
  relayReadProvider: Provider,
  auctionContracts: AuctionContracts,
): Promise<span class="hljs-operator">&#x3C;</span>DrawAuctionContext<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const prizePoolReserve <span class="hljs-operator">=</span> await auctionContracts.prizePoolContract.reserve();
  const prizePoolReserveForOpenDraw <span class="hljs-operator">=</span> await auctionContracts.prizePoolContract.reserveForOpenDraw();
  const reserve <span class="hljs-operator">=</span> prizePoolReserve.add(prizePoolReserveForOpenDraw);

  const rngContext <span class="hljs-operator">=</span> await getRng(rngReadProvider, auctionContracts, reserve);
  const relayContext <span class="hljs-operator">=</span> await getRelay(relayReadProvider, auctionContracts, rngContext);

  const rngNativeTokenMarketRateUsd <span class="hljs-operator">=</span> await getNativeTokenMarketRateUsd(RNG_CHAIN_ID);
  const relayNativeTokenMarketRateUsd <span class="hljs-operator">=</span> await getNativeTokenMarketRateUsd(RELAY_CHAIN_ID);

  const rngExpectedRewardUsd <span class="hljs-operator">=</span> rngContext.rngExpectedReward <span class="hljs-operator">*</span> relayContext.rewardToken.assetRateUsd;

  const context <span class="hljs-operator">=</span> {
    ...rngContext,
    ...relayContext,
    rngNativeTokenMarketRateUsd,
    relayNativeTokenMarketRateUsd,
    rngExpectedRewardUsd,
  };

  <span class="hljs-keyword">return</span> {
    ...context,
    state: getState(context),
  };
};

const getState <span class="hljs-operator">=</span> (context: DrawAuctionContext): DrawAuctionState <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-keyword">if</span> (context.rngIsAuctionOpen <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> context.rngFeeTokenIsSet <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> context.rngFeeUsd <span class="hljs-operator">></span> <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> DrawAuctionState.RngStartVrfHelper;
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (context.rngRelayIsAuctionOpen) {
    <span class="hljs-keyword">return</span> DrawAuctionState.RngRelayBridge;
  }
};
</code></pre><p><code>getRng</code> will collect all of the info necessary to determine if the <code>startRngRequest</code> function can be run, the fee we need to pay when requesting a random number, and the reward we could earn by requesting it:</p><pre data-type="codeBlock" text="export const getRng = async (
  rngReadProvider: Provider,
  auctionContracts: AuctionContracts,
  reserve: BigNumber,
): Promise&lt;RngDrawAuctionContext&gt; =&gt; {
  // RNG Auction Service Info
  const rngService = await auctionContracts.rngAuctionContract.getNextRngService();
  const rngServiceContract = new ethers.Contract(rngService, VrfRngAbi, rngReadProvider);
  const rngServiceRequestFee = await rngServiceContract.getRequestFee();

  const rngFeeTokenAddress = rngServiceRequestFee[0];

  // RNG Estimated Fee from VrfHelper
  const gasPrice = await rngReadProvider.getGasPrice();
  const requestGasPriceWei = gasPrice;

  const chainlinkVRFV2DirectRngAuctionHelperContract =
    await auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract;
  const vrfHelperRequestFee = await chainlinkVRFV2DirectRngAuctionHelperContract.estimateRequestFee(
    requestGasPriceWei,
  );
  const rngFeeAmount = vrfHelperRequestFee._requestFee;

  const rngFeeTokenIsSet = rngFeeTokenAddress !== ZERO_ADDRESS;

  let rngFeeTokenBalance = BigNumber.from(0);
  let rngFeeTokenAllowance = BigNumber.from(0);
  let rngFeeTokenContract;

  if (rngFeeTokenIsSet) {
    rngFeeTokenContract = new ethers.Contract(rngFeeTokenAddress, ERC20Abi, rngReadProvider);

    rngFeeTokenBalance = await rngFeeTokenContract.balanceOf(RELAYER_ADDRESS);
    rngFeeTokenAllowance = await rngFeeTokenContract.allowance(
      RELAYER_ADDRESS,
      auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract.address,
    );
  }

  // RNG Auction info
  const rngIsAuctionOpen = await auctionContracts.rngAuctionContract.isAuctionOpen();
  const rngIsRngComplete = await auctionContracts.rngAuctionContract.isRngComplete();
  const rngCurrentFractionalReward =
    await auctionContracts.rngAuctionContract.currentFractionalReward();

  // RNG Auction Service
  let rngFeeToken: TokenWithRate;
  if (rngFeeTokenIsSet) {
    const rngFeeTokenSymbol = await rngFeeTokenContract.symbol();
    rngFeeTokenContract = new ethers.Contract(rngFeeTokenAddress, ERC20Abi, rngReadProvider);

    rngFeeToken = {
      address: rngFeeTokenAddress,
      decimals: await rngFeeTokenContract.decimals(),
      name: await rngFeeTokenContract.name(),
      symbol: rngFeeTokenSymbol,
      assetRateUsd: RNG_FEE_TOKEN_MARKET_RATE_USD,
    };
  }

  const rngCurrentFractionalRewardString = ethers.utils.formatEther(rngCurrentFractionalReward);

  // TODO: Assuming 18 decimals. May need to format using rewardToken&apos;s decimals instead
  const reserveStr = ethers.utils.formatEther(reserve);
  const rngExpectedReward = Number(reserveStr) * Number(rngCurrentFractionalRewardString);

  // RNG Fee
  let relayer: DrawAuctionRelayerContext;
  if (rngFeeTokenIsSet) {
    relayer = {
      rngFeeTokenBalance,
      rngFeeTokenAllowance,
    };
  }

  let rngFeeUsd = 0;
  if (rngFeeTokenIsSet) {
    rngFeeUsd =
      parseFloat(formatUnits(rngFeeAmount, rngFeeToken.decimals)) * rngFeeToken.assetRateUsd;
  }

  return {
    rngFeeTokenIsSet,
    rngFeeToken,
    rngFeeAmount,
    rngFeeUsd,
    rngIsAuctionOpen,
    rngIsRngComplete,
    rngExpectedReward,
    relayer,
  };
};
"><code>export const getRng <span class="hljs-operator">=</span> async (
  rngReadProvider: Provider,
  auctionContracts: AuctionContracts,
  reserve: BigNumber,
): Promise<span class="hljs-operator">&#x3C;</span>RngDrawAuctionContext<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-comment">// RNG Auction Service Info</span>
  const rngService <span class="hljs-operator">=</span> await auctionContracts.rngAuctionContract.getNextRngService();
  const rngServiceContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(rngService, VrfRngAbi, rngReadProvider);
  const rngServiceRequestFee <span class="hljs-operator">=</span> await rngServiceContract.getRequestFee();

  const rngFeeTokenAddress <span class="hljs-operator">=</span> rngServiceRequestFee[<span class="hljs-number">0</span>];

  <span class="hljs-comment">// RNG Estimated Fee from VrfHelper</span>
  const gasPrice <span class="hljs-operator">=</span> await rngReadProvider.getGasPrice();
  const requestGasPriceWei <span class="hljs-operator">=</span> gasPrice;

  const chainlinkVRFV2DirectRngAuctionHelperContract <span class="hljs-operator">=</span>
    await auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract;
  const vrfHelperRequestFee <span class="hljs-operator">=</span> await chainlinkVRFV2DirectRngAuctionHelperContract.estimateRequestFee(
    requestGasPriceWei,
  );
  const rngFeeAmount <span class="hljs-operator">=</span> vrfHelperRequestFee._requestFee;

  const rngFeeTokenIsSet <span class="hljs-operator">=</span> rngFeeTokenAddress <span class="hljs-operator">!</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> ZERO_ADDRESS;

  let rngFeeTokenBalance <span class="hljs-operator">=</span> BigNumber.from(<span class="hljs-number">0</span>);
  let rngFeeTokenAllowance <span class="hljs-operator">=</span> BigNumber.from(<span class="hljs-number">0</span>);
  let rngFeeTokenContract;

  <span class="hljs-keyword">if</span> (rngFeeTokenIsSet) {
    rngFeeTokenContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(rngFeeTokenAddress, ERC20Abi, rngReadProvider);

    rngFeeTokenBalance <span class="hljs-operator">=</span> await rngFeeTokenContract.balanceOf(RELAYER_ADDRESS);
    rngFeeTokenAllowance <span class="hljs-operator">=</span> await rngFeeTokenContract.allowance(
      RELAYER_ADDRESS,
      auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract.<span class="hljs-built_in">address</span>,
    );
  }

  <span class="hljs-comment">// RNG Auction info</span>
  const rngIsAuctionOpen <span class="hljs-operator">=</span> await auctionContracts.rngAuctionContract.isAuctionOpen();
  const rngIsRngComplete <span class="hljs-operator">=</span> await auctionContracts.rngAuctionContract.isRngComplete();
  const rngCurrentFractionalReward <span class="hljs-operator">=</span>
    await auctionContracts.rngAuctionContract.currentFractionalReward();

  <span class="hljs-comment">// RNG Auction Service</span>
  let rngFeeToken: TokenWithRate;
  <span class="hljs-keyword">if</span> (rngFeeTokenIsSet) {
    const rngFeeTokenSymbol <span class="hljs-operator">=</span> await rngFeeTokenContract.symbol();
    rngFeeTokenContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(rngFeeTokenAddress, ERC20Abi, rngReadProvider);

    rngFeeToken <span class="hljs-operator">=</span> {
      <span class="hljs-keyword">address</span>: rngFeeTokenAddress,
      decimals: await rngFeeTokenContract.decimals(),
      name: await rngFeeTokenContract.<span class="hljs-built_in">name</span>(),
      symbol: rngFeeTokenSymbol,
      assetRateUsd: RNG_FEE_TOKEN_MARKET_RATE_USD,
    };
  }

  const rngCurrentFractionalRewardString <span class="hljs-operator">=</span> ethers.utils.formatEther(rngCurrentFractionalReward);

  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Assuming 18 decimals. May need to format using rewardToken's decimals instead</span>
  const reserveStr <span class="hljs-operator">=</span> ethers.utils.formatEther(reserve);
  const rngExpectedReward <span class="hljs-operator">=</span> Number(reserveStr) <span class="hljs-operator">*</span> Number(rngCurrentFractionalRewardString);

  <span class="hljs-comment">// RNG Fee</span>
  let relayer: DrawAuctionRelayerContext;
  <span class="hljs-keyword">if</span> (rngFeeTokenIsSet) {
    relayer <span class="hljs-operator">=</span> {
      rngFeeTokenBalance,
      rngFeeTokenAllowance,
    };
  }

  let rngFeeUsd <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
  <span class="hljs-keyword">if</span> (rngFeeTokenIsSet) {
    rngFeeUsd <span class="hljs-operator">=</span>
      parseFloat(formatUnits(rngFeeAmount, rngFeeToken.decimals)) <span class="hljs-operator">*</span> rngFeeToken.assetRateUsd;
  }

  <span class="hljs-keyword">return</span> {
    rngFeeTokenIsSet,
    rngFeeToken,
    rngFeeAmount,
    rngFeeUsd,
    rngIsAuctionOpen,
    rngIsRngComplete,
    rngExpectedReward,
    relayer,
  };
};
</code></pre><p><code>getRelay</code> takes information from <code>getRng</code> and determines if <code>relay()</code>(the bridging of the random number) can be run and how much we can earn by running that function:</p><pre data-type="codeBlock" text="export const getRelay = async (
  readProvider: Provider,
  auctionContracts: AuctionContracts,
  rngContext: RngDrawAuctionContext,
): Promise&lt;RelayDrawAuctionContext&gt; =&gt; {
  // Prize Pool Info
  const prizePoolOpenDrawEndsAt = await auctionContracts.prizePoolContract.openDrawEndsAt();

  const rewardTokenAddress = await auctionContracts.prizePoolContract.prizeToken();
  const rewardTokenContract = new ethers.Contract(rewardTokenAddress, ERC20Abi, readProvider);

  // Reward Token
  const rewardTokenSymbol = await rewardTokenContract.symbol();

  const rewardToken: TokenWithRate = {
    address: rewardTokenAddress,
    decimals: await rewardTokenContract.decimals(),
    name: await rewardTokenContract.name(),
    symbol: rewardTokenSymbol,
    assetRateUsd: REWARD_TOKEN_MARKET_RATE_USD,
  };

  // Relay Auction Info
  const rngRelayLastSequenceId = auctionContracts.rngAuctionContract.lastSequenceId();

  const lastSequenceCompleted = await auctionContracts.rngRelayAuctionContract.isSequenceCompleted(
    rngRelayLastSequenceId,
  );

  const rngRelayIsAuctionOpen =
    rngRelayLastSequenceId &gt; 0 &amp;&amp; rngContext.rngIsRngComplete &amp;&amp; !lastSequenceCompleted;

  // Relayer Reward
  let rngRelayExpectedReward, rngRelayExpectedRewardUsd;
  if (rngRelayIsAuctionOpen) {
    const [randomNumber, completedAt] =
      await auctionContracts.rngAuctionContract.callStatic.getRngResults();
    const rngLastAuctionResult = await auctionContracts.rngAuctionContract.getLastAuctionResult();
    const elapsedTime = Math.floor(Date.now() / 1000) - Number(completedAt.toString());

    const rngRelayRewardFraction =
      await auctionContracts.rngRelayAuctionContract.computeRewardFraction(elapsedTime);

    const auctionResult = {
      rewardFraction: rngRelayRewardFraction,
      recipient: RELAYER_ADDRESS,
    };

    const auctionResults = [];
    auctionResults[0] = rngLastAuctionResult;
    auctionResults[1] = auctionResult;

    const rngRelayExpectedRewardResult =
      await auctionContracts.rngRelayAuctionContract.callStatic.computeRewards(auctionResults);
    rngRelayExpectedReward = rngRelayExpectedRewardResult[1];

    rngRelayExpectedRewardUsd =
      parseFloat(formatUnits(rngRelayExpectedReward.toString(), rewardToken.decimals)) *
      rewardToken.assetRateUsd;
  }

  return {
    prizePoolOpenDrawEndsAt,
    rewardToken,
    rngRelayIsAuctionOpen,
    rngRelayExpectedReward,
    rngRelayExpectedRewardUsd,
    rngRelayLastSequenceId,
  };
};
"><code>export const getRelay <span class="hljs-operator">=</span> async (
  readProvider: Provider,
  auctionContracts: AuctionContracts,
  rngContext: RngDrawAuctionContext,
): Promise<span class="hljs-operator">&#x3C;</span>RelayDrawAuctionContext<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-comment">// Prize Pool Info</span>
  const prizePoolOpenDrawEndsAt <span class="hljs-operator">=</span> await auctionContracts.prizePoolContract.openDrawEndsAt();

  const rewardTokenAddress <span class="hljs-operator">=</span> await auctionContracts.prizePoolContract.prizeToken();
  const rewardTokenContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(rewardTokenAddress, ERC20Abi, readProvider);

  <span class="hljs-comment">// Reward Token</span>
  const rewardTokenSymbol <span class="hljs-operator">=</span> await rewardTokenContract.symbol();

  const rewardToken: TokenWithRate <span class="hljs-operator">=</span> {
    <span class="hljs-keyword">address</span>: rewardTokenAddress,
    decimals: await rewardTokenContract.decimals(),
    name: await rewardTokenContract.<span class="hljs-built_in">name</span>(),
    symbol: rewardTokenSymbol,
    assetRateUsd: REWARD_TOKEN_MARKET_RATE_USD,
  };

  <span class="hljs-comment">// Relay Auction Info</span>
  const rngRelayLastSequenceId <span class="hljs-operator">=</span> auctionContracts.rngAuctionContract.lastSequenceId();

  const lastSequenceCompleted <span class="hljs-operator">=</span> await auctionContracts.rngRelayAuctionContract.isSequenceCompleted(
    rngRelayLastSequenceId,
  );

  const rngRelayIsAuctionOpen <span class="hljs-operator">=</span>
    rngRelayLastSequenceId <span class="hljs-operator">></span> <span class="hljs-number">0</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> rngContext.rngIsRngComplete <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-operator">!</span>lastSequenceCompleted;

  <span class="hljs-comment">// Relayer Reward</span>
  let rngRelayExpectedReward, rngRelayExpectedRewardUsd;
  <span class="hljs-keyword">if</span> (rngRelayIsAuctionOpen) {
    const [randomNumber, completedAt] <span class="hljs-operator">=</span>
      await auctionContracts.rngAuctionContract.callStatic.getRngResults();
    const rngLastAuctionResult <span class="hljs-operator">=</span> await auctionContracts.rngAuctionContract.getLastAuctionResult();
    const elapsedTime <span class="hljs-operator">=</span> Math.floor(Date.now() <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>) <span class="hljs-operator">-</span> Number(completedAt.toString());

    const rngRelayRewardFraction <span class="hljs-operator">=</span>
      await auctionContracts.rngRelayAuctionContract.computeRewardFraction(elapsedTime);

    const auctionResult <span class="hljs-operator">=</span> {
      rewardFraction: rngRelayRewardFraction,
      recipient: RELAYER_ADDRESS,
    };

    const auctionResults <span class="hljs-operator">=</span> [];
    auctionResults[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> rngLastAuctionResult;
    auctionResults[<span class="hljs-number">1</span>] <span class="hljs-operator">=</span> auctionResult;

    const rngRelayExpectedRewardResult <span class="hljs-operator">=</span>
      await auctionContracts.rngRelayAuctionContract.callStatic.computeRewards(auctionResults);
    rngRelayExpectedReward <span class="hljs-operator">=</span> rngRelayExpectedRewardResult[<span class="hljs-number">1</span>];

    rngRelayExpectedRewardUsd <span class="hljs-operator">=</span>
      parseFloat(formatUnits(rngRelayExpectedReward.toString(), rewardToken.decimals)) <span class="hljs-operator">*</span>
      rewardToken.assetRateUsd;
  }

  <span class="hljs-keyword">return</span> {
    prizePoolOpenDrawEndsAt,
    rewardToken,
    rngRelayIsAuctionOpen,
    rngRelayExpectedReward,
    rngRelayExpectedRewardUsd,
    rngRelayLastSequenceId,
  };
};
</code></pre><h2 id="h-3-calculating-profitability" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">3. Calculating Profitability</h2><p>Now that we have all of that information about the state of the auctions and the cost of the RNG fee we can figure out how much could be earned by starting and completing the RNG process.</p><p>If the first phase <code>RngAuction#startRngRequest</code> is open, we will need to check if we can afford the RNG Fee (denominated in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.coingecko.com/en/coins/chainlink"><strong>LINK</strong></a><strong>),</strong> and if we have provided enough allowance to the contract to spend our relayer’s LINK. If the allowance is not high enough it will set it to maximum (as we trust the security of this contract. You may not want to set yours to max):</p><pre data-type="codeBlock" text="const checkBalance = (context: DrawAuctionContext) =&gt; {
  const cannotAffordRngFee = context.relayer.rngFeeTokenBalance.lt(context.rngFeeAmount);
  if (cannotAffordRngFee) {
    console.warn(
      `Need to increase relayer/bot&apos;s balance of &apos;${context.rngFeeToken.symbol}&apos; to pay RNG fee.`,
    );
  } else {
    console.log(&apos;Sufficient balance ✔&apos;);
  }
};

const increaseRngFeeAllowance = async (
  signer,
  context: DrawAuctionContext,
  auctionContracts: AuctionContracts,
) =&gt; {
  const rngFeeTokenContract = new ethers.Contract(context.rngFeeToken.address, ERC20Abi, signer);

  const allowance = context.relayer.rngFeeTokenAllowance;

  if (allowance.lt(context.rngFeeAmount)) {
    const tx = await rngFeeTokenContract.approve(
      auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract.address,
      ethers.constants.MaxInt256,
    );
    await tx.wait();
  }
};
"><code>const <span class="hljs-attr">checkBalance</span> = (context: DrawAuctionContext) => {
  const <span class="hljs-attr">cannotAffordRngFee</span> = context.relayer.rngFeeTokenBalance.lt(context.rngFeeAmount)<span class="hljs-comment">;</span>
  if (cannotAffordRngFee) {
    console.warn(
      `Need to increase relayer/bot's balance of '${context.rngFeeToken.symbol}' to pay RNG fee.`,
    )<span class="hljs-comment">;</span>
  } else {
    console.log('Sufficient balance ✔')<span class="hljs-comment">;</span>
  }
}<span class="hljs-comment">;</span>

const <span class="hljs-attr">increaseRngFeeAllowance</span> = async (
  signer,
  context: DrawAuctionContext,
  auctionContracts: AuctionContracts,
) => {
  const <span class="hljs-attr">rngFeeTokenContract</span> = new ethers.Contract(context.rngFeeToken.address, ERC20Abi, signer)<span class="hljs-comment">;</span>

  const <span class="hljs-attr">allowance</span> = context.relayer.rngFeeTokenAllowance<span class="hljs-comment">;</span>

  if (allowance.lt(context.rngFeeAmount)) {
    const <span class="hljs-attr">tx</span> = await rngFeeTokenContract.approve(
      auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract.address,
      ethers.constants.MaxInt256,
    )<span class="hljs-comment">;</span>
    await tx.wait()<span class="hljs-comment">;</span>
  }
}<span class="hljs-comment">;</span>
</code></pre><p>—</p><p><em>Note:</em> We’ll leave out how to get the gas cost in USD for the chain you’re working on. You can view the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/blob/fa44f128e0d5f85c2f9544746a48dba88255a7c7/packages/library/src/claimerProfitablePrizeTxs.ts#L262-L272"><strong>reference implementation</strong></a> to see how this is done using <code>eth_gasPrice</code>, <code>gasEstimate</code> on contract calls from ethers.js and the USD value of the native gas token (ie. ETH on Optimism) from a price feed such as Coingecko or Covalent, etc.</p><p>Now we can find out if we will make profit on this transaction:</p><pre data-type="codeBlock" text="const calculateProfit = async (
  gasCostUsd: number,
  context: DrawAuctionContext,
): Promise&lt;boolean&gt; =&gt; {
  const grossProfitUsd =
    context.state === DrawAuctionState.RngStartVrfHelper
      ? context.rngExpectedRewardUsd
      : context.rngRelayExpectedRewardUsd;

  let netProfitUsd;
  if (context.state === DrawAuctionState.RngStartVrfHelper) {
    netProfitUsd = grossProfitUsd - gasCostUsd - context.rngFeeUsd;
  } else {
    netProfitUsd = grossProfitUsd - gasCostUsd;
  }

  const profitable = netProfitUsd &gt; MIN_PROFIT_THRESHOLD_USD;

  return profitable;
};
"><code>const calculateProfit <span class="hljs-operator">=</span> async (
  gasCostUsd: number,
  context: DrawAuctionContext,
): Promise<span class="hljs-operator">&#x3C;</span>boolean<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const grossProfitUsd <span class="hljs-operator">=</span>
    context.state <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> DrawAuctionState.RngStartVrfHelper
      ? context.rngExpectedRewardUsd
      : context.rngRelayExpectedRewardUsd;

  let netProfitUsd;
  <span class="hljs-keyword">if</span> (context.state <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> DrawAuctionState.RngStartVrfHelper) {
    netProfitUsd <span class="hljs-operator">=</span> grossProfitUsd <span class="hljs-operator">-</span> gasCostUsd <span class="hljs-operator">-</span> context.rngFeeUsd;
  } <span class="hljs-keyword">else</span> {
    netProfitUsd <span class="hljs-operator">=</span> grossProfitUsd <span class="hljs-operator">-</span> gasCostUsd;
  }

  const profitable <span class="hljs-operator">=</span> netProfitUsd <span class="hljs-operator">></span> MIN_PROFIT_THRESHOLD_USD;

  <span class="hljs-keyword">return</span> profitable;
};
</code></pre><h2 id="h-4-executing-transactions" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">4. Executing Transactions</h2><p>Last but not least, we need to determine which contract we want to transact with using the <code>DrawAuctionState</code>, and then send the transaction.</p><p>The reason we use the <code>ChainlinkVRFV2DirectRngAuctionHelper</code> is that it takes care of transferring the ERC20 RNG fee token, making it easier for bots to interact with the <code>RngAuction</code>contract.</p><p>The <code>RngAuctionRelayerRemoteOwnerContract</code> is used when the RNG is on one chain and needs to be relayed to a prize pool on another chain. Otherwise we could use the <code>RngAuctionRelayerDirect#relay</code> function directly.</p><pre data-type="codeBlock" text="const sendTransaction = async (
  relayer: Relayer,
  auctionContracts: AuctionContracts,
  context: DrawAuctionContext,
) =&gt; {
  let populatedTx: PopulatedTransaction;
  if (context.drawAuctionState === DrawAuctionState.RngStartVrfHelper) {
    const chainlinkRngAuctionHelper = auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract;
    populatedTx = await chainlinkRngAuctionHelper.populateTransaction.transferFeeAndStartRngRequest(
      REWARD_RECIPIENT,
    );
  } else {
    populatedTx =
      await auctionContracts.rngAuctionRelayerRemoteOwnerContract.populateTransaction.relay(
        ERC_5164_MESSAGE_DISPATCHER_ADDRESS[RNG_CHAIN_ID],
        RELAY_CHAIN_ID,
        auctionContracts.remoteOwnerContract.address,
        auctionContracts.rngRelayAuctionContract.address,
        REWARD_RECIPIENT,
      );
  }

  const tx = await relayer.sendTransaction({
    data: populatedTx.data,
    to: populatedTx.to,
    gasLimit: 8000000,
  });

  console.log(&apos;Transaction hash:&apos;, tx.hash);
};
"><code>const sendTransaction <span class="hljs-operator">=</span> async (
  relayer: Relayer,
  auctionContracts: AuctionContracts,
  context: DrawAuctionContext,
) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  let populatedTx: PopulatedTransaction;
  <span class="hljs-keyword">if</span> (context.drawAuctionState <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> DrawAuctionState.RngStartVrfHelper) {
    const chainlinkRngAuctionHelper <span class="hljs-operator">=</span> auctionContracts.chainlinkVRFV2DirectRngAuctionHelperContract;
    populatedTx <span class="hljs-operator">=</span> await chainlinkRngAuctionHelper.populateTransaction.transferFeeAndStartRngRequest(
      REWARD_RECIPIENT,
    );
  } <span class="hljs-keyword">else</span> {
    populatedTx <span class="hljs-operator">=</span>
      await auctionContracts.rngAuctionRelayerRemoteOwnerContract.populateTransaction.relay(
        ERC_5164_MESSAGE_DISPATCHER_ADDRESS[RNG_CHAIN_ID],
        RELAY_CHAIN_ID,
        auctionContracts.remoteOwnerContract.<span class="hljs-built_in">address</span>,
        auctionContracts.rngRelayAuctionContract.<span class="hljs-built_in">address</span>,
        REWARD_RECIPIENT,
      );
  }

  const <span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> await relayer.sendTransaction({
    data: populatedTx.data,
    to: populatedTx.to,
    gasLimit: <span class="hljs-number">8000000</span>,
  });

  console.log(<span class="hljs-string">'Transaction hash:'</span>, <span class="hljs-built_in">tx</span>.hash);
};
</code></pre><h2 id="h-finishing-up" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Finishing Up</h2><p>That wraps up Part 3 and this series of tutorials on the new PoolTogether hyperstructure bots.</p><p>Just in case you missed it: in <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/ES-IJduktYPb0X_sBikfqL-PVFRweNpoPrlr01zcVX8"><strong>Part 1</strong></a> we looked at making profit with Liquidation bots — whose job is to liquidate interest-bearing (yield) tokens for POOL to supply the prize pool with.</p><p>In <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href=""><strong>Part 2</strong></a> we looked at Prize Claiming bots — a way to earn POOL while automating the claiming of prizes every day on behalf of winners.</p><p>If you have any questions don’t hesitate to ask anyone in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pooltogether.com/discord"><strong>PoolTogether Discord community</strong></a>. The <code>#developers</code> channel is your best bet!</p>]]></content:encoded>
            <author>chuck-bergeron-generation-software@newsletter.paragraph.com (Chuck Bergeron - Generation Software)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/84fca63e84bd2505e3df517d348f1de08d47882fd05770159a1da92f03ec6aac.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Part 2. Creating a PoolTogether Prize Claiming bot]]></title>
            <link>https://paragraph.com/@chuck-bergeron-generation-software/part-2-creating-a-pooltogether-prize-claiming-bot</link>
            <guid>EUyPNXgoMdP2Fc7iV7LV</guid>
            <pubDate>Tue, 19 Sep 2023 23:29:40 GMT</pubDate>
            <description><![CDATA[Tutorial duration: ~20 minutes UPDATE: This is the old method of running bots. There is a lot of good information in these blog posts but they may differ from the new bot code. See the Cabana docs for the latest on G9’s PoolTogether bots: https://docs.cabana.fi/protocol/botsWhat is a prize claiming bot for?The upcoming PoolTogether Hyperstructure automates prize claiming using incentives. You can earn fees by claiming prizes for winners. You earn money, and they get their prizes automatically...]]></description>
            <content:encoded><![CDATA[<p><strong><em>Tutorial duration:</em></strong> ~20 minutes</p><p><strong>UPDATE:</strong> This is the old method of running bots. There is a lot of good information in these blog posts but they may differ from the new bot code. See the Cabana docs for the latest on G9’s PoolTogether bots:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.cabana.fi/protocol/bots">https://docs.cabana.fi/protocol/bots</a></p><h2 id="h-what-is-a-prize-claiming-bot-for" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is a prize claiming bot for?</h2><p>The upcoming PoolTogether <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://jacob.energy/hyperstructures.html"><strong>Hyperstructure</strong></a> automates prize claiming using incentives. You can earn fees by claiming prizes for winners. You earn money, and they get their prizes automatically.</p><p>New prizes are released in daily draws. Winners must first be computed, then the prizes can be claimed. The reward for claiming a prize is priced according to a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/r?url=https%3A%2F%2Fwww.paradigm.xyz%2F2022%2F08%2Fvrgda"><strong>variable rate gradual dutch auction</strong></a> (VRGDA for short). In a nutshell, this means that the reward increases if prizes aren’t claimed fast enough.</p><p>In this tutorial we will create <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.logrocket.com/tools-smart-contract-automation-guide/#:~:text=how%20they%20work.-,Chainlink%20Keepers,transactions%20based%20on%20time%20intervals."><strong>a bot</strong></a> which will run every few minutes and check if there are prizes to claim on behalf of winners. If there are prizes to claim and the reward they will earn for claiming is worth the cost, the bot will send a transaction to claim.</p><p>—</p><p><strong><em>Note:</em></strong> We have created a bot (which runs on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.openzeppelin.com/defender"><strong>OpenZeppelin Defender</strong></a>, but you can use whichever cron/relay service you like) which you can use as a reference here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/prize-claimer"><strong>https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/prize-claimer</strong></a></p><p>These tutorials expect you have intermediate knowledge of programming concepts and JavaScript/TypeScript. We’ll use <code>ethers.js</code>, but you could write these bots using <code>web3.js</code>, <code>Web3.py</code>, etc.</p><p>Also, there is a JavaScript utils library for interacting with the <code>PrizePool</code> and associated contracts here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-utils-js"><strong>https://github.com/GenerationSoftware/pt-v5-utils-js</strong></a></p><h2 id="h-rewards-and-prize-expiry" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Rewards &amp; Prize Expiry</h2><p>Prizes will expire every day to incentivize claiming. The rewards for claiming increase exponentially during the course of the auction. Rewards are taken from the prize itself. A basic example of this is:</p><ul><li><p>a depositor wins 10 POOL</p></li><li><p>15 hours later (if the prize hasn’t been claimed yet) the reward for claiming would be 2 POOL</p></li><li><p>the winner would receive 8 POOL</p></li><li><p>the bot who claims the prize would earn the 2 POOL reward</p></li></ul><p>Thankfully the system has been designed so that a bot can send one transaction to claim multiple prizes in one go. Being able to claim multiple prizes in one transaction ensures it will be profitable to claim prizes soon after a draw, reducing the amount of claim fees taken from the prize itself.</p><p>The first prize claim transaction fee is about 220,000 wei worth of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/developers/docs/gas/"><strong>gas</strong></a>, and each subsequent prize claim is about 120,000 wei gas. During the time this article was written, on Optimism gas for <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://optimistic.etherscan.io/tx/0xce5d7d447a7e33575b4fd674e6322e2c39e8a02af69acfdba51860ca17574141"><strong>1 prize claimed</strong></a> was $0.08, and gas for <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://optimistic.etherscan.io/tx/0x9f611a9eeea0e4bdfb9a6c354bc60342a6021c17965d769d9d05630075b06e5f"><strong>7 prizes claimed</strong></a> was $0.20.</p><h2 id="h-claim-prizes-subgraph" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Claim Prizes Subgraph</h2><p>There is a subgraph live at <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://api.studio.thegraph.com/query/41211/pt-v5-optimism-prize-pool/v0.0.1"><strong>https://api.studio.thegraph.com/query/41211/pt-v5-optimism-prize-pool/v0.0.1</strong></a> which you can use for querying which prizes have previously been claimed for which draws. The <code>pt-v5-utils-js</code> library also has <code>getSubgraphClaimedPrizes</code> function for easily fetching claimed prizes.</p><p>And with that, let’s get into it:</p><h2 id="h-steps-to-claiming-prizes" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Steps to claiming prizes:</h2><ol><li><p><strong>Write main() function</strong></p></li><li><p><strong>Find all active depositor accounts and compute prizes won for each account</strong></p></li><li><p><strong>Check the optimal number of prizes to claim per tier to maximize rewards (called fees)</strong></p></li><li><p><strong>If the fees are sufficiently profitable, execute prize claims</strong></p></li><li><p><strong>Periodically withdraw the fees you have earned from the Prize Pool</strong></p></li></ol><h2 id="h-1-main-function" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">1. main() Function</h2><p>Let’s start by importing the various library functions and types (if using TypeScript) we will need to use, then setting up an async function that we will call right away:</p><pre data-type="codeBlock" text="import { Contract, BigNumber } from &apos;ethers&apos;;
import { JsonRpcProvider } from &apos;@ethersproject/providers&apos;
import { formatUnits } from &apos;@ethersproject/units&apos;;
import {
  computeDrawWinners,
  downloadContractsBlob,
  flagClaimedRpc,
  Claim,
  ContractsBlob,
} from &apos;@generationsoftware/pt-v5-utils-js&apos;;
import groupBy from &apos;lodash.groupby&apos;;

// The params required by the `claimPrizes()` contract
// function on the Claimer.sol contract
interface ClaimPrizesParams {
  vault: string;
  tier: number;
  winners: string[];
  prizeIndices: number[][];
  feeRecipient: string;
  minVrgdaFeePerClaim: string;
}

const CONTRACTS_VERSION = {
  major: 1,
  minor: 0,
  patch: 0,
};

// the address you want earned fees to accrue to
const FEE_RECIPIENT = &apos;0x0000000000000000000000000000000000000000&apos;
const CHAIN_ID = 10; // Optimism

const readProvider = new JsonRpcProvider(YOUR_JSON_RPC_PROVIDER_URL);

const main = async () =&gt; {
  const claimerContract = getClaimerContract(chainId, readProvider, contracts)
  
  const claims: Claim[] = await getClaims();
  
  const unclaimedClaimsGrouped = await findAndGroupUnclaimed(claims)
  
  await loopUnclaimedGroups(unclaimedClaimsGrouped, claimerContract)
}
main()

const getClaimerContract = (chainId: number, readProvider: Provider, contracts: ContractsBlob): Contract =&gt; {
  const claimerContract = getContract(
    &apos;Claimer&apos;,
    chainId,
    readProvider,
    contracts,
    CONTRACTS_VERSION,
  );
};
  
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Contract</span>, <span class="hljs-title class_">BigNumber</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'ethers'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">JsonRpcProvider</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ethersproject/providers'</span>
<span class="hljs-keyword">import</span> { formatUnits } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ethersproject/units'</span>;
<span class="hljs-keyword">import</span> {
  computeDrawWinners,
  downloadContractsBlob,
  flagClaimedRpc,
  <span class="hljs-title class_">Claim</span>,
  <span class="hljs-title class_">ContractsBlob</span>,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@generationsoftware/pt-v5-utils-js'</span>;
<span class="hljs-keyword">import</span> groupBy <span class="hljs-keyword">from</span> <span class="hljs-string">'lodash.groupby'</span>;

<span class="hljs-comment">// The params required by the `claimPrizes()` contract</span>
<span class="hljs-comment">// function on the Claimer.sol contract</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">ClaimPrizesParams</span> {
  <span class="hljs-attr">vault</span>: <span class="hljs-built_in">string</span>;
  <span class="hljs-attr">tier</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">winners</span>: <span class="hljs-built_in">string</span>[];
  <span class="hljs-attr">prizeIndices</span>: <span class="hljs-built_in">number</span>[][];
  <span class="hljs-attr">feeRecipient</span>: <span class="hljs-built_in">string</span>;
  <span class="hljs-attr">minVrgdaFeePerClaim</span>: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">CONTRACTS_VERSION</span> = {
  <span class="hljs-attr">major</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">minor</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">patch</span>: <span class="hljs-number">0</span>,
};

<span class="hljs-comment">// the address you want earned fees to accrue to</span>
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FEE_RECIPIENT</span> = <span class="hljs-string">'0x0000000000000000000000000000000000000000'</span>
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">CHAIN_ID</span> = <span class="hljs-number">10</span>; <span class="hljs-comment">// Optimism</span>

<span class="hljs-keyword">const</span> readProvider = <span class="hljs-keyword">new</span> <span class="hljs-title class_">JsonRpcProvider</span>(<span class="hljs-variable constant_">YOUR_JSON_RPC_PROVIDER_URL</span>);

<span class="hljs-keyword">const</span> <span class="hljs-title function_">main</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params"></span>) => {
  <span class="hljs-keyword">const</span> claimerContract = <span class="hljs-title function_">getClaimerContract</span>(chainId, readProvider, contracts)
  
  <span class="hljs-keyword">const</span> <span class="hljs-attr">claims</span>: <span class="hljs-title class_">Claim</span>[] = <span class="hljs-keyword">await</span> <span class="hljs-title function_">getClaims</span>();
  
  <span class="hljs-keyword">const</span> unclaimedClaimsGrouped = <span class="hljs-keyword">await</span> <span class="hljs-title function_">findAndGroupUnclaimed</span>(claims)
  
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">loopUnclaimedGroups</span>(unclaimedClaimsGrouped, claimerContract)
}
<span class="hljs-title function_">main</span>()

<span class="hljs-keyword">const</span> getClaimerContract = (<span class="hljs-attr">chainId</span>: <span class="hljs-built_in">number</span>, <span class="hljs-attr">readProvider</span>: <span class="hljs-title class_">Provider</span>, <span class="hljs-attr">contracts</span>: <span class="hljs-title class_">ContractsBlob</span>): <span class="hljs-function"><span class="hljs-params">Contract</span> =></span> {
  <span class="hljs-keyword">const</span> claimerContract = <span class="hljs-title function_">getContract</span>(
    <span class="hljs-string">'Claimer'</span>,
    chainId,
    readProvider,
    contracts,
    <span class="hljs-variable constant_">CONTRACTS_VERSION</span>,
  );
};
  
</code></pre><h2 id="h-2-find-winners" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">2. Find Winners</h2><p>To collect a list of all active depositor addresses we can use the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://v4.docs.pooltogether.com/protocol/next/api/subgraphs/"><strong>TwabController subgraph</strong></a>. We can then determine if they won in the previous draw by using RPC calls to the PrizePool contract’s<code>isWinner()</code>.</p><p>You can use the handy <code>computeDrawWinners()</code>in the <code>pt-v5-utils-js</code>library to find depositors, determine who won and if their prizes have been claimed already:</p><pre data-type="codeBlock" text="const getClaims = async (): Promise&lt;Claim[]&gt; { =&gt; {
  const contracts: ContractsBlob = await downloadContractsBlob(CHAIN_ID);

  // We have found this to be too heavy to use in our OpenZeppelin Defender autotasks.
  // Depending on the number of winners you may need to cache the results of this for
  // every draw in flat files that your bot then fetches later
  const claims: Claim[] = await computeDrawWinners(readProvider, contracts, CHAIN_ID);
  
  return claims;
}

// computeDrawWinners() returns:
[
  {
    vault: &apos;0x06b36307e4da41f0c42efb7d7abc02df0c8b5c49&apos;,
    winner: &apos;0x725e613f1816395dd453dc6394b1794a73152813&apos;,
    tier: 1,
    prizeIndex: 2,
    claimed: false
  },
  ...
]
"><code>const getClaims <span class="hljs-operator">=</span> async (): Promise<span class="hljs-operator">&#x3C;</span>Claim[]<span class="hljs-operator">></span> { <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const contracts: ContractsBlob <span class="hljs-operator">=</span> await downloadContractsBlob(CHAIN_ID);

  <span class="hljs-comment">// We have found this to be too heavy to use in our OpenZeppelin Defender autotasks.</span>
  <span class="hljs-comment">// Depending on the number of winners you may need to cache the results of this for</span>
  <span class="hljs-comment">// every draw in flat files that your bot then fetches later</span>
  const claims: Claim[] <span class="hljs-operator">=</span> await computeDrawWinners(readProvider, contracts, CHAIN_ID);
  
  <span class="hljs-keyword">return</span> claims;
}

<span class="hljs-comment">// computeDrawWinners() returns:</span>
[
  {
    vault: <span class="hljs-string">'0x06b36307e4da41f0c42efb7d7abc02df0c8b5c49'</span>,
    winner: <span class="hljs-string">'0x725e613f1816395dd453dc6394b1794a73152813'</span>,
    tier: <span class="hljs-number">1</span>,
    prizeIndex: <span class="hljs-number">2</span>,
    claimed: <span class="hljs-literal">false</span>
  },
  ...
]
</code></pre><p><strong><em>Note:</em></strong> There is also the<code>pt-v5-cli</code>helper library available which can be used to find depositors and cache data about their winnings to JSON flat files here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/generationSoftware/pt-v5-cli"><strong>https://github.com/generationSoftware/pt-v5-cli</strong></a></p><p>We get back an array containing info about each prize. One account can win multiple prizes in multiple <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://v4.docs.pooltogether.com/protocol/next/design/vaults"><strong>vaults</strong></a>, tiers and indices.</p><p><strong>Vaults</strong> — <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-4626"><strong>4626</strong></a>-compatible prize vaults, hold user’s deposits, and in return gives the user a interest-bearing token as collateral.</p><p><strong>Tiers</strong> — the various prize tiers which someone can win from. For instance, tier 0 prizes may be $1,000, while tier 1 prizes are $500, tier 2 $100, etc.</p><p><strong>Indices</strong> — a method of allowing one account to win multiple prizes from the same tier. This allows for fairer winning odds based on the pooler’s deposit size.</p><p>Continuing on we check live data with RPC calls for prizes that have already been claimed (removing them with <code>filter</code>) and group the unclaimed claims by Vault and Tier:</p><pre data-type="codeBlock" text="const findAndGroupUnclaimed = async (claims: Claim[]) =&gt; {
  // Cross-reference claimed prizes to flag if a prize has been claimed or not
  claims = await flagClaimedRpc(readProvider, contracts, claims);

  let unclaimedClaims = claims.filter((claim) =&gt; !claim.claimed);
  console.log(`${unclaimedClaims.length} prizes remaining to be claimed...`);

  if (unclaimedClaims.length === 0) {
    console.log(`No prizes left to claim. Exiting ...`);
    return;
  }
  
  // Sort unclaimed claims by tier so largest prizes (with the largest rewards) are first
  unclaimedClaims = unclaimedClaims.sort((a, b) =&gt; a.tier - b.tier);

  // Group claims by vault &amp; tier
  const unclaimedClaimsGrouped = groupBy(unclaimedClaims, (item) =&gt; [item.vault, item.tier]);
  
  return unclaimedClaimsGrouped;
};
"><code>const findAndGroupUnclaimed <span class="hljs-operator">=</span> async (claims: Claim[]) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-comment">// Cross-reference claimed prizes to flag if a prize has been claimed or not</span>
  claims <span class="hljs-operator">=</span> await flagClaimedRpc(readProvider, contracts, claims);

  let unclaimedClaims <span class="hljs-operator">=</span> claims.filter((claim) <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-operator">!</span>claim.claimed);
  console.log(`${unclaimedClaims.<span class="hljs-built_in">length</span>} prizes remaining to be claimed...`);

  <span class="hljs-keyword">if</span> (unclaimedClaims.<span class="hljs-built_in">length</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
    console.log(`No prizes left to claim. Exiting ...`);
    <span class="hljs-keyword">return</span>;
  }
  
  <span class="hljs-comment">// Sort unclaimed claims by tier so largest prizes (with the largest rewards) are first</span>
  unclaimedClaims <span class="hljs-operator">=</span> unclaimedClaims.sort((a, b) <span class="hljs-operator">=</span><span class="hljs-operator">></span> a.tier <span class="hljs-operator">-</span> b.tier);

  <span class="hljs-comment">// Group claims by vault &#x26; tier</span>
  const unclaimedClaimsGrouped <span class="hljs-operator">=</span> groupBy(unclaimedClaims, (item) <span class="hljs-operator">=</span><span class="hljs-operator">></span> [item.vault, item.tier]);
  
  <span class="hljs-keyword">return</span> unclaimedClaimsGrouped;
};
</code></pre><h2 id="h-3-check-for-profitable-claims" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">3. Check For Profitable Claims</h2><p>Now that we have a list of prizes grouped and sorted by Vault &amp; Tier (stored in the <code>unclaimedClaimsGrouped</code> variable) let’s iterate through each to check how many we should claim (if any) to make us profit:</p><pre data-type="codeBlock" text="const loopUnclaimedGroups = async (unclaimedClaimsGrouped, claimerContract: Contract) =&gt; {
  for (let vaultTier of Object.entries(unclaimedClaimsGrouped)) {
    const [key, value] = vaultTier;
    const [vault, tier] = key.split(&apos;,&apos;);
    const groupedClaims: any = value;

    console.log(`Vault:       ${vault}`);
    console.log(`Tier Index:  #${tier}`);
    console.log(`# prizes:    ${groupedClaims.length}`);

    const claimPrizesParams = await calculateProfit(
      vault,
      Number(tier),
      claimerContract,
      groupedClaims,
    );

    // It&apos;s profitable if there is at least 1 claim to claim
    if (claimPrizesParams.winners.length &gt; 0) {
      await executeTransaction(claimPrizesParams, claimerContract)
    }
};
"><code>const loopUnclaimedGroups <span class="hljs-operator">=</span> async (unclaimedClaimsGrouped, claimerContract: Contract) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-keyword">for</span> (let vaultTier of Object.entries(unclaimedClaimsGrouped)) {
    const [key, value] <span class="hljs-operator">=</span> vaultTier;
    const [vault, tier] <span class="hljs-operator">=</span> key.split(<span class="hljs-string">','</span>);
    const groupedClaims: any <span class="hljs-operator">=</span> value;

    console.log(`Vault:       ${vault}`);
    console.log(`Tier Index:  #${tier}`);
    console.log(`# prizes:    ${groupedClaims.<span class="hljs-built_in">length</span>}`);

    const claimPrizesParams <span class="hljs-operator">=</span> await calculateProfit(
      vault,
      Number(tier),
      claimerContract,
      groupedClaims,
    );

    <span class="hljs-comment">// It's profitable if there is at least 1 claim to claim</span>
    <span class="hljs-keyword">if</span> (claimPrizesParams.winners.<span class="hljs-built_in">length</span> <span class="hljs-operator">></span> <span class="hljs-number">0</span>) {
      await executeTransaction(claimPrizesParams, claimerContract)
    }
};
</code></pre><p>—</p><p><em>Note:</em> We’ll leave out how to get the gas cost in USD for the chain you’re working on. You can view the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/blob/fa44f128e0d5f85c2f9544746a48dba88255a7c7/packages/library/src/claimerProfitablePrizeTxs.ts#L262-L272"><strong>reference implementation</strong></a> to see how this is done using <code>eth_gasPrice</code>, <code>gasEstimate</code> on contract calls from ethers.js and the USD value of the native gas token (ie. ETH on Optimism) from a price feed such as Coingecko or Covalent, etc.</p><p><code>calculateProfit()</code> will pass the claim data to <code>getClaimInfo()</code>, and in return receive information about how many claims in the array should be processed. It will then slice up the array to only include that number of claims, and build the params list that we will send to the contract function in <code>executeTransaction()</code>:</p><pre data-type="codeBlock" text="const calculateProfit = async (
  vault: string,
  tierIndex: number,
  claimerContract: Contract,
  groupedClaims: any,
): Promise&lt;ClaimPrizesParams&gt; =&gt; {
  const { claimCount, claimFeesUsd, totalCostUsd, minVrgdaFeePerClaim } = 
    await getClaimInfo(
      claimerContract,
      tierIndex,
      groupedClaims,
    );

  const claimsSlice = groupedClaims.slice(0, claimCount);
  const claimPrizesParams = buildParams(
    vault,
    tierIndex,
    claimsSlice,
    minVrgdaFeePerClaim,
  );

  return claimPrizesParams;
};

const buildParams = (
  vault: string,
  tier: number,
  claims: Claim[],
  minVrgdaFeePerClaim: string,
): ClaimPrizesParams =&gt; {
  let winners: string[] = [];
  let prizeIndices: number[][] = [];

  claims.forEach((claim) =&gt; {
    winners.push(claim.winner);
    prizeIndices.push([claim.prizeIndex]);
  });

  return {
    vault,
    tier,
    winners,
    prizeIndices,
    feeRecipient: FEE_RECIPIENT,
    minVrgdaFeePerClaim,
  };
};
"><code>const calculateProfit <span class="hljs-operator">=</span> async (
  vault: <span class="hljs-keyword">string</span>,
  tierIndex: number,
  claimerContract: Contract,
  groupedClaims: any,
): Promise<span class="hljs-operator">&#x3C;</span>ClaimPrizesParams<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const { claimCount, claimFeesUsd, totalCostUsd, minVrgdaFeePerClaim } <span class="hljs-operator">=</span> 
    await getClaimInfo(
      claimerContract,
      tierIndex,
      groupedClaims,
    );

  const claimsSlice <span class="hljs-operator">=</span> groupedClaims.slice(<span class="hljs-number">0</span>, claimCount);
  const claimPrizesParams <span class="hljs-operator">=</span> buildParams(
    vault,
    tierIndex,
    claimsSlice,
    minVrgdaFeePerClaim,
  );

  <span class="hljs-keyword">return</span> claimPrizesParams;
};

const buildParams <span class="hljs-operator">=</span> (
  vault: <span class="hljs-keyword">string</span>,
  tier: number,
  claims: Claim[],
  minVrgdaFeePerClaim: <span class="hljs-keyword">string</span>,
): ClaimPrizesParams <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  let winners: <span class="hljs-keyword">string</span>[] <span class="hljs-operator">=</span> [];
  let prizeIndices: number[][] <span class="hljs-operator">=</span> [];

  claims.forEach((claim) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    winners.<span class="hljs-built_in">push</span>(claim.winner);
    prizeIndices.<span class="hljs-built_in">push</span>([claim.prizeIndex]);
  });

  <span class="hljs-keyword">return</span> {
    vault,
    tier,
    winners,
    prizeIndices,
    feeRecipient: FEE_RECIPIENT,
    minVrgdaFeePerClaim,
  };
};
</code></pre><p>In <code>getClaimInfo()</code>below we go through each claim and call the Claimer contract’s <code>computeTotalFees(uint8,uint256)</code> function to get the amount we can earn.</p><p>On each loop, we will compare the amount we would earn against the previous fees, gas costs and <code>MIN_PROFIT_USD</code>. If we’re continuing to earn profit we’ll keep going, and if not we will break out of the loop.</p><p><code>getClaimInfo()</code> will return an object of the <code>ClaimInfo</code> type. <code>ClaimInfo</code> includes the # of claims (which we’ll use to slice the original claims array) and the <code>minVrgdaFeePerClaim</code>— necessary to pass as an argument to the Claimer contract’s <code>claimPrizes()</code> function later:</p><pre data-type="codeBlock" text="const MIN_PROFIT_USD = 0.1;           // $0.10 per prize claimed
const FEE_TOKEN_ASSET_RATE_USD = 0.6; // $0.60 for prize token (likely POOL)
const FEE_TOKEN_DECIMALS = 18;

const GAS_ONE_CLAIM_USD = 0.08;            // $0.08 gas cost for one claim
const GAS_EACH_FOLLOWING_CLAIM_USD = 0.02; // $0.02 extra gas cost for each following claim

interface ClaimInfo {
  claimCount: number;
  minVrgdaFeePerClaim: string;
}

const getClaimInfo = async (
  claimerContract: Contract,
  tierIndex: number,
  claims: Claim[],
): Promise&lt;ClaimInfo&gt; =&gt; {
  let claimCount = 0;
  let claimFeesUsd = 0;
  let totalCostUsd = 0;
  let prevNetFeesUsd = 0;
  let claimFees = BigNumber.from(0);
  let minVrgdaFees: BigNumber[] = [];
  for (let numClaims = 1; numClaims &lt;= claims.length; numClaims++) {
    const nextClaimFees = await claimerContract.functions&apos;computeTotalFees(uint8,uint256)&apos;;

    const totalCostUsd =
      numClaims === 1
        ? GAS_ONE_CLAIM_USD
        : GAS_ONE_CLAIM_USD + GAS_EACH_FOLLOWING_CLAIM_USD * numClaims;

    const claimFeesFormatted = formatUnits(claimFees.toString(), FEE_TOKEN_DECIMALS);
    claimFeesUsd = parseFloat(claimFeesFormatted) * FEE_TOKEN_ASSET_RATE_USD;

    const nextClaimFeesFormatted = formatUnits(claimFees.toString(), FEE_TOKEN_DECIMALS);
    const nextClaimFeesUsd = parseFloat(nextClaimFeesFormatted) * FEE_TOKEN_ASSET_RATE_USD;

    const netFeesUsd = nextClaimFeesUsd - totalCostUsd;

    if (netFeesUsd &gt; prevNetFeesUsd &amp;&amp; netFeesUsd &gt; MIN_PROFIT_USD) {
      prevNetFeesUsd = netFeesUsd;
      claimCount = numClaims;
      claimFees = nextClaimFees;
      claimFeesUsd = nextClaimFeesUsd;

      minVrgdaFees.push(nextClaimFees);
    } else {
      break;
    }
  }
  
  // Sort array of BigNumbers by comparing them using basic JS built-in sort()
  minVrgdaFees.sort((a: BigNumber, b: BigNumber) =&gt; (a.gt(b) ? 1 : -1));

  // Take the lowest BigNumber as the lowest fee we will accept
  const minVrgdaFeePerClaim = Boolean(minVrgdaFees[0]) ? minVrgdaFees[0].toString() : &apos;0&apos;;
  
  console.log(`$${netProfitUsd} = ($${claimFeesUsd} - $${totalCostUsd})`);

  return { claimCount, minVrgdaFeePerClaim };
};
"><code>const <span class="hljs-attr">MIN_PROFIT_USD</span> = <span class="hljs-number">0.1</span><span class="hljs-comment">;           // $0.10 per prize claimed</span>
const <span class="hljs-attr">FEE_TOKEN_ASSET_RATE_USD</span> = <span class="hljs-number">0.6</span><span class="hljs-comment">; // $0.60 for prize token (likely POOL)</span>
const <span class="hljs-attr">FEE_TOKEN_DECIMALS</span> = <span class="hljs-number">18</span><span class="hljs-comment">;</span>

const <span class="hljs-attr">GAS_ONE_CLAIM_USD</span> = <span class="hljs-number">0.08</span><span class="hljs-comment">;            // $0.08 gas cost for one claim</span>
const <span class="hljs-attr">GAS_EACH_FOLLOWING_CLAIM_USD</span> = <span class="hljs-number">0.02</span><span class="hljs-comment">; // $0.02 extra gas cost for each following claim</span>

interface ClaimInfo {
  claimCount: number<span class="hljs-comment">;</span>
  minVrgdaFeePerClaim: string<span class="hljs-comment">;</span>
}

const <span class="hljs-attr">getClaimInfo</span> = async (
  claimerContract: Contract,
  tierIndex: number,
  claims: Claim<span class="hljs-section">[]</span>,
): Promise&#x3C;ClaimInfo> => {
  let <span class="hljs-attr">claimCount</span> = <span class="hljs-number">0</span><span class="hljs-comment">;</span>
  let <span class="hljs-attr">claimFeesUsd</span> = <span class="hljs-number">0</span><span class="hljs-comment">;</span>
  let <span class="hljs-attr">totalCostUsd</span> = <span class="hljs-number">0</span><span class="hljs-comment">;</span>
  let <span class="hljs-attr">prevNetFeesUsd</span> = <span class="hljs-number">0</span><span class="hljs-comment">;</span>
  let <span class="hljs-attr">claimFees</span> = BigNumber.from(<span class="hljs-number">0</span>)<span class="hljs-comment">;</span>
  let minVrgdaFees: BigNumber<span class="hljs-section">[]</span> = <span class="hljs-section">[]</span><span class="hljs-comment">;</span>
  for (let <span class="hljs-attr">numClaims</span> = <span class="hljs-number">1</span><span class="hljs-comment">; numClaims &#x3C;= claims.length; numClaims++) {</span>
    const <span class="hljs-attr">nextClaimFees</span> = await claimerContract.functions<span class="hljs-string">'computeTotalFees(uint8,uint256)'</span><span class="hljs-comment">;</span>

    const <span class="hljs-attr">totalCostUsd</span> =
      <span class="hljs-attr">numClaims</span> === <span class="hljs-number">1</span>
        ? GAS_ONE_CLAIM_USD
        : GAS_ONE_CLAIM_USD + GAS_EACH_FOLLOWING_CLAIM_USD * numClaims<span class="hljs-comment">;</span>

    const <span class="hljs-attr">claimFeesFormatted</span> = formatUnits(claimFees.toString(), FEE_TOKEN_DECIMALS)<span class="hljs-comment">;</span>
    <span class="hljs-attr">claimFeesUsd</span> = parseFloat(claimFeesFormatted) * FEE_TOKEN_ASSET_RATE_USD<span class="hljs-comment">;</span>

    const <span class="hljs-attr">nextClaimFeesFormatted</span> = formatUnits(claimFees.toString(), FEE_TOKEN_DECIMALS)<span class="hljs-comment">;</span>
    const <span class="hljs-attr">nextClaimFeesUsd</span> = parseFloat(nextClaimFeesFormatted) * FEE_TOKEN_ASSET_RATE_USD<span class="hljs-comment">;</span>

    const <span class="hljs-attr">netFeesUsd</span> = nextClaimFeesUsd - totalCostUsd<span class="hljs-comment">;</span>

    if (netFeesUsd > prevNetFeesUsd &#x26;&#x26; netFeesUsd > MIN_PROFIT_USD) {
      <span class="hljs-attr">prevNetFeesUsd</span> = netFeesUsd<span class="hljs-comment">;</span>
      <span class="hljs-attr">claimCount</span> = numClaims<span class="hljs-comment">;</span>
      <span class="hljs-attr">claimFees</span> = nextClaimFees<span class="hljs-comment">;</span>
      <span class="hljs-attr">claimFeesUsd</span> = nextClaimFeesUsd<span class="hljs-comment">;</span>

      minVrgdaFees.push(nextClaimFees)<span class="hljs-comment">;</span>
    } else {
      break<span class="hljs-comment">;</span>
    }
  }
  
  // Sort array of BigNumbers by comparing them using basic JS built-in sort()
  minVrgdaFees.sort((a: BigNumber, b: BigNumber) => (a.gt(b) ? 1 : -1))<span class="hljs-comment">;</span>

  // Take the lowest BigNumber as the lowest fee we will accept
  const <span class="hljs-attr">minVrgdaFeePerClaim</span> = Boolean(minVrgdaFees[<span class="hljs-number">0</span>]) ? minVrgdaFees[<span class="hljs-number">0</span>].toString() : <span class="hljs-string">'0'</span><span class="hljs-comment">;</span>
  
  console.log(`$${netProfitUsd} = ($${claimFeesUsd} - $${totalCostUsd})`)<span class="hljs-comment">;</span>

  return { claimCount, minVrgdaFeePerClaim }<span class="hljs-comment">;</span>
}<span class="hljs-comment">;</span>
</code></pre><p>Whew!</p><p>On to the last step.</p><h2 id="h-4-execute-profitable-claims" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">4. Execute Profitable Claims</h2><p>Finally, we need to determine if there are any profitable claims based on the params list returned to us by the <code>calculateProfit()</code> function, and if there are use the OpenZeppelin relayer to send a transaction to the blockchain.</p><p>Of course, here you can swap out the OpenZeppelin relayer for whichever method you prefer for sending transactions:</p><pre data-type="codeBlock" text="const executeTransaction = async (claimPrizesParams, claimerContract: Contract) =&gt; {
  // It&apos;s profitable if there is at least 1 claim to claim
  if (claimPrizesParams.winners.length &gt; 0) {
    const populatedTx = await claimerContract.populateTransaction.claimPrizes(
      ...Object.values(claimPrizesParams),
    );

    const tx = await relayer.sendTransaction({
      data: populatedTx.data,
      to: populatedTx.to,
      gasLimit: 8000000,
    });
    console.log(&apos;Transaction hash:&apos;, tx.hash);
  } else {
    console.log(`Not profitable to claim Draw #${context.drawId}, Tier Index: #${tier}`);
  }
}
  
"><code>const executeTransaction <span class="hljs-operator">=</span> async (claimPrizesParams, claimerContract: Contract) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-comment">// It's profitable if there is at least 1 claim to claim</span>
  <span class="hljs-keyword">if</span> (claimPrizesParams.winners.<span class="hljs-built_in">length</span> <span class="hljs-operator">></span> <span class="hljs-number">0</span>) {
    const populatedTx <span class="hljs-operator">=</span> await claimerContract.populateTransaction.claimPrizes(
      ...Object.values(claimPrizesParams),
    );

    const <span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> await relayer.sendTransaction({
      data: populatedTx.data,
      to: populatedTx.to,
      gasLimit: <span class="hljs-number">8000000</span>,
    });
    console.log(<span class="hljs-string">'Transaction hash:'</span>, <span class="hljs-built_in">tx</span>.hash);
  } <span class="hljs-keyword">else</span> {
    console.log(`Not profitable to claim Draw #${context.drawId}, Tier Index: #${tier}`);
  }
}
  
</code></pre><h2 id="h-5-withdrawing-claim-rewards" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">5. Withdrawing Claim Rewards</h2><p>Last but not least, every now and then you will need to withdraw the rewards you have earned from the PrizePool. There is an ancillary bot written to take care of that as well, which you can find a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/withdraw-claim-rewards"><strong>reference implementation of here</strong></a>.</p><h2 id="h-finishing-up" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Finishing Up</h2><p>And with that, we have a bot that is capable of profiting from claiming prizes on behalf of PoolTogether v5 depositors. We’re impressed you made it this far! 👏</p><p>In <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/ES-IJduktYPb0X_sBikfqL-PVFRweNpoPrlr01zcVX8"><strong>Part 1</strong></a>, we looked at making liquidations with our bots. In <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/1o-d_ScnJ8F0cer5SRmILMSPxTCn4vlWgN7fkU4FD4o"><strong>Part 3</strong></a> we will look at how to earn by helping with the RNG (random number generation) and relay (bridging the random number to other chains) Draw Auction bot.</p><p>If you have any questions feel free to reach out to anyone in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pooltogether.com/discord"><strong>PoolTogether Discord community</strong></a>. The <code>#developers</code> channel is your best bet!</p>]]></content:encoded>
            <author>chuck-bergeron-generation-software@newsletter.paragraph.com (Chuck Bergeron - Generation Software)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/4fe2cea6254f83c64d5d938feb00c4e1852f03bf01efa4c2fa30fff16d1bcef8.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Part 1. Creating a PoolTogether Arbitrage Swapping bot]]></title>
            <link>https://paragraph.com/@chuck-bergeron-generation-software/part-1-creating-a-pooltogether-arbitrage-swapping-bot</link>
            <guid>GTwDMjVE53uqQ6B0dj9D</guid>
            <pubDate>Tue, 19 Sep 2023 23:13:19 GMT</pubDate>
            <description><![CDATA[or How to Spin the Gears of the PoolTogether Ecosystem For Fun and Profit*Tutorial duration: ~20 minutes UPDATE: This is the old method of running bots. There is a lot of good information in these blog posts but they may differ from the new bot code. See the Cabana docs for the latest on G9’s PoolTogether bots: https://docs.cabana.fi/protocol/botsWhy bots?The new version of PoolTogether (called the hyperstructure) features incentivization in place of governance. This ensures that the protocol...]]></description>
            <content:encoded><![CDATA[<h2 id="h-or-how-to-spin-the-gears-of-the-pooltogether-ecosystem-for-fun-and-profit" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">or How to Spin the Gears of the PoolTogether Ecosystem For Fun and Profit*</h2><p><strong><em>Tutorial duration:</em></strong> ~20 minutes</p><p><strong>UPDATE:</strong> This is the old method of running bots. There is a lot of good information in these blog posts but they may differ from the new bot code. See the Cabana docs for the latest on G9’s PoolTogether bots:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.cabana.fi/protocol/bots">https://docs.cabana.fi/protocol/bots</a></p><h2 id="h-why-bots" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why bots?</h2><p>The new version of PoolTogether (called the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://jacob.energy/hyperstructures.html"><strong>hyperstructure</strong></a>) features incentivization in place of governance. This ensures that the protocol can exist and function for many generations to come without the need for any voters or admin staff.</p><p>In order for this to work, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Mechanism_design"><strong>mechanism design</strong></a> was baked into the protocol so that those with technical know-how (or those interested in learning) of running bots are incentivized to do so.</p><p>In this tutorial we will be walking through creating and running a successful Arbitrage Swap <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.logrocket.com/tools-smart-contract-automation-guide/#:~:text=how%20they%20work.-,Chainlink%20Keepers,transactions%20based%20on%20time%20intervals."><strong>bot</strong></a> — this bot will run every few minutes, find profitable trades, and autonomously submit those transactions.</p><p>Where this differs from other arbitrage bots is that it does not run transactions on a typical DEX (such as Uniswap), but instead only submits transactions to run on the new PoolTogether Liquidation contracts. The Liquidator exists simply to convert yield that a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://v4.docs.pooltogether.com/protocol/next/design/vaults"><strong>Prize Vault</strong></a> has accrued into Prize Tokens.</p><p>—</p><p><strong><em>Note:</em></strong> We have created a bot (which runs on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.openzeppelin.com/defender"><strong>OpenZeppelin Defender</strong></a>, but you can use whichever cron/relay service you like) which you can use as a reference here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/arb-liquidator"><strong>https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/tree/main/packages/arb-liquidator</strong></a></p><p>These tutorials expect you have intermediate knowledge of programming concepts and JavaScript/TypeScript. We’ll use <code>ethers.js</code>, but you could write these bots using <code>web3.js</code>, <code>Web3.py</code>, etc.</p><p>Also, there is a JavaScript utils library for interacting with the <code>PrizePool</code> and associated contracts here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-utils-js"><strong>https://github.com/GenerationSoftware/pt-v5-utils-js</strong></a></p><h2 id="h-the-liquidator" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Liquidator</h2><p>Depositors in PoolTogether can deposit any ERC-20 crypto tokens so long as they have an <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://eips.ethereum.org/EIPS/eip-4626"><strong>EIP-4626 Vault</strong></a> to receive them. While the tokens are stored in the vault, they accrue yield (ie. when you deposit USDC to an Aave vault you receive Staked Aave DAI in return, an <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.xdefi.io/article/yield-bearing-assets/#:~:text=Yield-bearing%20assets%20are%20DeFi,returns%20on%20some%20DeFi%20protocols."><strong>interest-bearing asset</strong></a>). This yield needs to be continually liquidated into prize tokens (currently the prize token is <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/token/0x0cec1a9154ff802e7934fc916ed7ca50bde6844e"><strong>POOL</strong></a>), so that depositors who win prizes will be compensated in <strong>POOL</strong> instead of the interest-bearing asset.</p><p>The Liquidator has been designed so that anyone can run these swaps and profit from them. The bot sends POOL prize tokens and receives prize asset tokens in return.</p><h2 id="h-liquidation-pairs" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Liquidation Pairs</strong></h2><p>Liquidation Pairs are the mechanism by which yield is liquidated. Each PoolTogether Vault will have one or more associated Liquidation Pairs. A Liquidation Pair is like a Uniswap pair, but it only supports swaps in a single direction.</p><h2 id="h-to-arbitrage-yield-you-would-follow-these-steps" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">To arbitrage yield, you would follow these steps:</h2><ol><li><p><strong>Find Liquidation Pairs you would like to arb</strong></p></li><li><p><strong>Calculate most profitable swap</strong></p></li><li><p><strong>If profitable, execute swap</strong></p></li></ol><h2 id="h-1-finding-liquidation-pairs" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">1. Finding Liquidation Pairs</h2><p>All of the Liquidation Pairs are created by the Liquidation Pair Factory. We can use that factory to list and iterate through each Liquidation Pair:</p><pre data-type="codeBlock" text="const liquidationPairFactoryContract = new ethers.Contract(
  liquidationPairFactoryAddress,
  LiquidationPairFactoryAbi,
  readProvider
);
let liquidationPairContracts: Contract[] = [];

const numPairs = await liquidationPairFactoryContract.totalPairs();

for (let i = 0; i &lt; numPairs; i++) {
  const liquidationPairAddress = 
        await liquidationPairFactoryContract.allPairs(i);
  const liquidationPairContract = new ethers.Contract(
    liquidationPairAddress,
    LiquidationPairAbi,
    readProvider
  );
  liquidationPairContracts.push(liquidationPairContract);
}
"><code>const liquidationPairFactoryContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(
  liquidationPairFactoryAddress,
  LiquidationPairFactoryAbi,
  readProvider
);
let liquidationPairContracts: Contract[] <span class="hljs-operator">=</span> [];

const numPairs <span class="hljs-operator">=</span> await liquidationPairFactoryContract.totalPairs();

<span class="hljs-keyword">for</span> (let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> numPairs; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
  const liquidationPairAddress <span class="hljs-operator">=</span> 
        await liquidationPairFactoryContract.allPairs(i);
  const liquidationPairContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(
    liquidationPairAddress,
    LiquidationPairAbi,
    readProvider
  );
  liquidationPairContracts.<span class="hljs-built_in">push</span>(liquidationPairContract);
}
</code></pre><p><strong><em>Note:</em></strong> When this was written everything was being run against the Beta contracts. The list of Beta contract addresses and ABIs can be found here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-beta/tree/main/deployments"><strong>https://github.com/GenerationSoftware/pt-v5-beta/tree/main/deployments</strong></a></p><h2 id="h-2-calculate-most-profitable-swap" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">2. Calculate Most Profitable Swap</h2><p>In order to make the decision if we should execute the swap or not, and for how much of the input token we will need to find out the current available liquidity, the cost of the POOL we will be sending vs. the value of interest-bearing tokens we will receive in return, and the gas fee for the transaction.</p><p>To compare with Uniswap’s language, POOL (the prize token) is called <code>tokenIn</code>. The prize asset token (such as PTUSDC, PTWETH) is <code>tokenOut</code>. And the <code>underlyingAssetToken</code> is the original token deposited into the PoolTogether vault (the unwrapped interest-bearing token: eg. USDC, wETH, etc.).</p><p>—</p><p><em>Note:</em> We’ll leave out how to get the gas cost and token values in USD for the chain you’re working on. You can view the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/blob/dfdef6ac5b0183edadee2275ec77c4cb92ae8544/packages/library/src/liquidatorArbitrageSwap.ts#L523"><strong>reference implementation</strong></a> to see how this is done using <code>eth_gasPrice</code>, <code>gasEstimate</code> on contract calls from ethers.js and the USD value of the tokens from a price feed such as Coingecko or Covalent, etc.</p><p>The liquidator uses a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2022/04/gda"><strong>CGDA</strong></a> (continuous gradual dutch auction) to auction off yield.</p><p>Let’s start by figuring out how much POOL we will need to supply. We can do this by making <code>callStatic</code>calls (<code>callStatic</code>simulates an <code>external</code> write function instead of actually sending a transaction) to the LiquidationPair’s <code>maxAmountOut()</code> (the maximum amount available to receive in return for POOL), and<code>computeExactAmountIn()</code>(how much POOL we need to supply):</p><pre data-type="codeBlock" text="const maxAmountOut = await liquidationPairContract.callStatic.maxAmountOut();

const exactAmountIn = await liquidationPairContract.callStatic.computeExactAmountIn(
  wantedAmountOut,
);
"><code>const <span class="hljs-attr">maxAmountOut</span> = await liquidationPairContract.callStatic.maxAmountOut()<span class="hljs-comment">;</span>

const <span class="hljs-attr">exactAmountIn</span> = await liquidationPairContract.callStatic.computeExactAmountIn(
  wantedAmountOut,
)<span class="hljs-comment">;</span>
</code></pre><p>However, it’s not efficient or most profitable to always swap the maximum amount out for the maximum amount in, so we will want a way to calculate a more profitable swap. The easiest way to compute the best swap is to first get the <code>maxAmountOut</code> that we can receive right now, then split that up into many data points (we use 100 below) to find the optimal <code>amountOut</code>:</p><pre data-type="codeBlock" text="const { originalMaxAmountOut, wantedAmountsOut } = await calculateAmountOut(
  liquidationPairContract,
  context,
);

// Calculates necessary input parameters for the swap call based on current state
// of the contracts
const calculateAmountOut = async (
  liquidationPair: Contract,
  context: ArbLiquidatorContext,
): Promise&lt;{
  originalMaxAmountOut: BigNumber;
  wantedAmountsOut: BigNumber[];
}&gt; =&gt; {
  const wantedAmountsOut = [];
  const amountOut = await liquidationPair.callStatic.maxAmountOut();

  // Get multiple data points across the auction function to determine
  // the most amount of profitability (most amount out for least amount
  // of token in depending on the state of the gradual auction)
  for (let i = 1; i &lt;= 100; i++) {
    const amountToSendPercent = i;
    const wantedAmountOut = amountOut
      .mul(ethers.BigNumber.from(amountToSendPercent))
      .div(100);

    wantedAmountsOut.push(wantedAmountOut);
  }

  return {
    originalMaxAmountOut: amountOut,
    wantedAmountsOut,
  };
};
"><code>const { originalMaxAmountOut, wantedAmountsOut } <span class="hljs-operator">=</span> await calculateAmountOut(
  liquidationPairContract,
  context,
);

<span class="hljs-comment">// Calculates necessary input parameters for the swap call based on current state</span>
<span class="hljs-comment">// of the contracts</span>
const calculateAmountOut <span class="hljs-operator">=</span> async (
  liquidationPair: Contract,
  context: ArbLiquidatorContext,
): Promise<span class="hljs-operator">&#x3C;</span>{
  originalMaxAmountOut: BigNumber;
  wantedAmountsOut: BigNumber[];
}<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const wantedAmountsOut <span class="hljs-operator">=</span> [];
  const amountOut <span class="hljs-operator">=</span> await liquidationPair.callStatic.maxAmountOut();

  <span class="hljs-comment">// Get multiple data points across the auction function to determine</span>
  <span class="hljs-comment">// the most amount of profitability (most amount out for least amount</span>
  <span class="hljs-comment">// of token in depending on the state of the gradual auction)</span>
  <span class="hljs-keyword">for</span> (let i <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; i <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-number">100</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
    const amountToSendPercent <span class="hljs-operator">=</span> i;
    const wantedAmountOut <span class="hljs-operator">=</span> amountOut
      .mul(ethers.BigNumber.from(amountToSendPercent))
      .div(<span class="hljs-number">100</span>);

    wantedAmountsOut.<span class="hljs-built_in">push</span>(wantedAmountOut);
  }

  <span class="hljs-keyword">return</span> {
    originalMaxAmountOut: amountOut,
    wantedAmountsOut,
  };
};
</code></pre><p>We can then use the output from these 100 data points (stored in <code>wantedAmountsOut</code>) to find their corresponding <code>wantedAmountsIn</code>:</p><pre data-type="codeBlock" text="import { BigNumber, ethers } from &apos;ethers&apos;;
import { Contract } from &apos;ethers&apos;;
import { Provider } from &apos;@ethersproject/providers&apos;;
import { getLiquidationPairComputeExactAmountInMulticall }
  from &apos;@generationsoftware/pt-v5-autotasks-library&apos;;

const { amountIn, amountInMin, wantedAmountsIn } = await calculateAmountIn(
  readProvider,
  liquidationPairContract,
  originalMaxAmountOut,
  wantedAmountsOut,
);

// Calculates optimal input parameters for the swap call based on current
// state of the auction
const calculateAmountIn = async (
  readProvider: Provider,
  liquidationPairContract: Contract,
  originalMaxAmountOut: BigNumber,
  wantedAmountsOut: BigNumber[],
): Promise&lt;{
  amountIn: BigNumber;
  amountInMin: BigNumber;
  wantedAmountsIn: BigNumber[];
}&gt; =&gt; {
  let wantedAmountsIn = [];

  const amountIn: BigNumber = await liquidationPairContract
    .callStatic.computeExactAmountIn(originalMaxAmountOut);

  const amountInMin = ethers.constants.MaxInt256;

  wantedAmountsIn = await getLiquidationPairComputeExactAmountInMulticall(
    liquidationPairContract,
    wantedAmountsOut,
    readProvider,
  );

  return {
    amountIn,
    amountInMin,
    wantedAmountsIn,
  };
};
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">BigNumber</span>, <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">Contract</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">Provider</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@ethersproject/providers'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">getLiquidationPairComputeExactAmountInMulticall</span> }
  <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@generationsoftware/pt-v5-autotasks-library'</span>;

const { amountIn, amountInMin, wantedAmountsIn } <span class="hljs-operator">=</span> await calculateAmountIn(
  readProvider,
  liquidationPairContract,
  originalMaxAmountOut,
  wantedAmountsOut,
);

<span class="hljs-comment">// Calculates optimal input parameters for the swap call based on current</span>
<span class="hljs-comment">// state of the auction</span>
const calculateAmountIn <span class="hljs-operator">=</span> async (
  readProvider: Provider,
  liquidationPairContract: Contract,
  originalMaxAmountOut: BigNumber,
  wantedAmountsOut: BigNumber[],
): Promise<span class="hljs-operator">&#x3C;</span>{
  amountIn: BigNumber;
  amountInMin: BigNumber;
  wantedAmountsIn: BigNumber[];
}<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  let wantedAmountsIn <span class="hljs-operator">=</span> [];

  const amountIn: BigNumber <span class="hljs-operator">=</span> await liquidationPairContract
    .callStatic.computeExactAmountIn(originalMaxAmountOut);

  const amountInMin <span class="hljs-operator">=</span> ethers.constants.MaxInt256;

  wantedAmountsIn <span class="hljs-operator">=</span> await getLiquidationPairComputeExactAmountInMulticall(
    liquidationPairContract,
    wantedAmountsOut,
    readProvider,
  );

  <span class="hljs-keyword">return</span> {
    amountIn,
    amountInMin,
    wantedAmountsIn,
  };
};
</code></pre><p>—</p><p><em>Note:</em> This is using multicall reads to speed up network calls. Instead of doing 100 RPC calls, we can do 1 RPC call with 100 queries. See the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/GenerationSoftware/pt-v5-autotasks-monorepo/blob/dfdef6ac5b0183edadee2275ec77c4cb92ae8544/packages/library/src/utils/getLiquidationPairComputeExactAmountInMulticall.ts"><strong>implementation here</strong></a>.</p><p>Let’s now pass the arrays of amounts in and amounts out to the a <code>calculateProfit</code> helper function. This will create a new array with the gross profit of all 100 data points, which we can then compare (with <code>Math.max()</code>) to find the maximum profit:</p><pre data-type="codeBlock" text="import { ethers, BigNumber } from &apos;ethers&apos;;

const { profitable, selectedIndex } = await calculateProfit(wantedAmountsIn, wantedAmountsOut);

// Calculates the amount of profit the bot will make on this swap
// and if it&apos;s profitable or not
const calculateProfit = async (
  wantedAmountsIn: BigNumber[],
  wantedAmountsOut: BigNumber[],
): Promise&lt;{ profitable: boolean; selectedIndex: number }&gt; =&gt; {
  console.log(&apos;Gross profit = tokenOut - tokenIn&apos;);

  const grossProfitsUsd = [];
  for (let i = 0; i &lt; wantedAmountsIn.length; i++) {
    const amountOut = wantedAmountsOut[i];
    const amountIn = wantedAmountsIn[i];

    const underlyingAssetTokenUsd =
      parseFloat(ethers.utils.formatUnits(amountOut, tokenOut.decimals)) *
      underlyingAssetToken.assetRateUsd;
    const tokenInUsd =
      parseFloat(ethers.utils.formatUnits(amountIn, tokenIn.decimals)) * tokenIn.assetRateUsd;

    const grossProfitUsd = underlyingAssetTokenUsd - tokenInUsd;

    console.log(`Index ${i}: $${grossProfitUsd} = $${underlyingAssetTokenUsd} - $${tokenInUsd}`);

    grossProfitsUsd.push(grossProfitUsd);
  }

  const getMaxGrossProfit = (grossProfitsUsd: number[]) =&gt; {
    const max = grossProfitsUsd.reduce((a, b) =&gt; Math.max(a, b), -Infinity);
    return { maxGrossProfit: max, selectedIndex: grossProfitsUsd.indexOf(max) };
  };

  const { selectedIndex, maxGrossProfit } = getMaxGrossProfit(grossProfitsUsd);
  console.log(`Selected Index ${selectedIndex} - $${maxGrossProfit}`);

  // Compare the profit with the gas costs to learn if transaction will be profitable
  const estimatedProfitUsd = maxGrossProfit - gasFeeUsd;
  const profitable = estimatedProfitUsd &gt; 0;

  return { profitable, selectedIndex };
};
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">ethers</span>, <span class="hljs-title">BigNumber</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>;

const { profitable, selectedIndex } <span class="hljs-operator">=</span> await calculateProfit(wantedAmountsIn, wantedAmountsOut);

<span class="hljs-comment">// Calculates the amount of profit the bot will make on this swap</span>
<span class="hljs-comment">// and if it's profitable or not</span>
const calculateProfit <span class="hljs-operator">=</span> async (
  wantedAmountsIn: BigNumber[],
  wantedAmountsOut: BigNumber[],
): Promise<span class="hljs-operator">&#x3C;</span>{ profitable: boolean; selectedIndex: number }<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  console.log(<span class="hljs-string">'Gross profit = tokenOut - tokenIn'</span>);

  const grossProfitsUsd <span class="hljs-operator">=</span> [];
  <span class="hljs-keyword">for</span> (let i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> wantedAmountsIn.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
    const amountOut <span class="hljs-operator">=</span> wantedAmountsOut[i];
    const amountIn <span class="hljs-operator">=</span> wantedAmountsIn[i];

    const underlyingAssetTokenUsd <span class="hljs-operator">=</span>
      parseFloat(ethers.utils.formatUnits(amountOut, tokenOut.decimals)) <span class="hljs-operator">*</span>
      underlyingAssetToken.assetRateUsd;
    const tokenInUsd <span class="hljs-operator">=</span>
      parseFloat(ethers.utils.formatUnits(amountIn, tokenIn.decimals)) <span class="hljs-operator">*</span> tokenIn.assetRateUsd;

    const grossProfitUsd <span class="hljs-operator">=</span> underlyingAssetTokenUsd <span class="hljs-operator">-</span> tokenInUsd;

    console.log(`Index ${i}: $${grossProfitUsd} <span class="hljs-operator">=</span> $${underlyingAssetTokenUsd} <span class="hljs-operator">-</span> $${tokenInUsd}`);

    grossProfitsUsd.<span class="hljs-built_in">push</span>(grossProfitUsd);
  }

  const getMaxGrossProfit <span class="hljs-operator">=</span> (grossProfitsUsd: number[]) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    const max <span class="hljs-operator">=</span> grossProfitsUsd.reduce((a, b) <span class="hljs-operator">=</span><span class="hljs-operator">></span> Math.<span class="hljs-built_in">max</span>(a, b), <span class="hljs-operator">-</span>Infinity);
    <span class="hljs-keyword">return</span> { maxGrossProfit: max, selectedIndex: grossProfitsUsd.indexOf(max) };
  };

  const { selectedIndex, maxGrossProfit } <span class="hljs-operator">=</span> getMaxGrossProfit(grossProfitsUsd);
  console.log(`Selected Index ${selectedIndex} <span class="hljs-operator">-</span> $${maxGrossProfit}`);

  <span class="hljs-comment">// Compare the profit with the gas costs to learn if transaction will be profitable</span>
  const estimatedProfitUsd <span class="hljs-operator">=</span> maxGrossProfit <span class="hljs-operator">-</span> gasFeeUsd;
  const profitable <span class="hljs-operator">=</span> estimatedProfitUsd <span class="hljs-operator">></span> <span class="hljs-number">0</span>;

  <span class="hljs-keyword">return</span> { profitable, selectedIndex };
};
</code></pre><p>This will output something like the following:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/73ec1aa51d01547ec4ad10ef5a0bf2ab4c1ab475ff01eab6ced3069adf3de8ac.webp" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Example console.log output of all the data point comparisons for finding the optimal profit.</p><p>In this case data point 21 (with index 20) was the most profitable. If we wanted to run it to earn the $0.11 of profit we could, but it would likely be best to wait until we are further along in the auction when the gross profit will be higher.</p><h2 id="h-3-executing-swaps" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">3. Executing Swaps</h2><p>When we find a profitable swap, we can execute the swap using the Liquidation Router. The Router provides one function to do so: <code>swapExactAmountOut()</code> allows the caller to define the expected number of output tokens.</p><p>We have been using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.openzeppelin.com/defender"><strong>OpenZeppelin Defender</strong></a> to run our bots every <em>n</em> minutes, which is where our transaction<code>relayer</code> instance comes from. However you could do this using ethers.js providers, a web3.js provider, Gelato, Chainlink Keepers, etc.</p><p>Next let’s check the relayer account’s POOL balance:</p><pre data-type="codeBlock" text="// Your Relayer (the EOA or &quot;externally owned account&quot;) will be swapping POOL 
// tokens, so it will need to have a POOL balance.
const relayerAddress = &apos;0x49ca801A80e31B1ef929eAB13Ab3FBbAe7A55e8F&apos;;

// Check if tokenIn balance for relayer (bot) account is sufficient
const tokenInContract = new ethers.Contract(tokenInAddress, ERC20Abi, writeProvider);
const tokenInBalance = await tokenInContract.balanceOf(relayerAddress);
const sufficientBalance = tokenInBalance.gt(exactAmountIn);

if (!sufficientBalance) {
  console.warn(&apos;Insufficient POOL balance.&apos;)
}
"><code><span class="hljs-comment">// Your Relayer (the EOA or "externally owned account") will be swapping POOL </span>
<span class="hljs-comment">// tokens, so it will need to have a POOL balance.</span>
const relayerAddress <span class="hljs-operator">=</span> <span class="hljs-string">'0x49ca801A80e31B1ef929eAB13Ab3FBbAe7A55e8F'</span>;

<span class="hljs-comment">// Check if tokenIn balance for relayer (bot) account is sufficient</span>
const tokenInContract <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ethers.Contract(tokenInAddress, ERC20Abi, writeProvider);
const tokenInBalance <span class="hljs-operator">=</span> await tokenInContract.balanceOf(relayerAddress);
const sufficientBalance <span class="hljs-operator">=</span> tokenInBalance.gt(exactAmountIn);

<span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>sufficientBalance) {
  console.warn(<span class="hljs-string">'Insufficient POOL balance.'</span>)
}
</code></pre><p>If the allowance of POOL for the relayer to the LiquidationRouter hasn’t been set previously, we will need to set that as well:</p><pre data-type="codeBlock" text="// Get allowance approval
const allowance = await tokenInContract.allowance(
  relayerAddress,
  liquidationRouterContract.address
);

// Note that we&apos;re setting it to the max allowance as we trust the security
// audits of the LiquidationRouter contract
if (allowance.lt(exactAmountIn)) {
  const tx = await tokenInContract.approve(
    liquidationRouterContract.address,
    ethers.constants.MaxInt256
  );
  await tx.wait();

  const newAllowanceResult = await tokenInContract.allowance(
    relayerAddress,
    liquidationRouterContract.address,
  );
  console.log(&apos;New allowance:&apos;, newAllowanceResult[0].toString());
} else {
  console.log(&apos;Sufficient allowance ✔&apos;);
}
"><code><span class="hljs-comment">// Get allowance approval</span>
const allowance <span class="hljs-operator">=</span> await tokenInContract.allowance(
  relayerAddress,
  liquidationRouterContract.<span class="hljs-built_in">address</span>
);

<span class="hljs-comment">// Note that we're setting it to the max allowance as we trust the security</span>
<span class="hljs-comment">// audits of the LiquidationRouter contract</span>
<span class="hljs-keyword">if</span> (allowance.lt(exactAmountIn)) {
  const <span class="hljs-built_in">tx</span> <span class="hljs-operator">=</span> await tokenInContract.approve(
    liquidationRouterContract.<span class="hljs-built_in">address</span>,
    ethers.constants.MaxInt256
  );
  await <span class="hljs-built_in">tx</span>.wait();

  const newAllowanceResult <span class="hljs-operator">=</span> await tokenInContract.allowance(
    relayerAddress,
    liquidationRouterContract.<span class="hljs-built_in">address</span>,
  );
  console.log(<span class="hljs-string">'New allowance:'</span>, newAllowanceResult[<span class="hljs-number">0</span>].toString());
} <span class="hljs-keyword">else</span> {
  console.log(<span class="hljs-string">'Sufficient allowance ✔'</span>);
}
</code></pre><p>With the allowance taken care of, we can create and send the transaction:</p><pre data-type="codeBlock" text="if (profitable) {
  const transactionPopulated = await 
    liquidationRouterContract.populateTransaction.swapExactAmountOut(
      liquidationPair.address,
      swapRecipient,
      wantedAmountsOut[selectedIndex],
      amountInMin,
      Math.floor(Date.now() / 1000) + 100 // deadline
    );

  const transactionSentToNetwork = await relayer.sendTransaction({
    data: transactionPopulated.data,
    to: transactionPopulated.to,
    gasLimit: 600000,
  });
  console.log(&apos;Transaction hash:&apos;, transactionSentToNetwork.hash);
}
"><code><span class="hljs-keyword">if</span> (profitable) {
  const transactionPopulated <span class="hljs-operator">=</span> await 
    liquidationRouterContract.populateTransaction.swapExactAmountOut(
      liquidationPair.<span class="hljs-built_in">address</span>,
      swapRecipient,
      wantedAmountsOut[selectedIndex],
      amountInMin,
      Math.floor(Date.now() <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>) <span class="hljs-operator">+</span> <span class="hljs-number">100</span> <span class="hljs-comment">// deadline</span>
    );

  const transactionSentToNetwork <span class="hljs-operator">=</span> await relayer.sendTransaction({
    data: transactionPopulated.data,
    to: transactionPopulated.to,
    gasLimit: <span class="hljs-number">600000</span>,
  });
  console.log(<span class="hljs-string">'Transaction hash:'</span>, transactionSentToNetwork.hash);
}
</code></pre><p><em>Note:</em> <code>swapExactAmountOut()</code> exists on both the LiquidationPair contracts and the LiquidationRouter, however for your swaps to be successful you will need to run it on the LiquidationRouter.</p><h2 id="h-finishing-up" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Finishing Up</h2><p>Hopefully that gives you a good rundown of how to build an Arbitrage Liquidations swap bot with the new PoolTogether (hyperstructure) protocol.</p><p>In <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/xPSEh1pfjV2IT1yswcsjN2gBBrVf548V8q9W23xxA8U"><strong>Part 2</strong></a> we will look at Prize Claiming bots - another way to pocket profit while automating the claiming of prizes every day on behalf of winners.</p><p>In <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/chuckbergeron-g9.eth/1o-d_ScnJ8F0cer5SRmILMSPxTCn4vlWgN7fkU4FD4o"><strong>Part 3</strong></a> we break down the Draw Auction bot, yet another way to profit by helping with the RNG (random number creation and bridging) process.</p><p>If you have any questions feel free to reach out to anyone in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pooltogether.com/discord"><strong>PoolTogether Discord community</strong></a>. The <code>#developers</code> channel is your best bet!</p><p>Happy arbing!</p><hr><blockquote><p>* Profit is not guaranteed. Make sure to monitor and tweak your bot code and settings to nail your profit margins!</p></blockquote>]]></content:encoded>
            <author>chuck-bergeron-generation-software@newsletter.paragraph.com (Chuck Bergeron - Generation Software)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/e3e8eefccf90a67c197ab2d78555dde8abc2960359227f9c4d07ac125a951f69.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>