Run Keep3r on Fantom?

Hey there

do you have any documentation to run Keep3r with Fantom?

Bolo

1 Like

Hi @Bolo,

We don’t as far as I know. I assume it is similar to running a Keep3r on Ethereum mainnet.

I found the following for Fantom:

Hey @bolo, if the network running on Fantom is equivalent to the one on Mainnet, it should not be too difficult to add support for it. Can you point us to the Keeper registry contract on the Fantom network, or to some documentation on Fantom’s side?

1 Like

I have found this.

1 Like

According to ftmscan, the token has zero total supply and no holders, so it seems like it’s still not operational. I’ve also reached out to Andre to see if he can provide more info.

1 Like

I don’t even see the method bound() :confused:

hey @spalladino I have started to update the code for FTM

  const { ethers } = require("ethers");
const { DefenderRelaySigner, DefenderRelayProvider } = require('defender-relay-client/lib/ethers');

// ABIs for jobs and registry (contain only the methods needed, not the full ABIs of the contracts)
const ABIs = {
    SushiswapV1Oracle:[[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"KP3R","outputs":[{"internalType":"contract IKeep3rV1","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WFTM","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address","name":"tokenOut","type":"address"}],"name":"current","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pair","type":"address"}],"name":"lastObservation","outputs":[{"components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"price0Cumulative","type":"uint256"},{"internalType":"uint256","name":"price1Cumulative","type":"uint256"}],"internalType":"struct SushiswapV1Oracle.Observation","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pair","type":"address"}],"name":"observationLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"observations","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"uint256","name":"price0Cumulative","type":"uint256"},{"internalType":"uint256","name":"price1Cumulative","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"pairFor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"}],"name":"pairForWETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"pairs","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingGovernance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"periodSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"points","type":"uint256"}],"name":"prices","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"granularity","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"points","type":"uint256"},{"internalType":"uint256","name":"window","type":"uint256"}],"name":"sample","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"setGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"update","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"i","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"updateFor","outputs":[{"internalType":"bool","name":"updated","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pair","type":"address"}],"name":"updatePair","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"work","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"workForFree","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"workable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pair","type":"address"}],"name":"workable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]]
};

const Jobs = [
  {name: 'SushiswapV1Oracle', address: '0x654823397594d248f6182012d8F7cC17D51f496c', workableFn: 'workable', workFn: 'work' }
]

// Address for the Keeper registry
const RegistryAddress = '0x2a5062d22adcfaafbd5c541d4da82e4b450d4212';

// Running time of this autotask
const Duration = 55000;

// Pauses for milliseconds
async function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(() => resolve(), ms);
  });
}

// Checks if there is available work on the job and works it
async function workIfNeeded(contract, job) {
  if (await contract[job.workableFn]()) {
    console.log(`${job.name} is workable`);
    const tx = await contract[job.workFn]();
    console.log(`${job.name} worked: ${tx.hash}`);
    return true;
  }
  return false;
}

// Checks if there is available work on the job every 5s
// There is no need to check more often, since checking once per block is enough
// Avoid further increasing the frequency or it may lead to provider quota errors
async function tryWorking(signer, job, duration) {
  try {
    const start = Date.now();
    const contract = new ethers.Contract(job.address, ABIs[job.name], signer);
    while (Date.now() - start < duration) {
      if (await workIfNeeded(contract, job)) return;
      await sleep(duration / 11);
    }
    console.log(`${job.name} is not workable`);
  } catch (err) {
    console.log(`Error working ${job.name}: ${err.message ? err.message.slice(0, 100) : 'unknown error'}`);
  }
}

async function main(signer, jobs, duration, registryAddress) {
  console.log(jobs)
  // Check if relayer is a registered keeper
  const registry = new ethers.Contract(registryAddress, ABIs.Registry, signer);
  const keeperAddress = await signer.getAddress();
  const isKeeper = await registry.keepers(keeperAddress);
  if (!isKeeper) {
    console.log(`Relayer ${keeperAddress} has not yet been activated as a keep3r`);
    return;
  }

  // Monitor all jobs in parallel every 5 seconds and return after 1 min
  console.log(`Starting work...`);
  await Promise.all(jobs.map(job => tryWorking(signer, job, duration)));
}

// Entrypoint for the Autotask
exports.handler = async function(credentials) {
  const provider = new DefenderRelayProvider(credentials);;
  const signer = new DefenderRelaySigner(credentials, provider, { speed: 'fastest' });
  await main(signer, Jobs, Duration, RegistryAddress);
}

I guess i should use a specific provider for Fantom ?

new ethers.providers.StaticJsonRpcProvider(url)

1 Like

I think I found the correct token

1 Like

I have to try bound my Relay manually but it’s not working :frowning:

1 Like

If you connect your Autotask to a Fantom Relayer, you should get access to the network directly using a DefenderRelayProvider.

I've gone through the txs to that contract, and I'm not sure it's operational. The only txs I see are from the Deployer account for setting it up, and then a single failed bond operation (which I'm going to guess is yours). Did you get confirmation from the keep3r team or the fantom team that it's open to the public?

1 Like

There are only 2 keepers who was added manually with function addKeeper.

I have tried on forum but without success.

FTM team on discord nobody really use it.

1 Like