Bridge and invest in Aave
This example involves two tasks. The goal of the first task is to bridge tokens from one chain to another, and the goal of the second task is to invest in Aave the tokens received on the destination chain.
To achieve this, the bridge must complete before the investment begins. Mimic enables this kind of task concatenation by allowing one task to emit a custom event that triggers other tasks in response.
The first task should include something as follows:
SwapBuilder.addEvent('0xCustomId', '0xDataForTheOtherTask')And the configuration for the second task should look like:
const trigger = {
type: TriggerType.Event
chainId: Chains.Optimism,
contract: MIMIC_PROTOCOL_SETTLER,
topics: [
[INTENT_EXECUTED_TOPIC],
[encode('0xUser')],
['0xCustomId'],
],
}
await client.configs.signAndCreate({ ..., trigger })With this configuration, the invest task will automatically execute every time an intent from user 0xUser emits an event containing 0xCustomId on Optimism.
1. Bridge
Task
import {
Arbitrum,
Base,
Bytes,
ChainId,
ERC20Token,
evm,
EvmEncodeParam,
Optimism,
SwapBuilder,
Token,
TokenAmount,
} from '@mimicprotocol/lib-ts'
import { inputs } from './types/bridge'
export default function main(): void {
const sourceChain = inputs.sourceChain
const destinationChain = inputs.destinationChain
if (sourceChain == destinationChain) throw new Error('Single-chain swap not supported')
const tokenIn = getUsdc(sourceChain)
const tokenAmountIn = TokenAmount.fromStringDecimal(tokenIn, inputs.amount)
const tokenOut = getUsdc(destinationChain)
const tokenAmountOut = TokenAmount.fromStringDecimal(tokenOut, inputs.minAmountOut)
const feeToken = new ERC20Token(inputs.feeToken, inputs.destinationChain)
const maxFee = TokenAmount.fromStringDecimal(feeToken, inputs.maxFee)
// Set topic hash and encode tokenOut address as extra data
const topic = evm.keccak('Bridged USDC')
const data = evm.encode([EvmEncodeParam.fromValue('address', tokenAmountOut.token.address)])
// Bridge tokens owned by the smart account
SwapBuilder.forChains(sourceChain, destinationChain)
.addTokenInFromTokenAmount(tokenAmountIn)
.addTokenOutFromTokenAmount(tokenAmountOut, inputs.smartAccount)
.addUser(inputs.smartAccount)
.addMaxFee(maxFee)
// Settler smart contract will emit an IntentExecuted event in which `topic2 = keccak256('Bridged USDC')`
.addEvent(Bytes.fromHexString(topic), Bytes.fromHexString(data)) // Optional
.build()
.send()
}
function getUsdc(chainId: i32): Token {
if (chainId == ChainId.ARBITRUM) return Arbitrum.USDC
if (chainId == ChainId.BASE) return Base.USDC
if (chainId == ChainId.OPTIMISM) return Optimism.USDC
throw new Error('Invalid chain')
}
Manifest
version: 1.0.0
name: Bridge USDC and emit an event
description: Bridge USDC between Arbitrum, Base and Optimism, and emit a custom event to trigger another task
inputs:
- sourceChain: uint32
- destinationChain: uint32
- smartAccount: address # Important: The smart account should exist in both chains
- amount: string
- minAmountOut: string
- feeToken: address
- maxFee: string
2. Invest
Task
import {
Address,
BigInt,
ChainId,
environment,
ERC20Token,
evm,
EvmCallBuilder,
EvmDecodeParam,
JSON,
TokenAmount,
TriggerType,
} from '@mimicprotocol/lib-ts'
import { inputs } from './types/invest'
import { AavePoolUtils } from './types/invest/AavePool'
import { ERC20Utils } from './types/invest/ERC20'
import { IntentExecutedEvent } from './types/invest/Settler'
export default function main(): void {
const chainId = inputs.chainId
const smartAccount = inputs.smartAccount
const aaveV3Pool = getAaveV3Pool(chainId)
const trigger = environment.getContext().trigger
if (trigger.type != TriggerType.EVENT) throw new Error('Trigger not event')
const triggerData = trigger.getEventData()
const event = IntentExecutedEvent.decode(triggerData.topics, triggerData.eventData)
if (event.intent.user != smartAccount) throw new Error('Intent user not smart account')
// Get the token and the amount from the event emitted by the task that triggered this one
const tokenStr = evm.decode(new EvmDecodeParam('address', event.data.toHexString()))
const token = Address.fromString(tokenStr)
const amountsStr = evm.decode(new EvmDecodeParam('uint256[]', event.output.toHexString()))
const amounts = JSON.parse<string[]>(amountsStr)
if (amounts.length == 0) throw new Error('Empty amounts array')
const amount = BigInt.fromString(amounts[0])
const feeToken = new ERC20Token(inputs.feeToken, chainId)
const maxFee = TokenAmount.fromStringDecimal(feeToken, inputs.maxFee)
const approveData = ERC20Utils.encodeApprove(aaveV3Pool, amount)
const supplyData = AavePoolUtils.encodeSupply(token, amount, smartAccount, 0)
// Give approval and deposit the tokens, owned by the smart account
EvmCallBuilder.forChain(chainId)
.addCall(token, approveData)
.addCall(aaveV3Pool, supplyData)
.addUser(smartAccount)
.addMaxFee(maxFee)
.build()
.send()
}
function getAaveV3Pool(chainId: i32): Address {
if (chainId == ChainId.ARBITRUM) return Address.fromString('0x794a61358d6845594f94dc1db02a252b5b4814ad')
if (chainId == ChainId.BASE) return Address.fromString('0xa238dd80c259a72e81d7e4664a9801593f98d1c5')
if (chainId == ChainId.OPTIMISM) return Address.fromString('0x794a61358d6845594f94dc1db02a252b5b4814ad')
throw new Error('Invalid chain')
}
Manifest
version: 1.0.0
name: Deposit USDC in Aave (event trigger)
description: Deposit in Aave USDC tokens that were bridged from another chain
inputs:
- chainId: uint32
- smartAccount: address
- feeToken: address
- maxFee: string
abis:
- AavePool: ./src/abis/AavePool.json
- ERC20: ./src/abis/ERC20.json
- Settler: ./src/abis/Settler.json
Backend
import { config } from 'dotenv'
config()
import { Chains, Client, EthersSigner, Sort, TriggerType } from '@mimicprotocol/sdk'
import { AbiCoder, Interface, keccak256, toUtf8Bytes, Wallet } from 'ethers'
import { inc } from 'semver'
import SettlerAbi from './abis/Settler.json'
const SETTLER_IFACE = new Interface(SettlerAbi)
const INTENT_EXECUTED_TOPIC = SETTLER_IFACE.getEvent('IntentExecuted')!.topicHash
const BRIDGED_TOPIC = keccak256(toUtf8Bytes('Bridged USDC'))
const MIMIC_PROTOCOL_SETTLER = '0x609d831C0068844e11eF85a273c7F356212Fd6D1'
const USDT: Record<number, string> = {
10: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', // Optimism
8453: '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2', // Base
42161: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', // Arbitrum
}
async function main(): Promise<void> {
if (!process.env.PRIVATE_KEY) throw new Error('Missing PRIVATE_KEY in .env file')
if (!process.env.INVEST_CID) throw new Error('Missing INVEST_CID in .env file')
if (!process.env.SMART_ACCOUNT) throw new Error('Missing SMART_ACCOUNT in .env file')
const { PRIVATE_KEY, INVEST_CID, SMART_ACCOUNT } = process.env
// Create client with signer
const signer = EthersSigner.fromPrivateKey(PRIVATE_KEY)
const client = new Client({ signer })
// Get task manifest from deployed task
const manifest = await client.tasks.getManifest(INVEST_CID)
// Set user topic to filter events
const USER_TOPIC = AbiCoder.defaultAbiCoder().encode(['address'], [SMART_ACCOUNT])
// Increment config version
const latestConfig = await client.configs.get({ taskCid: INVEST_CID, sort: Sort.desc, limit: 1 })
const version = latestConfig.length > 0 ? inc(latestConfig[0].version.split('-')[0], 'patch') : '0.0.1'
if (!version) throw new Error('Invalid config version')
// Set trigger based on a blockchain event
const trigger = {
type: TriggerType.Event as const,
contract: MIMIC_PROTOCOL_SETTLER,
topics: [
[INTENT_EXECUTED_TOPIC], // The event emitted by the Settler
[USER_TOPIC], // Important: To prevent other users from triggering this task
[BRIDGED_TOPIC], // Emitted by the bridge task
],
delta: '1h',
endDate: 0, // No end date
}
// Submit one task config per chain
const chainIds = [Chains.Arbitrum, Chains.Base, Chains.Optimism]
for (const chainId of chainIds) {
const input = {
chainId,
smartAccount: SMART_ACCOUNT,
feeToken: USDT[chainId],
maxFee: '0.3',
}
const config = await client.configs.signAndCreate({
description: `Invest bridged amount in Aave (${chainId})`,
taskCid: INVEST_CID,
version: `${version}-${chainId}`,
manifest,
trigger: { ...trigger, chainId },
input,
executionFeeLimit: '0',
minValidations: 1,
signer: new Wallet(PRIVATE_KEY).address,
})
console.log(`Created config on chain ${chainId}: ${config.sig}`)
}
}
main().catch((err) => {
console.error(err)
process.exit(1)
})
The ABIs used for this example can be downloaded below:
Last updated