Using off-chain data
This page shows how to enrich your Mimic tasks with off‑chain data. You’ll learn three common patterns:
Pricing convert token balances to USD
Discovery of relevant tokens for a user across a chain and bulk‑transfer them
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, andmimic 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.maxFeeis specified in token units here; pass a USD‑denominated cap instead by usingTokenAmount.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
getRelevantTokenshelps 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/downscalelands on correct integer units.
Slippage in BPS
slippageBps = 50→ 0.50% buffer. We computeminAmountOut = 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