Skip to content

Commit

Permalink
feat(ts): morpho vault deposit & withdraw actions (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
stat committed Jan 22, 2025
1 parent 1a29967 commit b5afc7c
Show file tree
Hide file tree
Showing 11 changed files with 713 additions and 2 deletions.
5 changes: 5 additions & 0 deletions cdp-agentkit-core/typescript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Added

- Added `morpho_deposit` action to deposit to Morpho Vault.
- Added `morpho_withdrawal` action to withdraw from Morpho Vault.

## [0.0.12] - 2025-01-17

### Added
Expand Down
14 changes: 14 additions & 0 deletions cdp-agentkit-core/typescript/src/actions/cdp/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const ERC20_APPROVE_ABI = [
{
constant: false,
inputs: [
{ internalType: "address", name: "spender", type: "address" },
{ internalType: "uint256", name: "value", type: "uint256" },
],
name: "approve",
outputs: [{ internalType: "bool", name: "", type: "bool" }],
payable: false,
stateMutability: "nonpayable",
type: "function",
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const MORPHO_BASE_ADDRESS = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb";

export const METAMORPHO_ABI = [
{
inputs: [
{ internalType: "uint256", name: "assets", type: "uint256" },
{ internalType: "address", name: "receiver", type: "address" },
],
name: "deposit",
outputs: [{ internalType: "uint256", name: "shares", type: "uint256" }],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{ internalType: "uint256", name: "assets", type: "uint256" },
{ internalType: "address", name: "receiver", type: "address" },
{ internalType: "address", name: "owner", type: "address" },
],
name: "withdraw",
outputs: [{ internalType: "uint256", name: "shares", type: "uint256" }],
stateMutability: "nonpayable",
type: "function",
},
];
112 changes: 112 additions & 0 deletions cdp-agentkit-core/typescript/src/actions/cdp/defi/morpho/deposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Asset, Wallet } from "@coinbase/coinbase-sdk";
import { z } from "zod";
import { Decimal } from "decimal.js";

import { CdpAction } from "../../cdp_action";
import { approve } from "../../utils";

import { METAMORPHO_ABI } from "./constants";

const DEPOSIT_PROMPT = `
This tool allows depositing assets into a Morpho Vault.
It takes:
- vaultAddress: The address of the Morpho Vault to deposit to
- assets: The amount of assets to deposit in whole units
Examples for WETH:
- 1 WETH
- 0.1 WETH
- 0.01 WETH
- receiver: The address to receive the shares
- tokenAddress: The address of the token to approve
Important notes:
- Make sure to use the exact amount provided. Do not convert units for assets for this action.
- Please use a token address (example 0x4200000000000000000000000000000000000006) for the tokenAddress field. If you are unsure of the token address, please clarify what the requested token address is before continuing.
`;

/**
* Input schema for Morpho Vault deposit action.
*/
export const MorphoDepositInput = z
.object({
assets: z
.string()
.regex(/^\d+(\.\d+)?$/, "Must be a valid integer or decimal value")
.describe("The quantity of assets to deposit, in whole units"),
receiver: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe(
"The address that will own the position on the vault which will receive the shares",
),
tokenAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The address of the assets token to approve for deposit"),
vaultAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The address of the Morpho Vault to deposit to"),
})
.describe("Input schema for Morpho Vault deposit action");

/**
* Deposits assets into a Morpho Vault
* @param Wallet - The wallet instance to execute the transaction
* @param args - The input arguments for the action
* @returns A success message with transaction details or an error message
*/
export async function depositToMorpho(
wallet: Wallet,
args: z.infer<typeof MorphoDepositInput>,
): Promise<string> {
const assets = new Decimal(args.assets);

if (assets.comparedTo(new Decimal(0.0)) != 1) {
return "Error: Assets amount must be greater than 0";
}

try {
const tokenAsset = await Asset.fetch(wallet.getNetworkId(), args.tokenAddress);
const atomicAssets = tokenAsset.toAtomicAmount(assets);

const approvalResult = await approve(
wallet,
args.tokenAddress,
args.vaultAddress,
atomicAssets,
);
if (approvalResult.startsWith("Error")) {
return `Error approving Morpho Vault as spender: ${approvalResult}`;
}

const contractArgs = {
assets: atomicAssets.toString(),
receiver: args.receiver,
};

const invocation = await wallet.invokeContract({
contractAddress: args.vaultAddress,
method: "deposit",
abi: METAMORPHO_ABI,
args: contractArgs,
});

const result = await invocation.wait();

return `Deposited ${args.assets} to Morpho Vault ${args.vaultAddress} with transaction hash: ${result.getTransactionHash()} and transaction link: ${result.getTransactionLink()}`;
} catch (error) {
return `Error depositing to Morpho Vault: ${error}`;
}
}

/**
* Morpho Vault deposit action.
*/
export class MorphoDepositAction implements CdpAction<typeof MorphoDepositInput> {
public name = "morpho_deposit";
public description = DEPOSIT_PROMPT;
public argsSchema = MorphoDepositInput;
public func = depositToMorpho;
}
18 changes: 18 additions & 0 deletions cdp-agentkit-core/typescript/src/actions/cdp/defi/morpho/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CdpAction, CdpActionSchemaAny } from "../../cdp_action";

import { MorphoDepositAction } from "./deposit";
import { MorphoWithdrawAction } from "./withdraw";

/**
* Retrieves all Morpho action instances.
* WARNING: All new Morpho action classes must be instantiated here to be discovered.
*
* @returns - Array of Morpho action instances
*/
export function getAllMorphoActions(): CdpAction<CdpActionSchemaAny>[] {
return [new MorphoDepositAction(), new MorphoWithdrawAction()];
}

export const MORPHO_ACTIONS = getAllMorphoActions();

export { MorphoDepositAction, MorphoWithdrawAction };
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Asset, Wallet } from "@coinbase/coinbase-sdk";
import { z } from "zod";

import { CdpAction } from "../../cdp_action";
import { METAMORPHO_ABI } from "./constants";

const WITHDRAW_PROMPT = `
This tool allows withdrawing assets from a Morpho Vault. It takes:
- vaultAddress: The address of the Morpho Vault to withdraw from
- assets: The amount of assets to withdraw in atomic units
- receiver: The address to receive the shares
`;

/**
* Input schema for Morpho Vault withdraw action.
*/
export const MorphoWithdrawInput = z
.object({
vaultAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The address of the Morpho Vault to withdraw from"),
assets: z
.string()
.regex(/^\d+$/, "Must be a valid whole number")
.describe("The amount of assets to withdraw in atomic units e.g. 1"),
receiver: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The address to receive the shares"),
})
.strip()
.describe("Input schema for Morpho Vault withdraw action");

/**
* Withdraw assets from a Morpho Vault.
*
* @param wallet - The wallet to execute the withdrawal from
* @param args - The input arguments for the action
* @returns A success message with transaction details or error message
*/
export async function withdrawFromMorpho(
wallet: Wallet,
args: z.infer<typeof MorphoWithdrawInput>,
): Promise<string> {
if (BigInt(args.assets) <= 0) {
return "Error: Assets amount must be greater than 0";
}

try {
const invocation = await wallet.invokeContract({
contractAddress: args.vaultAddress,
method: "withdraw",
abi: METAMORPHO_ABI,
args: {
assets: args.assets,
receiver: args.receiver,
owner: args.receiver,
},
});

const result = await invocation.wait();

return `Withdrawn ${args.assets} from Morpho Vault ${args.vaultAddress} with transaction hash: ${result.getTransaction().getTransactionHash()} and transaction link: ${result.getTransaction().getTransactionLink()}`;
} catch (error) {
return `Error withdrawing from Morpho Vault: ${error}`;
}
}

/**
* Morpho Vault withdraw action.
*/
export class MorphoWithdrawAction implements CdpAction<typeof MorphoWithdrawInput> {
public name = "morpho_withdraw";
public description = WITHDRAW_PROMPT;
public argsSchema = MorphoWithdrawInput;
public func = withdrawFromMorpho;
}
9 changes: 7 additions & 2 deletions cdp-agentkit-core/typescript/src/actions/cdp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { TradeAction } from "./trade";
import { TransferAction } from "./transfer";
import { TransferNftAction } from "./transfer_nft";
import { WrapEthAction } from "./wrap_eth";
import { WOW_ACTIONS } from "./defi/wow";

import { MORPHO_ACTIONS } from "./defi/morpho";
import { PYTH_ACTIONS } from "./data/pyth";
import { WOW_ACTIONS } from "./defi/wow";

/**
* Retrieves all CDP action instances.
Expand All @@ -37,7 +39,10 @@ export function getAllCdpActions(): CdpAction<CdpActionSchemaAny>[] {
];
}

export const CDP_ACTIONS = getAllCdpActions().concat(WOW_ACTIONS).concat(PYTH_ACTIONS);
export const CDP_ACTIONS = getAllCdpActions()
.concat(MORPHO_ACTIONS)
.concat(PYTH_ACTIONS)
.concat(WOW_ACTIONS);

export {
CdpAction,
Expand Down
36 changes: 36 additions & 0 deletions cdp-agentkit-core/typescript/src/actions/cdp/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Wallet } from "@coinbase/coinbase-sdk";

import { ERC20_APPROVE_ABI } from "./constants";

/**
* Approve a spender to spend a specified amount of tokens.
* @param wallet - The wallet to execute the approval from
* @param tokenAddress - The address of the token contract
* @param spender - The address of the spender
* @param amount - The amount of tokens to approve
* @returns A success message with transaction hash or error message
*/
export async function approve(
wallet: Wallet,
tokenAddress: string,
spender: string,
amount: bigint,
): Promise<string> {
try {
const invocation = await wallet.invokeContract({
contractAddress: tokenAddress,
method: "approve",
abi: ERC20_APPROVE_ABI,
args: {
spender: spender,
value: amount.toString(),
},
});

const result = await invocation.wait();

return `Approved ${amount} tokens for ${spender} with transaction hash: ${result.getTransactionHash()}`;
} catch (error) {
return `Error approving tokens: ${error}`;
}
}
Loading

0 comments on commit b5afc7c

Please sign in to comment.