Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions projects/enso/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
6 changes: 6 additions & 0 deletions projects/enso/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 4,
"printWidth": 180
}
61 changes: 61 additions & 0 deletions projects/enso/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Enso

Enso

## Supported Networks

- ETHEREUM
- OPTIMISM
- BSC
- GNOSIS
- POLYGON
- ZKSYNC
- BASE
- ARBITRUM
- AVALANCHE
- SEPOLIA

## Common Tasks

1. Basic Operations

- "Swap 15000 USDT to WETH on @enso on Optimism network"
- "Redeem 0.01 WETH-USDT UNI-V2 LP token for WBTC on @enso on Ethereum network"
- "Deposit 10 ETH into a Curve pool on @enso"
- "Withdraw aEthWBTC from Aave V3 for USDC on @enso"

2. Information Queries

- "Show me supported tokens on @enso"
- "Is USDC supported on @enso on Base network"
- "Show me supported protocols on @enso on Etheruem network"
- "Is Uniswap supported on @enso on Ethereum network"

## Available Functions

### Actions

- route

### Getters

- getProtocol
- getProtocols
- getToken
- getTokens

## Documentation

- [Router API reference](https://api-docs.enso.build/router-api/introduction)
- [Tokens API reference](https://api-docs.enso.build/metadata-api/api-reference/tokens)
- [Protocols API reference](https://api-docs.enso.build/metadata-api/api-reference/protocols)

## Installation

```bash
yarn add @heyanon/enso
```

## Usage

Example usage will be added here.
19 changes: 19 additions & 0 deletions projects/enso/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ChainId } from '@heyanon/sdk';

export const supportedChains = [
ChainId.ETHEREUM,
ChainId.OPTIMISM,
ChainId.BSC,
ChainId.GNOSIS,
ChainId.POLYGON,
ChainId.ZKSYNC,
ChainId.BASE,
ChainId.ARBITRUM,
ChainId.AVALANCHE,
ChainId.SEPOLIA,
];

export const ENSO_API = 'https://api.enso.finance/api/v1' as const;
export const ENSO_API_TOKEN = '1e02632d-6feb-4a75-a157-documentation' as const;

export const ENSO_ETH = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as const;
44 changes: 44 additions & 0 deletions projects/enso/functions/getProtocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FunctionReturn, toResult, getChainFromName } from '@heyanon/sdk';
import { ENSO_API_TOKEN, supportedChains } from '../constants';
import axios from 'axios';
import { EnsoApiProtocol } from './getProtocols';
import { EnsoClient } from '@ensofinance/sdk';

interface Props {
chainName: string;
protocol: string;
}

/**
* Search for a protocol by its slug on specified chain
* @param props - The function parameters
* @returns Protocol
*/
export async function getProtocol({ chainName, protocol }: Props): Promise<FunctionReturn> {
const chainId = getChainFromName(chainName);
if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true);
if (!protocol) return toResult(`No protocol provided`, true);
if (!supportedChains.includes(chainId)) return toResult(`Enso is not supported on ${chainName}`, true);

try {
const ensoClient = new EnsoClient({
apiKey: ENSO_API_TOKEN,
});
const protocolData = await ensoClient.getProtocolData({ slug: protocol });

if (protocolData.length == 0) {
return toResult(`Protocol ${protocol} not found`, true);
}

const isChainSupported = protocolData[0].chains.some((chain) => chain.id === chainId);
if (!isChainSupported) return toResult(`Protocol ${protocol} is not supported on ${chainName}`, true);

delete (protocolData[0] as Partial<EnsoApiProtocol>).chains;
return toResult(JSON.stringify(protocolData));
} catch (e) {
if (axios.isAxiosError(e)) {
return toResult(`API error: ${e.message}`, true);
}
return toResult(`Unknown error when fetching protocol from Enso API`, true);
}
}
48 changes: 48 additions & 0 deletions projects/enso/functions/getProtocols.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FunctionReturn, toResult, getChainFromName } from '@heyanon/sdk';
import { ENSO_API_TOKEN, supportedChains } from '../constants';
import axios from 'axios';
import { EnsoClient } from '@ensofinance/sdk';

interface Props {
chainName: string;
}

export interface EnsoApiProtocol {
chains: { name: string; id: number }[];
name: string | null;
description: string | null;
slug: string;
url: string;
logosUri: string[];
}

/**
* Get protocols that are supported by Enso on specified chain
* @param props - The function parameters
* @returns List of protocol slugs
*/
export async function getProtocols({ chainName }: Props): Promise<FunctionReturn> {
const chainId = getChainFromName(chainName);
if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true);
if (!supportedChains.includes(chainId)) return toResult(`Enso is not supported on ${chainName}`, true);

try {
const ensoClient = new EnsoClient({ apiKey: ENSO_API_TOKEN });
const protocolData = await ensoClient.getProtocolData();

const protocols: string[] = [];
for (const protocol of protocolData) {
const hasChain = protocol.chains.some((chain) => chain.id === chainId);
if (hasChain) {
protocols.push(protocol.slug);
}
}

return toResult(JSON.stringify(protocols));
} catch (e) {
if (axios.isAxiosError(e)) {
return toResult(`API error: ${e.message}`, true);
}
return toResult(`Unknown error when fetching protocols from Enso API`, true);
}
}
38 changes: 38 additions & 0 deletions projects/enso/functions/getToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { FunctionReturn, toResult, getChainFromName } from '@heyanon/sdk';
import { ENSO_API_TOKEN, supportedChains } from '../constants';
import axios from 'axios';
import { Address } from 'viem';
import { EnsoClient } from '@ensofinance/sdk';

interface Props {
chainName: string;
address: Address;
}

/**
* Search for a token by its address
* @param props - The function parameters
* @returns Token
*/
export async function getToken({ chainName, address }: Props): Promise<FunctionReturn> {
const chainId = getChainFromName(chainName);
if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true);
if (!address) return toResult('Token address not provided', true);
if (!supportedChains.includes(chainId)) return toResult(`Enso is not supported on ${chainName}`, true);

try {
const ensoClient = new EnsoClient({ apiKey: ENSO_API_TOKEN });
const tokenData = await ensoClient.getTokenData({ chainId, address });

if (tokenData.data.length === 0) {
return toResult('Token not found', true);
}

return toResult(JSON.stringify(tokenData.data[0]));
} catch (e) {
if (axios.isAxiosError(e)) {
return toResult(`API error: ${e.message}`, true);
}
return toResult(`Unknown error when fetching token from Enso API`, true);
}
}
45 changes: 45 additions & 0 deletions projects/enso/functions/getTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FunctionReturn, toResult, getChainFromName } from '@heyanon/sdk';
import { ENSO_API, ENSO_API_TOKEN, supportedChains } from '../constants';
import axios from 'axios';

interface Props {
chainName: string;
}

export interface EnsoApiToken {
chainId: number;
address: string;
decimals: number;
type: string;
}

// NOTE: Not sure if this should be kept or getToken is enough
// This method in Enso SDK is not supported
/**
* Get tokens that are supported by Enso on specified chain
* @param props - The function parameters
* @returns List of protocols
*/
export async function getTokens({ chainName }: Props): Promise<FunctionReturn> {
const chainId = getChainFromName(chainName);
if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true);
if (!supportedChains.includes(chainId)) return toResult(`Enso is not supported on ${chainName}`, true);

try {
const url = `${ENSO_API}/tokens?chainId=${chainId}&page=1`;
const res = await axios.get<{ data: EnsoApiToken[] }>(url, { headers: { Authorization: `Bearer ${ENSO_API_TOKEN}` } });

// NOTE: Not sure if only address should be returned or address+symbol/address+name
// as well. Maybe it is benefitial for the LLM to have name/symbol as well
const tokens = res.data.data.map((token) => {
return token.address;
});

return toResult(JSON.stringify(tokens));
} catch (e) {
if (axios.isAxiosError(e)) {
return toResult(`API error: ${e.message}`, true);
}
return toResult(`Unknown error when fetching tokens from Enso API`, true);
}
}
5 changes: 5 additions & 0 deletions projects/enso/functions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { getProtocols } from './getProtocols';
export { getProtocol } from './getProtocol';
export { getTokens } from './getTokens';
export { getToken } from './getToken';
export { route } from './route';
111 changes: 111 additions & 0 deletions projects/enso/functions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { FunctionReturn, toResult, getChainFromName, FunctionOptions, TransactionParams, checkToApprove } from '@heyanon/sdk';
import { ENSO_API_TOKEN, ENSO_ETH, supportedChains } from '../constants';
import axios from 'axios';
import { Address, Hex, isAddress } from 'viem';
import { EnsoClient, RouteParams } from '@ensofinance/sdk';

interface Props {
chainName: string;
account: Address;
tokenIn: Address;
tokenOut: Address;
amountIn: string;
amountOut?: string;
receiver?: Address;
spender?: Address;
slippage?: number;
fee?: number;
feeReceiver?: Address;
}

/**
* Best route from a token to a token
* @param props - The function parameters
*/
export async function route(
{ chainName, tokenIn, tokenOut, amountIn, spender, receiver, slippage, amountOut, account, fee, feeReceiver }: Props,
{ sendTransactions, notify, getProvider }: FunctionOptions,
): Promise<FunctionReturn> {
if (!account) return toResult('No account provided', true);
if (!tokenIn) return toResult('No tokenIn provided', true);
if (!tokenOut) return toResult('No tokenOut provided', true);
if (!amountIn) return toResult('No amountIn provided', true);

const chainId = getChainFromName(chainName);
if (!chainId) return toResult(`Unsupported chain name: ${chainName}`, true);
if (!supportedChains.includes(chainId)) return toResult(`Enso is not supported on ${chainName}`, true);

const params: RouteParams = {
chainId,
tokenIn,
tokenOut,
amountIn,
fromAddress: account,
receiver: receiver || account,
spender: spender || account,
};

if (!isAddress(params.receiver)) {
return toResult('Receiver is not an address', true);
}
if (!isAddress(params.spender)) {
return toResult('Spender is not an address', true);
}

if (amountOut) {
params.minAmountOut = amountOut;
}
if (slippage) {
if (slippage > 10_000 || slippage < 0) {
return toResult('Slippage is outside of 0-10000 range', true);
}
params.slippage = slippage;
}
if (fee) {
if (fee > 100 || fee < 0) {
return toResult('Fee is outside of 0-100 range', true);
}
params.fee = fee;
}
if (feeReceiver) {
if (!isAddress(feeReceiver)) {
return toResult('Fee receiver is not an address', true);
}
params.feeReceiver = feeReceiver;
}

try {
const ensoClient = new EnsoClient({ apiKey: ENSO_API_TOKEN });
await notify('Fetching the best route from Enso API');
const routeData = await ensoClient.getRouterData(params);

const provider = getProvider(chainId);
const transactions: TransactionParams[] = [];

if (tokenIn !== ENSO_ETH) {
await checkToApprove({
args: {
account,
target: tokenIn,
spender: routeData.tx.to,
amount: BigInt(amountIn),
},
provider,
transactions,
});
}

const tx: TransactionParams = { target: routeData.tx.to, data: routeData.tx.data as Hex, value: BigInt(routeData.tx.value) };
transactions.push(tx);

const result = await sendTransactions({ chainId, account, transactions });
const depositMessage = result.data[result.data.length - 1];

return toResult(result.isMultisig ? depositMessage.message : `Successfully executed route. ${depositMessage.message}`);
} catch (e) {
if (axios.isAxiosError(e)) {
return toResult(`API error: ${e.message}`, true);
}
return toResult(`Unknown error when fetching route from Enso API`, true);
}
}
9 changes: 9 additions & 0 deletions projects/enso/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AdapterExport } from '@heyanon/sdk';
import { tools } from './tools';
import * as functions from './functions';

export default {
tools,
functions,
description: 'Enso',
} satisfies AdapterExport;
Loading