# Skill: Writing a Function

A Mimic function is an AssemblyScript module compiled to WebAssembly. It runs inside a sandboxed environment on every trigger execution, receives typed inputs from the manifest, queries on-chain and off-chain data, and emits **intents** — declarations of what you want to happen on-chain.

***

## Project anatomy

```
my-function/
├── manifest.yaml          # Inputs, ABIs, and metadata
├── src/
│   ├── function.ts        # Your function logic (entry point)
│   └── types/             # Auto-generated by `mimic codegen`
│       ├── index.ts       # Typed inputs
│       └── ERC20.ts       # Generated ABI wrapper (one per ABI)
└── build/
    ├── function.wasm      # Compiled output
    └── manifest.json      # Validated manifest
```

***

## Manifest

The manifest describes your function's metadata, inputs, and contract ABIs. The CLI uses it to validate, generate types, compile, and deploy.

```yaml
version: 1.0.0
name: My Automation Function
description: Swaps USDC to ETH when balance exceeds a threshold.
inputs:
  - chainId: int32
  - smartAccount: address
  - tokenIn: address
  - tokenOut: address
  - threshold: string
  - feeAmount: string
abis:
  - ERC20: "./abis/ERC20.json"
```

Inputs can optionally include a description:

```yaml
inputs:
  - threshold:
      type: string
      description: Minimum balance in human-readable units, e.g. "100.5"
  - feeAmount:
      type: string
      description: Max fee in USD, e.g. "1.5"
```

**All token amounts should be declared as `string`** and converted in code with `fromStringDecimal`. This keeps the UI human-readable — a user types `1` for 1 USDC, not `1000000`.

```typescript
// In the manifest: threshold: string
// In the function:
const threshold = TokenAmount.fromStringDecimal(Tokens.USDC.on(inputs.chainId), inputs.threshold)
const fee       = TokenAmount.fromStringDecimal(DenominationToken.USD(), inputs.feeAmount)
```

**Input type mapping** — manifest types and the AssemblyScript types they produce:

| Manifest type        | AssemblyScript type |
| -------------------- | ------------------- |
| `int32`              | `i32`               |
| `int64`              | `i64`               |
| `uint8`              | `u8`                |
| `uint16`             | `u16`               |
| `uint32`             | `u32`               |
| `uint64`             | `u64`               |
| `uint256` / `int256` | `BigInt`            |
| `address`            | `Address`           |
| `bytes` / `bytesN`   | `Bytes`             |
| `string`             | `string`            |

* `inputs` and `abis` are merged into maps during validation — duplicate keys are rejected.
* ABI paths are resolved relative to the directory of your `manifest.yaml`.

***

## Function structure

Every function must export a `main` function. The inputs type is auto-generated from your manifest.

```typescript
import { environment, log, TokenAmount, TransferBuilder } from '@mimicprotocol/lib-ts'
import { inputs } from './types'

export default function main(): void {
  // 1. Read inputs
  const account = inputs.account
  const threshold = inputs.threshold

  // 2. Query on-chain or off-chain data
  const balanceResult = environment.relevantTokensQuery(account, [inputs.chainId])
  if (balanceResult.isError) {
    log.error('Failed to query balances: {}', [balanceResult.error])
    return
  }

  // 3. Apply logic and emit intents
  const balances = balanceResult.unwrap()
  // ... build and send an intent
}
```

***

## Library reference (`@mimicprotocol/lib-ts`)

### Primitives

```typescript
import { Address, BigInt, Bytes } from '@mimicprotocol/lib-ts'

// Address
const addr    = Address.fromString('0x...')
const zero    = Address.zero()

// BigInt — used for all token amounts and large numbers
const amount  = BigInt.fromString('1000000000000000000') // 1 ETH in wei
const doubled = amount.times(BigInt.fromI32(2))
const power   = BigInt.fromI32(10).pow(18)

// Bytes — arbitrary byte data
const data    = Bytes.fromHexString('0x095ea7b3...')
const empty   = Bytes.empty()
const utf8    = Bytes.fromUTF8('hello')
```

***

### Tokens and amounts

#### Pre-defined tokens

There are two ways to reference a known token depending on whether the chain is fixed or dynamic.

**Chain namespaces** — use when the chain is known at compile time:

```typescript
import { Ethereum, Arbitrum, Base, Optimism, Gnosis, Polygon, Avalanche, BNB, Sonic } from '@mimicprotocol/lib-ts'

const usdc = Ethereum.USDC   // ERC20Token with address, chainId, decimals and symbol pre-filled
const eth  = Ethereum.ETH
const wbtc = Arbitrum.WBTC
```

**`Tokens` class** — use when the chain comes from `inputs.chainId` at runtime:

```typescript
import { Tokens } from '@mimicprotocol/lib-ts'

// Resolves the correct USDC address for whatever chain the user configured
const usdc = Tokens.USDC.on(inputs.chainId)

// Check before resolving if the token might not be supported on all chains
if (Tokens.USDC.isSupported(inputs.chainId)) {
  const usdc = Tokens.USDC.on(inputs.chainId)
}
```

Available tokens in `Tokens`: `USDC`, `USDT`, `DAI`, `WBTC`, `WETH`, `ETH`, `AVAX`, `WAVAX`, `POL`, `WPOL`, `BNB`, `WBNB`, `XDAI`, `WXDAI`, `SONIC`, `WSONIC`.

**`ERC20Token.fromString`** — last resort for tokens not in the registry:

```typescript
import { ERC20Token } from '@mimicprotocol/lib-ts'

// Decimals and symbol are optional — if omitted they are fetched on-chain lazily when first accessed.
// Provide them when known to avoid the extra RPC calls.
const myToken    = ERC20Token.fromString('0x...', inputs.chainId)             // lazy on-chain fetch
const myTokenOpt = ERC20Token.fromString('0x...', inputs.chainId, 18, 'MYT') // no extra calls
```

#### DenominationToken

`DenominationToken.USD()` is the standard way to express max fees. It is **not** a real on-chain token — it deducts from the user's Mimic credits. It can **only** be used as the fee argument to `.send()` or `IntentBuilder.addMaxFee()`. You cannot transfer or swap it.

```typescript
import { DenominationToken, TokenAmount } from '@mimicprotocol/lib-ts'

const fee = TokenAmount.fromStringDecimal(DenominationToken.USD(), inputs.feeAmount)

// Only valid use — passing to .send() or IntentBuilder.addMaxFee()
intentBuilder.addMaxFee(fee)
builder.build().send(fee)
```

#### TokenAmount

```typescript
import { TokenAmount, USD } from '@mimicprotocol/lib-ts'

const amount   = TokenAmount.fromStringDecimal(usdc, '1000.50')              // 1000.5 USDC
const native   = TokenAmount.fromI32(eth, 5)                                 // 5 ETH
const raw      = TokenAmount.fromBigInt(usdc, BigInt.fromString('1000000'))  // 1 USDC (6 decimals)

// Price conversions
const usdValue = amount.toUsd().unwrap()                    // USD
const inWbtc   = amount.toTokenAmount(Ethereum.WBTC).unwrap() // TokenAmount

// USD
const usd    = USD.fromStringDecimal('1000')
const inUsdc = usd.toTokenAmount(Ethereum.USDC).unwrap()
```

***

### Result type

All queries (except `getContext`) return `Result<V, string>`. Always handle errors.

```typescript
import { Result } from '@mimicprotocol/lib-ts'

const result = environment.tokenPriceQuery(Ethereum.USDC)

// Check state
result.isOk    // true on success
result.isError // true on failure

// Unwrap
result.unwrap()              // throws if error
result.unwrapOr(USD.zero())  // fallback value
result.unwrapOrElse(() => {  // fallback function
  log.warning('Price unavailable, using zero')
  return USD.zero()
})

// Access error message
if (result.isError) log.error('Error: {}', [result.error])
```

***

### Environment queries

#### Token price

```typescript
import { environment, Ethereum } from '@mimicprotocol/lib-ts'

// Current USD price (median across sources)
const price = environment.tokenPriceQuery(Ethereum.USDC).unwrap()

// Historical price
const historical = environment.tokenPriceQuery(Ethereum.USDC, new Date(1640995200000)).unwrap()
```

#### Relevant token balances

Returns all token balances for an address, with optional chain, allow/deny list, and USD minimum filters.

```typescript
import { environment, Address, ChainId, ListType, USD } from '@mimicprotocol/lib-ts'

// All tokens on Ethereum and Polygon worth at least $100, excluding USDT
const tokens = environment.relevantTokensQuery(
  userAddress,
  [ChainId.ETHEREUM, ChainId.POLYGON],
  USD.fromStringDecimal('100'),
  [Ethereum.USDT],
  ListType.DenyList
).unwrap()  // TokenAmount[]
```

#### EVM contract read (raw)

Use generated ABI wrappers (see below) instead of raw calls where possible.

```typescript
const response = environment.evmCallQuery(
  Address.fromString('0xcontract'),
  ChainId.ETHEREUM,
  '0x70a08231' + encodedArgs,  // selector + ABI-encoded params
  null                          // optional timestamp
).unwrap()  // raw hex string
```

#### Native token balance

```typescript
const balance = environment.getNativeTokenBalance(ChainId.ETHEREUM, userAddress).unwrap() // BigInt in wei
```

#### Account code

```typescript
const code = environment.getCode(ChainId.ETHEREUM, contractAddress).unwrap() // Bytes
```

#### Subgraph query

```typescript
const result = environment.subgraphQuery(
  ChainId.ETHEREUM,
  'QmSubgraphId',
  '{ tokens(first: 5) { id symbol } }',
  null  // optional timestamp
).unwrap()  // SubgraphQueryResult
```

#### Execution context

`getContext()` does not return a `Result` — it cannot fail.

```typescript
import { environment, ChainId, TriggerType } from '@mimicprotocol/lib-ts'

const ctx = environment.getContext()

ctx.user               // Address — the user this trigger belongs to
ctx.timestamp          // u64 — current execution timestamp in milliseconds
ctx.consensusThreshold // u8 — minimum number of agreeing relayers required
ctx.triggerSig         // string — unique signature of the trigger

// Access trigger payload data
const payload = ctx.triggerPayload
if (payload.type == TriggerType.CRON) {
  const scheduleTimestamp = payload.getCronData()  // BigInt
}
if (payload.type == TriggerType.EVENT) {
  const eventData = payload.getEventData()
  // eventData.chainId, eventData.contract, eventData.topics, eventData.eventData
}
```

***

### Generated ABI wrappers

After running `mimic codegen`, each ABI declared in the manifest produces a file in `src/types/<ContractName>.ts` containing four things:

1. **The contract class** — instantiate with `(address, chainId, timestamp?)` to call methods
2. **A `<ContractName>Utils` static class** — raw encode/decode helpers (rarely needed directly)
3. **Struct/tuple classes** — one per Solidity struct or multi-output tuple
4. **Event classes** — one per event, with a `static decode(topics, data)` method

#### Contract class — read and write methods

```typescript
import { ChainId } from '@mimicprotocol/lib-ts'
import { ERC20 } from './types/ERC20'

const token = new ERC20(tokenAddress, ChainId.ETHEREUM)

// Read methods — call the contract and return Result<T, string>
const balanceResult = token.balanceOf(userAddress)  // Result<BigInt, string>
if (balanceResult.isError) return
const balance = balanceResult.unwrap()

// Write methods — return an EvmCallBuilder, do not call the chain directly
const approveBuilder = token.approve(spenderAddress, amount)  // EvmCallBuilder
approveBuilder.build().send(fee)

// Historical queries — pass a timestamp as third constructor arg
const historicalToken = new ERC20(tokenAddress, ChainId.ETHEREUM, new Date(1640995200000))
```

#### Struct classes

Solidity structs and multi-output tuples become generated classes with typed fields. You typically receive them as unwrapped return values from read methods — you don't construct them manually.

```typescript
// If a read method returns a struct, unwrap it and access its fields directly
const feeState = contract.getFeeState().unwrap()  // FeeState
const recipient = feeState.feeRecipient            // Address
const percentage = feeState.streamingFeePercentage // BigInt
```

#### Event classes

Each event gets a class with a `static decode(topics, data)` method, matching the shape of the `TriggerType.EVENT` payload:

```typescript
import { MyContract, MyContractTransferEvent } from './types/MyContract'

const eventData = ctx.triggerPayload.getEventData()
const event = MyContractTransferEvent.decode(eventData.topics, eventData.eventData)
const from   = event.from   // Address
const value  = event.value  // BigInt
```

#### Utils class

The `<ContractName>Utils` static class exposes `encodeX()` and `decodeX()` methods for raw ABI encoding. Use these only when you need the raw encoded bytes — for example to pass a call directly to `addCall()` without instantiating the contract class.

```typescript
import { ERC20Utils } from './types/ERC20'

const data = ERC20Utils.encodeTransfer(recipientAddress, amount)  // Bytes
builder.addCall(tokenAddress, data)
```

#### Solidity → AssemblyScript type mapping

| Solidity                           | AssemblyScript          |
| ---------------------------------- | ----------------------- |
| `address`                          | `Address`               |
| `bool`                             | `bool`                  |
| `string`                           | `string`                |
| `bytes` / `bytesN`                 | `Bytes`                 |
| `uint8`–`uint16`, `int8`–`int16`   | `u8`/`u16`/`i8`/`i16`   |
| `uint32`–`uint64`, `int32`–`int64` | `u32`/`u64`/`i32`/`i64` |
| `uint256` / `int256`               | `BigInt`                |
| `T[]`                              | `T[]`                   |
| `tuple` / `struct`                 | generated class         |

***

### Intent builders

Intents are declarations of what you want to happen. The protocol finds the best way to fulfill them.

#### Transfer — move tokens between addresses

```typescript
import { Address, ChainId, Ethereum, TokenAmount, TransferBuilder } from '@mimicprotocol/lib-ts'

const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1')

TransferBuilder.forChain(ChainId.ETHEREUM)
  .addTransferFromStringDecimal(Ethereum.USDC, '500', recipientAddress)
  .addTransferFromStringDecimal(Ethereum.WBTC, '0.01', recipientAddress)
  .build()
  .send(fee)
```

#### Swap — exchange tokens

```typescript
import { Arbitrum, ChainId, Ethereum, SwapBuilder } from '@mimicprotocol/lib-ts'

const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1')

// Same-chain swap
SwapBuilder.forChain(ChainId.ETHEREUM)
  .addTokenInFromStringDecimal(Ethereum.USDC, '1000')
  .addTokenOutFromStringDecimal(Ethereum.USDT, '990', recipientAddress)  // min amount out
  .build()
  .send(fee)

// Cross-chain swap
SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ARBITRUM)
  .addTokenInFromStringDecimal(Ethereum.USDC, '1000')
  .addTokenOutFromStringDecimal(Arbitrum.USDC, '990', recipientAddress)
  .build()
  .send(fee)
```

The `tokenOut` amount is a **minimum** — relayers may deliver more.

#### EVM Call — execute contract functions

```typescript
import { Address, ChainId, Ethereum, EvmCallBuilder, TokenAmount } from '@mimicprotocol/lib-ts'
import { MyContract } from './types/MyContract'

const contract = new MyContract(contractAddress, ChainId.ETHEREUM)
const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1')

// Single call from a generated wrapper
contract.myFunction(arg1, arg2)  // returns EvmCallBuilder
  .build()
  .send(fee)

// Multiple calls in one intent — use addCallsFromBuilder to merge generated wrappers.
// This is preferred over extracting and re-encoding data manually with addCall.
EvmCallBuilder.forChain(ChainId.ETHEREUM)
  .addCallsFromBuilder(contract.functionA(arg1))
  .addCallsFromBuilder(contract.functionB(arg2))
  .addUser(inputs.smartAccount)
  .build()
  .send(fee)

// Or pass all builders at once
EvmCallBuilder.forChain(ChainId.ETHEREUM)
  .addCallsFromBuilders([contract.functionA(arg1), contract.functionB(arg2)])
  .addUser(inputs.smartAccount)
  .build()
  .send(fee)

// Use addCall directly only when you have raw encoded data (e.g. from a multicall response)
EvmCallBuilder.forChain(ChainId.ETHEREUM)
  .addCall(contractAddress, encodedData)           // value defaults to 0
  .addCall(contractAddress, encodedData2, BigInt.fromI32(1000)) // optional ETH value for payable calls
  .build()
  .send(fee)
```

#### Common builder options

All builders support these optional methods before `.build()`:

| Method                   | Purpose                                    |
| ------------------------ | ------------------------------------------ |
| `.addUser(address)`      | Override the user (defaults to `ctx.user`) |
| `.addEvent(topic, data)` | Attach an on-chain event to the operation  |
| `.addEvents(events[])`   | Attach multiple events                     |

The Intent builder supports these optional methods before `.build()`:

| Method                    | Purpose                                                |
| ------------------------- | ------------------------------------------------------ |
| `.addFeePayer(address)`   | Override the fee payer (defaults to `ctx.user`)        |
| `.addDeadline(timestamp)` | Override the deadline (defaults to 5 minutes from now) |
| `.addNonce(string)`       | Override the nonce (defaults to auto-generated)        |
| `.addMaxFee(TokenAmount)` | Sets max fee that can be paied for the intent          |

**When to use `.addUser()`:**

* **Transfer / Swap intents** — `ctx.user` is the default and is correct for most cases. `ctx.user` is the EOA that signed and triggered the function, so you normally do not need to call `.addUser()`.
* **EVM Call intents** — you must call `.addUser(inputs.smartAccount)`. EVM Call intents execute generic contract calls, which require a smart account. `ctx.user` is an EOA and cannot fulfill this role.

```typescript
// Transfer — ctx.user is the default, no .addUser() needed
TransferBuilder.forChain(inputs.chainId)
  .addTransferFromTokenAmount(tokenAmount, recipientAddress)
  .build()
  .send(fee)

// EVM Call — smart account required
EvmCallBuilder.forChain(inputs.chainId)
  .addCall(contractAddress, callData)
  .addUser(inputs.smartAccount)  // required for generic calls
  .build()
  .send(fee)
```

**Guarding against empty intents:**

Never send an intent with no transfers, calls, or swaps. If you are conditionally adding items (e.g. iterating over a list), check the builder before sending. If you are unconditionally adding at least one item, no guard is needed.

```typescript
// Guard needed — items are added conditionally inside a loop
const builder = TransferBuilder.forChain(inputs.chainId)
for (let i = 0; i < tokenBalances.length; i++) {
  if (tokenBalances[i].amount.gt(BigInt.zero())) {
    builder.addTransferFromTokenAmount(tokenBalances[i], inputs.recipient)
  }
}
if (builder.transfers.length > 0) {  // only send if something was added
  builder.build().send(fee)
}

// No guard needed — the call is always added
EvmCallBuilder.forChain(inputs.chainId)
  .addCall(contractAddress, callData)
  .addUser(inputs.smartAccount)
  .build()
  .send(fee)
```

**Batching large numbers of items:**

A single intent may not fit in one transaction if it contains too many calls, transfers, or swaps. If you are building a variable-length list, split it into batches of at most 20 items and send one intent per batch.

```typescript
const BATCH_SIZE = 20

for (let i = 0; i < calls.length; i += BATCH_SIZE) {
  const builder = EvmCallBuilder.forChain(inputs.chainId)
  const end = i + BATCH_SIZE < calls.length ? i + BATCH_SIZE : calls.length
  for (let j = i; j < end; j++) {
    builder.addCall(calls[j].address, calls[j].data)
  }
  builder.addUser(inputs.smartAccount).build().send(fee)
}
```

***

### Persistent storage

Use the `storage` namespace to read and write arbitrary bytes to the user's on-chain storage via the Mimic Helper contract. Useful for maintaining state between executions.

```typescript
import { environment, storage, Bytes, ChainId, TokenAmount, DenominationToken } from '@mimicprotocol/lib-ts'

const userAddress = environment.getContext().user
const fee = TokenAmount.fromStringDecimal(DenominationToken.USD(), '0.5')

// Write — emits an EvmCall intent that stores the data on-chain
storage.createSetDataCall(
  userAddress,
  fee,
  'last-execution',                    // storage key
  Bytes.fromUTF8('2024-01-01'),        // value (arbitrary bytes)
  ChainId.OPTIMISM                     // chain (defaults to Optimism)
).send()

// Read — synchronous on-chain call, returns Result<Bytes, string>
const dataResult = storage.getData(userAddress, 'last-execution', ChainId.OPTIMISM)
if (dataResult.isOk) {
  const value = dataResult.unwrap().toString()
}
```

***

### Logging

```typescript
import { log } from '@mimicprotocol/lib-ts'

// Format string style — {} placeholders replaced in order
log.debug('Processing account {}', [account.toHexString()])
log.info('Balance: {} USDC', [balance.toString()])
log.warning('Slippage is high: {}%', [slippage.toString()])
log.error('Token price query failed: {}', [result.error])

// Template literal style — equivalent and more readable for inline expressions
log.info(`Balance: ${balance.toString()} USDC`)
log.info(`Processing pool ${pool}, token ${token}, fees ${fees.toString()}`)

// CRITICAL terminates execution immediately
log.critical('Unexpected state, aborting')
```

Both styles work. Template literals are preferred for multi-variable messages; format strings are preferred when you need to call `.toString()` on custom types. Levels: `DEBUG < INFO < WARNING < ERROR < CRITICAL`.

***

### EVM utilities

```typescript
import { evm, EvmEncodeParam, EvmDecodeParam, Address, BigInt } from '@mimicprotocol/lib-ts'

// ABI-encode parameters
const encoded = evm.encode([
  EvmEncodeParam.fromValue('address', Address.zero()),
  EvmEncodeParam.fromValue('uint256', BigInt.fromI32(100)),
])

// ABI-decode a response
const decoded = evm.decode(new EvmDecodeParam('uint256', responseHex))

// Keccak-256 hash
const hash = evm.keccak('some data')
```

***

## Putting it together — annotated example

```typescript
import {
  Address, ChainId, DenominationToken, Ethereum, environment, log,
  SwapBuilder, TokenAmount,
} from '@mimicprotocol/lib-ts'
import { inputs } from './types'
import { ERC20 } from './types/ERC20'

export default function main(): void {
  const ctx     = environment.getContext()
  const account = ctx.user
  const token   = new ERC20(inputs.tokenIn, ChainId.ETHEREUM)

  // Read on-chain balance
  const balResult = token.balanceOf(account)
  if (balResult.isError) {
    log.error('balanceOf failed: {}', [balResult.error])
    return
  }
  const balance = balResult.unwrap()

  // Check threshold
  const threshold = inputs.threshold
  if (balance.lt(threshold)) {
    log.info(`Balance ${balance.toString()} below threshold ${threshold.toString()}, skipping`)
    return
  }

  // Swap to ETH
  const fee    = TokenAmount.fromStringDecimal(DenominationToken.USD(), inputs.feeAmount)
  const minOut = TokenAmount.fromBigInt(Ethereum.ETH, balance)
    .toTokenAmount(Ethereum.ETH).unwrap()

  SwapBuilder.forChain(ChainId.ETHEREUM)
    .addTokenInFromStringDecimal(Ethereum.USDC, balance.toString())
    .addTokenOutFromTokenAmount(minOut, account)
    .addUser(inputs.smartAccount)
    .build()
    .send(fee)

  log.info('Swap intent emitted')
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mimic.fi/skills/skill-function.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
