Using off-chain data

This page shows how to enrich your Mimic tasks with off‑chain data. You’ll learn three common patterns:

  1. Pricing convert token balances to USD

  2. Discovery of relevant tokens for a user across a chain and bulk‑transfer them

  3. Custom subgraph queries (e.g., fetch a Uniswap pool price)

We’ll walk through each pattern, the inputs they require, and implementation details you should keep in mind (precision, slippage, and fees).

Pre-reqs: You’ve already read the basic task guide and can mimic codegen, mimic compile, and mimic deploy.


Use price feeds to act on a USD threshold

Goal: Top up a recipient if their token balance (in USD) falls below a threshold.

import { BigInt, ERC20Token, log, TokenAmount, Transfer, USD } from '@mimicprotocol/lib-ts'

import { ERC20 } from './types/ERC20'
import { inputs } from './types'

export default function main(): void {
  const tokenContract = new ERC20(inputs.token, inputs.chainId)
  const balance = tokenContract.balanceOf(inputs.recipient)

  const token = ERC20Token.fromAddress(inputs.token, inputs.chainId)
  const balanceInUsd = TokenAmount.fromBigInt(token, balance).toUsd()
  const thresholdUsd = USD.fromStringDecimal(inputs.thresholdUsd)
  log.info(`Balance in USD: ${balanceInUsd}`)

  if (balanceInUsd.lt(thresholdUsd)) {
    const amount = BigInt.fromStringDecimal(inputs.amount, token.decimals)
    const maxFee = BigInt.fromStringDecimal(inputs.maxFee, token.decimals)
    Transfer.create(token, amount, inputs.recipient, maxFee).send()
  }
}

Notes

  • Convert all human‑readable decimals using BigInt.fromStringDecimal(value, decimals) to avoid precision loss.

  • maxFee is specified in token units here; pass a USD‑denominated cap instead by using TokenAmount.fromStringDecimal(DenominationToken.USD(), ...) (see next example).


Find relevant tokens and send them in one go

Goal: Detect which tokens a user actually holds on a chain and transfer any non‑zero balances to a recipient, paying a single USD‑capped fee.

import { DenominationToken, environment, ListType, log, TokenAmount, TransferBuilder, USD } from '@mimicprotocol/lib-ts'

import { inputs } from './types'

export default function main(): void {
  const context = environment.getContext()
  const tokens = environment.getRelevantTokens(context.user, [inputs.chainId], USD.zero(), [], ListType.DenyList)
  const builder = TransferBuilder.forChain(inputs.chainId)

  for (let i = 0; i < tokens.length; i++) {
    const token = tokens[i]
    builder.addTransferFromTokenAmount(token, inputs.recipient)
    log.info(`Adding transfer for ${token} on chain ${inputs.chainId}`)
  }

  if (builder.transfers.length == 0) {
    log.info(`No tokens found on chain ${inputs.chainId}`)
    return
  }

  builder.addMaxFee(TokenAmount.fromStringDecimal(DenominationToken.USD(), inputs.maxFeeUsd)).build().send()
}

Notes

  • getRelevantTokens helps you focus on balances that matter. Supply an allow‑list if you want strict control over which tokens are considered.

  • The fee cap is set in USD via DenominationToken.USD(); the runtime handles conversion.


Query a subgraph for price and swap with slippage

Goal: Fetch a Uniswap pool price from a subgraph, compute expected output, apply slippage in BPS, and submit a swap intent.

import { Address, BigInt, environment, ERC20Token, Swap } from '@mimicprotocol/lib-ts'
import { JSON } from 'json-as/assembly'

import { ERC20 } from './types/ERC20'
import { inputs } from './types'

@json
class UniswapPool {
  constructor(
    public token0Price: string,
    public token1Price: string
  ) {}
}

@json
class UniswapPoolsData {
  constructor(public pools: UniswapPool[]) {}
}

const PRICE_PRECISION: u8 = 40
const BPS_DENOMINATOR = BigInt.fromI32(10_000)

export default function main(): void {
  if (inputs.tokenIn == inputs.tokenOut) throw new Error('Token in and out must be different')
  if (BigInt.fromI32(inputs.slippageBps as i32) > BPS_DENOMINATOR) throw new Error('Slippage must be between 0 and 100')

  const me = environment.getContext().user
  const amountIn = new ERC20(inputs.tokenIn, inputs.chainId).balanceOf(me)
  if (amountIn.isZero()) throw new Error('No amount in to swap')

  const price = getTokenPrice(inputs.chainId, inputs.subgraphId, inputs.tokenIn, inputs.tokenOut)
  const tokenIn = ERC20Token.fromAddress(inputs.tokenIn, inputs.chainId)
  const tokenOut = ERC20Token.fromAddress(inputs.tokenOut, inputs.chainId)
  const expectedOut = amountIn
    .times(price)
    .upscale(tokenOut.decimals)
    .downscale(tokenIn.decimals + PRICE_PRECISION)
  const slippageFactor = BPS_DENOMINATOR.minus(BigInt.fromI32(inputs.slippageBps as i32))
  const minAmountOut = expectedOut.times(slippageFactor).div(BPS_DENOMINATOR)
  Swap.create(inputs.chainId, tokenIn, amountIn, tokenOut, minAmountOut).send()
}

function getTokenPrice(chainId: i32, subgraphId: string, tokenIn: Address, tokenOut: Address): BigInt {
  let token0: Address
  let token1: Address
  if (tokenIn.toString() < tokenOut.toString()) {
    token0 = tokenIn
    token1 = tokenOut
  } else {
    token0 = tokenOut
    token1 = tokenIn
  }

  const query = `{pools(where: { token0: "${token0}", token1: "${token1}" }) {token0Price  token1Price}}`
  const response = environment.subgraphQuery(chainId, subgraphId, query, null)
  const data = JSON.parse<UniswapPoolsData>(response.data)

  if (tokenIn == token0 && tokenOut === token1) {
    return BigInt.fromStringDecimal(data.pools[0].token1Price, PRICE_PRECISION)
  } else {
    return BigInt.fromStringDecimal(data.pools[0].token0Price, PRICE_PRECISION)
  }
}

Why PRICE_PRECISION = 40?

  • Subgraph prices are strings with decimal precision. We parse them into a big integer using a high fixed precision (40) to minimize rounding error before scaling to token decimals. Match this with your downstream math so upscale/downscale lands on correct integer units.

Slippage in BPS

  • slippageBps = 50 → 0.50% buffer. We compute minAmountOut = expectedOut * (1 − bps/10_000).

Edge cases

  • Ensure the pool exists (data.pools.length > 0).

  • Consider stable pools whose price may deviate

  • Handle tokens with non‑standard decimals (e.g., 6, 8).

Last updated