Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Swapping and Bridging from other ecosystem with Layerzero #12

Merged
merged 5 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 3 additions & 6 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,13 @@
"@elizaos/core": "workspace:*",
"@elizaos/plugin-bootstrap": "workspace:*",
"json5": "2.2.3",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
},
"devDependencies": {
"@types/node": "^22.10.5",
"@types/node": "^22.13.5",
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"ts-node": "10.9.2",
"tsup": "8.3.5"
"ts-jest": "^29.2.6",
"ts-node": "^10.9.2"
}
}
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
"husky": "9.1.7",
"jest": "^29.7.0",
"lerna": "8.1.5",
"nodemon": "3.1.7",
"only-allow": "1.2.1",
"turbo": "2.3.3",
"turbo": "2.4.4",
"typedoc": "0.26.11",
"typescript": "5.6.3",
"viem": "2.21.58",
Expand All @@ -54,9 +55,14 @@
"overrides": {
"@ai-sdk/provider": "1.0.6",
"@ai-sdk/provider-utils": "2.1.6",
"@octokit/request-error@>=1.0.0 <5.1.1": ">=5.1.1",
"@octokit/request@>=1.0.0 <9.2.1": ">=9.2.1",
"@octokit/plugin-paginate-rest@>=1.0.0 <11.4.1": ">=11.4.1",
"@onflow/fcl": "1.13.4",
"bs58": "5.0.0",
"cookie": "0.7.0",
"dompurify@<3.2.4": ">=3.2.4",
"esbuild@<=0.24.2": ">=0.25.0",
"onnxruntime-node": "1.20.1",
"viem": "2.21.58",
"axios@>=0.8.1 <0.28.0": ">=0.28.0",
Expand Down
134 changes: 134 additions & 0 deletions packages/plugin-bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Eliza Bridge Plugin

A plugin for Eliza on Flow that enables bridging and swapping tokens between Flow and other ecosystems using LayerZero's OFT (Omnichain Fungible Token) standard.

## Features

- Bridge tokens from other ecosystems to Flow EVM using LayerZero
- Bridge tokens from Flow EVM to other ecosystems using LayerZero
- Swap tokens on Flow EVM
- Check token balances across multiple chains

## Supported Chains

- Flow EVM (Chain ID: 747, LayerZero Endpoint ID: 30747)
- Ethereum (Chain ID: 1, LayerZero Endpoint ID: 30101)
- Arbitrum (Chain ID: 42161, LayerZero Endpoint ID: 30110)
- Optimism (Chain ID: 10, LayerZero Endpoint ID: 30111)
- Base (Chain ID: 8453, LayerZero Endpoint ID: 30184)
- Polygon (Chain ID: 137, LayerZero Endpoint ID: 30109)

## Supported Tokens

- FLOW
- USDC
- ETH

## Installation

```bash
npm install @elizaos-plugins/plugin-bridge
```

## Usage

### Register the plugin

```typescript
import { bridgePlugin } from '@elizaos-plugins/plugin-bridge';

// In your Eliza configuration
const config = {
plugins: [
// other plugins
bridgePlugin,
],
};
```

### Bridge Tokens

```typescript
// Bridge 100 USDC from Ethereum to Flow EVM
const result = await agent.execute("Bridge 100 USDC from Ethereum to Flow EVM");

// Bridge 50 FLOW from Flow EVM to Arbitrum
const result = await agent.execute("Bridge 50 FLOW from Flow EVM to Arbitrum");
```

### Swap Tokens

```typescript
// Swap 50 FLOW to USDC on Flow EVM
const result = await agent.execute("Swap 50 FLOW to USDC");

// Swap with custom slippage
const result = await agent.execute("Swap 100 USDC to FLOW with 1% slippage");
```

### Check Balances

```typescript
// Check FLOW balance on Flow EVM
const result = await agent.execute("Check my FLOW balance on Flow EVM");

// Check all token balances on Ethereum
const result = await agent.execute("Show my balances on Ethereum");
```

## Technical Details

### LayerZero Integration

This plugin uses LayerZero's OFT (Omnichain Fungible Token) standard for cross-chain token transfers. The OFT standard allows for seamless token transfers between different blockchains without the need for wrapped tokens.

#### How it works:

1. **Token Bridging**: When a user initiates a token bridge, the plugin:
- Connects to the source chain
- Approves the OFT contract to spend the tokens
- Calls the `send` function on the OFT contract with the appropriate parameters
- The OFT contract locks the tokens on the source chain and mints equivalent tokens on the destination chain

2. **Fee Estimation**: Before executing a bridge transaction, the plugin estimates the fees by calling the `quoteSend` function on the OFT contract.

3. **Address Conversion**: LayerZero requires recipient addresses to be in bytes32 format. The plugin handles this conversion automatically.

### Token Swapping

For token swapping on Flow EVM, the plugin connects to decentralized exchanges (DEXs) to execute swaps with the following process:

1. Calculate the amount with decimals
2. Calculate minimum amount out based on slippage
3. Execute the swap through the DEX
4. Return the transaction details

## Development

### Build

```bash
npm run build
```

### Development Mode

```bash
npm run dev
```

### Lint

```bash
npm run lint
```

## Resources

- [LayerZero Documentation](https://docs.layerzero.network/)
- [OFT Standard](https://docs.layerzero.network/v2/developers/evm/oft/quickstart)
- [Flow EVM Documentation](https://developers.flow.com/evm)

## License

MIT
36 changes: 36 additions & 0 deletions packages/plugin-bridge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@elizaos-plugins/plugin-bridge",
"version": "0.1.0",
"description": "Eliza plugin for bridging and swapping tokens between Flow and other ecosystems",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src --ext .ts,.tsx",
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@elizaos/core": "^0.1.0",
"@elizaos-plugins/plugin-di": "^0.1.0",
"@elizaos-plugins/plugin-flow": "^0.1.0",
"axios": "^1.6.0",
"inversify": "^6.0.1",
"zod": "^3.22.2"
},
"devDependencies": {
"@types/node": "^20.8.2",
"eslint": "^8.50.0",
"tsup": "^7.2.0",
"typescript": "^5.2.2"
},
"peerDependencies": {
"@elizaos/core": "^0.1.0",
"@elizaos-plugins/plugin-di": "^0.1.0",
"@elizaos-plugins/plugin-flow": "^0.1.0"
}
}
152 changes: 152 additions & 0 deletions packages/plugin-bridge/src/actions/bridge-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// src/actions/bridge-token.ts
import { z } from "zod";
import { inject, injectable } from "inversify";
import {
elizaLogger,
type HandlerCallback,
type IAgentRuntime,
type Memory,
type State,
} from "@elizaos/core";
import { type ActionOptions, globalContainer, property } from "@elizaos-plugins/plugin-di";
import { BaseFlowInjectableAction } from "@elizaos-plugins/plugin-flow";
import { BridgeService } from "../services/bridge.service";
import { CHAIN_CONFIGS, TOKENS, DEFAULT_SLIPPAGE } from "../constants";

export class BridgeTokenContent {
@property({
description: "Source chain name",
schema: z.string(),
examples: ["Example: arbitrum, base, ethereum, optimism, polygon"],
})
sourceChain: string;

@property({
description: "Destination chain name (usually flow-evm)",
schema: z.string(),
examples: ["Example: flow-evm"],
})
destinationChain: string = "flow-evm";

@property({
description: "Token symbol to bridge",
schema: z.string(),
examples: ["Example: USDC, ETH, FLOW"],
})
token: string;

@property({
description: "Amount of tokens to bridge",
schema: z.number().positive(),
})
amount: number;

@property({
description: "Recipient address (defaults to agent's address if not provided)",
schema: z.string().optional(),
})
recipient?: string;

@property({
description: "Slippage percentage (defaults to 0.5%)",
schema: z.number().positive().optional(),
})
slippage?: number = DEFAULT_SLIPPAGE;
}

const actionOpts: ActionOptions<BridgeTokenContent> = {
name: "BRIDGE_TOKEN",
similes: ["BRIDGE_TOKENS", "TRANSFER_TOKENS_CROSS_CHAIN"],
description: "Bridge tokens from another ecosystem to Flow",
examples: [
[
{
user: "{{user1}}",
content: {
text: "Bridge 100 USDC from Arbitrum to Flow",
action: "BRIDGE_TOKEN",
},
},
],
],
contentClass: BridgeTokenContent,
suppressInitialMessage: true,
};

@injectable()
export class BridgeTokenAction extends BaseFlowInjectableAction<BridgeTokenContent> {
constructor(
@inject(BridgeService)
private readonly bridgeService: BridgeService,
@inject("FlowWalletService")
private readonly walletService: any
) {
super(actionOpts);
}

async validate(_runtime: IAgentRuntime, message: Memory): Promise<boolean> {
const content = typeof message.content === "string" ? message.content : message.content?.text;
const keywords = ["bridge", "transfer", "cross-chain"];
return keywords.some(keyword => content.toLowerCase().includes(keyword));
}

async execute(
content: BridgeTokenContent,
_runtime: IAgentRuntime,
_message: Memory,
_state?: State,
callback?: HandlerCallback,
) {
elizaLogger.log("Starting BRIDGE_TOKEN handler...");

try {
// Validate source chain
if (!CHAIN_CONFIGS[content.sourceChain]) {
throw new Error(`Source chain ${content.sourceChain} not supported`);
}

// Validate destination chain
if (!CHAIN_CONFIGS[content.destinationChain]) {
throw new Error(`Destination chain ${content.destinationChain} not supported`);
}

// Validate token
if (!TOKENS[content.token]) {
throw new Error(`Token ${content.token} not supported`);
}

// Use agent's address if recipient not provided
const recipient = content.recipient || this.walletService.address;

const result = await this.bridgeService.bridgeTokens({
sourceChain: content.sourceChain,
destinationChain: content.destinationChain,
amount: content.amount,
token: content.token,
recipient: recipient,
slippage: content.slippage,
});

if (result.success) {
callback?.({
text: `Successfully initiated bridge of ${content.amount} ${content.token} from ${content.sourceChain} to ${content.destinationChain}. Transaction ID: ${result.txId}`,
content: { success: true, ...result },
source: "Bridge",
});
} else {
throw new Error(result.errorMessage);
}
} catch (error) {
callback?.({
text: `Failed to bridge tokens: ${error.message}`,
content: { error: error.message },
source: "Bridge",
});
}

elizaLogger.log("Completed BRIDGE_TOKEN handler.");
}
}

// Register the action with the global container
globalContainer.bind(BridgeTokenAction).toSelf();
Loading
Loading