# 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 with `.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 .addMaxFee()
builder.addMaxFee(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.addMaxFee(fee).build().send()

// 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)
  .addMaxFee(fee)
  .build()
  .send()
```

#### Swap — exchange tokens

```typescript
import { ChainId, Ethereum, SwapBuilder, TokenAmount } 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
  .addMaxFee(fee)
  .build()
  .send()

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

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
  .addMaxFee(fee)
  .build()
  .send()

// 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))
  .addMaxFee(fee)
  .addUser(inputs.smartAccount)
  .build()
  .send()

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

// 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
  .addMaxFee(fee)
  .build()
  .send()
```

#### Common builder options

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

| Method                    | Purpose                                                |
| ------------------------- | ------------------------------------------------------ |
| `.addUser(address)`       | Override the user (defaults to `ctx.user`)             |
| `.addDeadline(timestamp)` | Override the deadline (defaults to 5 minutes from now) |
| `.addNonce(string)`       | Override the nonce (defaults to auto-generated)        |
| `.addEvent(topic, data)`  | Attach an on-chain event to the intent                 |
| `.addEvents(events[])`    | Attach multiple events                                 |

**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)
  .addMaxFee(fee)
  .build()
  .send()

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

**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).addMaxFee(fee)
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()
}

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

**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).addMaxFee(fee)
  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()
}
```

***

### 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)
    .addMaxFee(fee)
    .addUser(inputs.smartAccount)
    .build()
    .send()

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