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.feeAmountUsd)).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).


Next steps

  1. Read more about the Lib

    Learn about available APIs for creating intents and interacting with contracts.

  2. Learn more about the CLI

    Discover advanced CLI commands for testing, validating, and managing tasks.

  3. Build more complex use cases Expand your automation examples.

Last updated