Skip to content

Commit c81fbec

Browse files
committed
add dynamic call example
1 parent 0f93709 commit c81fbec

12 files changed

Lines changed: 4086 additions & 1 deletion

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
build
3+
types
4+
scripts/.env
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"node-option": ["import=tsx/esm"]
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node-linker=hoisted
2+
legacy-peer-deps=true
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import eslintPluginTypeScript from "@typescript-eslint/eslint-plugin"
2+
import eslintParserTypeScript from "@typescript-eslint/parser"
3+
import eslintPluginImport from "eslint-plugin-import"
4+
import eslintPluginSimpleImportSort from "eslint-plugin-simple-import-sort"
5+
import eslintConfigPrettier from "eslint-config-prettier"
6+
import eslintPluginPrettier from "eslint-plugin-prettier"
7+
8+
export default [
9+
{
10+
ignores: ["node_modules/**", "**/dist/**", "**/build/**", "**/.prettierrc.*", "./src/types/**"]
11+
},
12+
{
13+
files: ["**/*.{ts,tsx}"],
14+
languageOptions: {
15+
ecmaVersion: "latest",
16+
sourceType: "module",
17+
parser: eslintParserTypeScript,
18+
parserOptions: {
19+
project: "./tsconfig.json"
20+
}
21+
},
22+
plugins: {
23+
"@typescript-eslint": eslintPluginTypeScript,
24+
prettier: eslintPluginPrettier,
25+
import: eslintPluginImport,
26+
"simple-import-sort": eslintPluginSimpleImportSort
27+
},
28+
rules: {
29+
...eslintPluginTypeScript.configs.recommended.rules,
30+
"@typescript-eslint/no-namespace": "off",
31+
"@typescript-eslint/no-unused-vars": ["error"],
32+
"@typescript-eslint/explicit-function-return-type": "error",
33+
"@typescript-eslint/no-explicit-any": "error",
34+
35+
"prettier/prettier": [
36+
"error",
37+
{
38+
"semi": false,
39+
"singleQuote": true,
40+
"trailingComma": "es5",
41+
"arrowParens": "always",
42+
"bracketSpacing": true,
43+
"printWidth": 120,
44+
"tabWidth": 2,
45+
"useTabs": false
46+
}
47+
],
48+
49+
"simple-import-sort/imports": [
50+
"error",
51+
{
52+
groups: [
53+
["^@?\\w"],
54+
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
55+
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"]
56+
]
57+
}
58+
],
59+
"simple-import-sort/exports": "error",
60+
61+
"comma-spacing": ["error", { before: false, after: true }],
62+
"no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }]
63+
},
64+
settings: {
65+
"import/resolver": {
66+
typescript: {
67+
alwaysTryTypes: true,
68+
project: "./tsconfig.json"
69+
}
70+
}
71+
}
72+
},
73+
// configuration for test files
74+
{
75+
files: ["tests/**/*.{ts,tsx}", "**/*.spec.{ts,tsx}", "**/*.test.{ts,tsx}"],
76+
languageOptions: {
77+
ecmaVersion: "latest",
78+
sourceType: "module",
79+
parser: eslintParserTypeScript,
80+
parserOptions: {
81+
project: "./tests/tsconfig.json"
82+
}
83+
},
84+
rules: {
85+
"@typescript-eslint/no-unused-expressions": "off"
86+
}
87+
},
88+
{
89+
files: ["scripts/**/*.{ts,tsx}"],
90+
languageOptions: {
91+
ecmaVersion: "latest",
92+
sourceType: "module",
93+
parser: eslintParserTypeScript,
94+
parserOptions: {
95+
project: "./scripts/tsconfig.json"
96+
}
97+
},
98+
settings: {
99+
"import/resolver": {
100+
typescript: {
101+
alwaysTryTypes: true,
102+
project: "./scripts/tsconfig.json"
103+
}
104+
}
105+
}
106+
},
107+
eslintConfigPrettier
108+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 1.0.0
2+
name: Swap + Dynamic Call Function
3+
description: Use swap outputs as dynamic call inputs
4+
inputs:
5+
- tokenIn: address
6+
- tokenOut: address
7+
- chainId: uint32
8+
- amount: string # e.g., '20.5' = 20.5 of the given token
9+
- slippageBps: uint16 # e.g., 50 = 0.50%
10+
- smartAccount: address
11+
- recipient: address
12+
- maxFeeUsd: string # e.g., '0.01' = 0.01 USD
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "mimic-function",
3+
"version": "0.0.1",
4+
"license": "Unlicensed",
5+
"private": true,
6+
"type": "module",
7+
"scripts": {
8+
"deploy": "mimic deploy",
9+
"build": "mimic build",
10+
"codegen": "mimic codegen",
11+
"compile": "mimic compile",
12+
"test": "mimic test",
13+
"lint": "eslint ."
14+
},
15+
"devDependencies": {
16+
"@mimicprotocol/cli": "1.0.0",
17+
"@mimicprotocol/lib-ts": "0.1.0",
18+
"@mimicprotocol/sdk": "0.1.0",
19+
"@mimicprotocol/test-ts": "0.1.0",
20+
"@types/chai": "^5.2.2",
21+
"@types/mocha": "^10.0.10",
22+
"@types/node": "^22.10.5",
23+
"assemblyscript": "0.27.36",
24+
"chai": "^4.3.7",
25+
"dotenv": "^17.2.3",
26+
"eslint": "^9.10.0",
27+
"json-as": "1.1.7",
28+
"mocha": "^10.2.0",
29+
"tsx": "^4.20.3",
30+
"typescript": "^5.8.3",
31+
"visitor-as": "0.11.4"
32+
}
33+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {
2+
Bytes,
3+
DenominationToken,
4+
ERC20Token,
5+
EvmDynamicArg,
6+
EvmDynamicCallBuilder,
7+
EvmEncodeParam,
8+
IntentBuilder,
9+
SwapBuilder,
10+
TokenAmount,
11+
} from '@mimicprotocol/lib-ts'
12+
13+
import { inputs } from './types'
14+
15+
export default function main(): void {
16+
const chainId = inputs.chainId
17+
const smartAccount = inputs.smartAccount
18+
const recipient = inputs.recipient
19+
20+
const tokenIn = ERC20Token.fromAddress(inputs.tokenIn, chainId)
21+
const tokenOut = ERC20Token.fromAddress(inputs.tokenOut, chainId)
22+
23+
const amountIn = TokenAmount.fromStringDecimal(tokenIn, inputs.amount)
24+
const expectedOut = amountIn.toTokenAmount(tokenOut).unwrap()
25+
const minAmountOut = expectedOut.applySlippageBps(inputs.slippageBps)
26+
27+
const maxFee = TokenAmount.fromStringDecimal(DenominationToken.USD(), inputs.maxFeeUsd)
28+
29+
// Swap operation (tokens are received in the smart account)
30+
const swap = SwapBuilder.forChain(chainId)
31+
.addTokenInFromTokenAmount(amountIn)
32+
.addTokenOutFromTokenAmount(minAmountOut, smartAccount)
33+
34+
/*
35+
Dynamic call operation (called after the swap, using the received tokens)
36+
It is calling the contract of tokenOut.address on the transfer() function
37+
Using a literal address value for the first arg and the swap operation output as the second arg
38+
This would be equivalent of doing:
39+
`IERC20(tokenOut.address).transfer(to=recipient, value=result of swap)`
40+
*/
41+
const target = tokenOut.address
42+
const selector = Bytes.fromHexString('0xa9059cbb') // transfer() selector
43+
const dynamicCall = EvmDynamicCallBuilder.forChain(chainId)
44+
.addCall(target, selector, [
45+
EvmDynamicArg.literal([new EvmEncodeParam('address', recipient.toString())], false),
46+
EvmDynamicArg.variable(0, 0, false), // swap operation output
47+
])
48+
.addUser(smartAccount)
49+
50+
new IntentBuilder()
51+
.addOperationsBuilders([swap, dynamicCall]) // The order is important!
52+
.addMaxFee(maxFee)
53+
.send()
54+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Chains, EvmDynamicCallOperation, OpType, randomEvmAddress, SwapOperation } from '@mimicprotocol/sdk'
2+
import { EvmCallQueryMock, runFunction, TokenPriceQueryMock } from '@mimicprotocol/test-ts'
3+
import { expect } from 'chai'
4+
5+
describe('Function', () => {
6+
const functionDir = './build'
7+
8+
const chainId = Chains.Optimism
9+
10+
const context = {
11+
user: '0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0',
12+
settlers: [{ address: '0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d', chainId }],
13+
timestamp: Date.now(),
14+
}
15+
16+
const inputs = {
17+
tokenIn: randomEvmAddress(),
18+
tokenOut: randomEvmAddress(),
19+
chainId,
20+
amount: '1.5',
21+
slippageBps: 1,
22+
smartAccount: randomEvmAddress(),
23+
recipient: randomEvmAddress(),
24+
maxFeeUsd: '0.1',
25+
}
26+
27+
const calls: EvmCallQueryMock[] = [
28+
{
29+
request: { to: inputs.tokenIn, chainId, fnSelector: '0x313ce567' }, // decimals
30+
response: { value: '6', abiType: 'uint8' },
31+
},
32+
{
33+
request: { to: inputs.tokenOut, chainId, fnSelector: '0x313ce567' }, // decimals
34+
response: { value: '6', abiType: 'uint8' },
35+
},
36+
]
37+
38+
const prices: TokenPriceQueryMock[] = [
39+
{
40+
request: { token: { address: inputs.tokenIn, chainId: inputs.chainId } },
41+
response: ['1000000000000000000'], // 1 token = 1 USD
42+
},
43+
{
44+
request: { token: { address: inputs.tokenOut, chainId: inputs.chainId } },
45+
response: ['1000000000000000000'], // 1 token = 1 USD
46+
},
47+
]
48+
49+
it('produces the expected intents', async () => {
50+
const result = await runFunction(functionDir, context, { inputs, calls, prices })
51+
expect(result.success).to.be.true
52+
expect(result.timestamp).to.be.equal(context.timestamp)
53+
54+
expect(result.intents).to.have.lengthOf(1)
55+
const intent = result.intents[0]
56+
57+
expect(intent.feePayer).to.be.equal(context.user)
58+
expect(intent.feePayer).to.be.equal(context.user)
59+
expect(intent.maxFees).to.have.lengthOf(1)
60+
expect(intent.maxFees[0].token).to.be.equal('0x0000000000000000000000000000000000000348')
61+
expect(intent.maxFees[0].amount).to.be.equal('100000000000000000')
62+
expect(intent.operations).to.have.lengthOf(2)
63+
expect(intent.settler).to.be.equal(context.settlers[0].address)
64+
65+
const swap = intent.operations[0] as SwapOperation
66+
const call = intent.operations[1] as EvmDynamicCallOperation
67+
expect(swap.opType).to.be.equal(OpType.Swap)
68+
expect(call.opType).to.be.equal(OpType.EvmDynamicCall)
69+
70+
expect(swap.user).to.be.equal(context.user)
71+
expect(call.user).to.be.equal(inputs.smartAccount)
72+
73+
expect(swap.sourceChain).to.be.equal(inputs.chainId)
74+
expect(swap.destinationChain).to.be.equal(inputs.chainId)
75+
expect(call.chainId).to.be.equal(inputs.chainId)
76+
77+
expect(call.calls).to.have.lengthOf(1)
78+
expect(call.calls[0].target).to.be.equal(inputs.tokenOut)
79+
expect(call.calls[0].arguments).to.have.lengthOf(2)
80+
})
81+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "ESNext",
5+
"moduleResolution": "node",
6+
"esModuleInterop": true,
7+
"allowSyntheticDefaultImports": true,
8+
"strict": true,
9+
"declaration": true,
10+
"composite": true,
11+
"outDir": "./dist",
12+
"rootDir": "./",
13+
"resolveJsonModule": true,
14+
"skipLibCheck": true,
15+
"forceConsistentCasingInFileNames": true,
16+
"lib": ["ES2020", "DOM"],
17+
"types": ["mocha", "chai", "node"]
18+
},
19+
"include": ["./**/*.ts"],
20+
"exclude": ["node_modules", "dist"]
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "assemblyscript/std/assembly.json",
3+
"include": ["./src/**/*.ts"],
4+
"exclude": ["tests/**/*"],
5+
"references": [{ "path": "./tests" }]
6+
}

0 commit comments

Comments
 (0)