Permissions for Zodiac Roles covering interactions with DeFi protocols
You can use DeFi Kit as a TypeScript SDK or via a REST API. Below you can find simple examples for both. For an in-depth overview refer to the docs.
yarn add defi-kit
import { apply, allow } from "defi-kit"
const calls = await apply(roleKey, [allow.cowswap.swap(tokenIn, tokenOut)], {
address: rolesModAddress,
mode: "extend",
})
const res = await fetch(
"https://kit.karpatkey.com/api/v1/eth:<MOD>/<ROLE>/allow/cowswap/swap?sell=<TOKEN_IN>&buy=<TOKEN_OUT>"
)
const calls = await res.json()
Install all dependencies:
yarn
Fetch ABIs and generate types for the sdk package:
yarn setup
Start app dev server and watch sources for changes:
yarn dev
Build sdk and app:
yarn build
To run tests, you first need to install anvil
, which comes with foundry. See installation instructions.
Then, you can run tests in watch mode using:
yarn test:watch
-
Create folder named after the protocol you wanna add in sdk/src/protocols.
-
Create a index.ts file in that folder.
-
For every chain that the protocol supports export an object under the chain prefix, e.g.:
export const eth = {} export const gno = {}
-
For every action that the protocol support add a key to each of the chain exports, e.g.:
export const eth = { deposit: async () => [], } export const gno = { deposit: async () => [], }
-
In sdk/src/protocols/index.ts, register the new protocol adding it to the exports for all the chains that it supports, e.g.:
import * as newProtocol from "./newProtocol" export const eth = { ..., // existing entries newProtocol: newProtocol.eth, } satisfies Record<string, ProtocolActions> export const gno = { ..., // existing entries newProtocol: newProtocol.gno, } satisfies Record<string, ProtocolActions>
-
Implement the functions, adhering to the general API for that action type and returning an array of
Permission[]
. See following section for guidance.
Note: For the new protocol functions to become available in the SDK playground, the changes must first be published to npm as a new version of the defi-kit package.
-
Add entries for the contracts you want to allow calling to sdk/eth-sdk/config.ts. If there are multiple instances of the same contract, such as different pool instances, only add a single entry using any exemplary contract address, e.g.:
mainnet: { curve: { regularPool: "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", } }
-
Run
yarn setup
. This will automatically fetch the ABI for the listed contracts. In case this fails, you will have to manually add the ABI json file add the respective location in sdk/eth-sdk/abis. -
Use the typed allow kit that has been generated:
import { allow } from "zodiac-roles-sdk/kit" const permissions = allow.curve.regularPool.exchange()
In case VSCode IntelliSense does not reflect the newly added contract entries, restart the TypeScript server by pressing cmd+shift+p and selecting "TypeScript: Restart TS Server".
Note that you don't have to use typed allow kits but you can always also author permissions manually.
All action functions should be covered with tests to make sure the returned permissions actually allow the desired protocol action and that the protocol action actually leads to the desired state.
-
Use the
applyPermissions
helper function to apply the action's permission to a test role that is prepared as part of the global test setup:import { applyPermissions } from "../../../test/helpers" import { eth } from "." await applyPermissions(await eth.deposit({ targets: ["ETH", "USDC"] }))
-
Use test kit to execute calls to any contract in eth-sdk/config.ts through the test role:
import { testKit } from "../../../test/kit" await testKit.eth.maker.DSProxy.attach(proxyAddress).execute(...args)
-
Use the custom jest matchers to check on the transaction outcome:
import { testKit } from "../../../test/kit" await expect( testKit.eth.maker.DSProxy.attach(proxyAddress).execute(...args) ).not.toRevert()
The test:watch
script allows you to target specific test files rather than running the entire test suite:
yarn test:watch maker/deposit
You can pass any substring of the file paths to the targeted test files. Then you can also use the jest watch cli to control which tests shall be run whenever saving changes.
Once a protocol has been added to the SDK, a couple of extra steps are necessary to make it available also via the REST API.
-
Define the parameter schema for the protocol action. Create a schema file in the protocol folder (src/protocols//schema.ts) and add exports for every supported network and action:
export const eth = { deposit: z.object({ targets: z.enum(...).array() }), } export const gno = { deposit: z.object({ targets: z.enum(...).array() }), }
-
Register the protocol schema in sdk/src/protocols/schema.ts:
import * as newProtocol from "./newProtocol/schema" export const eth = { ..., // existing entries newProtocol: newProtocol.eth, } satisfies Record<string, ProtocolSchemas> export const gno = { ..., // existing entries newProtocol: newProtocol.gno, } satisfies Record<string, ProtocolSchemas>
-
Check that the new endpoints have been registered correctly, by opening https://localhost:3000/api-docs.
We rely on information about each protocol for deriving types and configs. This information is stored in files with names starting with _
that are automatically generated by Python scripts. You find all Python scripts in the scripts directory.
All scripts are run as a nightly job in a Github workflow.
To run a script locally you need to first go through some setup steps:
-
Setup python env, e.g., using anaconda:
conda create --name defi-kit python=3.1 conda activate defi-kit python -m pip install --upgrade pip
-
Install dependencies:
cd scripts pip install -r requirements.txt
-
Ask your colleagues for the config.json file for the defi-protocols package. Store it in scripts/config.json.
-
Create a file scripts/.env with the following content:
CONFIG_PATH=<ABSOLUTE_PATH_TO_CONFIG.JSON>
-
Run any script inside the scripts folder using the following command:
yarn run-script aave_v2.py