Skip to content

Commit 47d4371

Browse files
mmarfinetz0xmarfwtfsayo
authored
feat: Add arbitrage plugin with example character (#2784)
* feat: Add arbitrage plugin with example character * update intialization * Update .env.example --------- Co-authored-by: 0xmarf <mitch@integral.link> Co-authored-by: Sayo <hi@sayo.wtf>
1 parent a3133ed commit 47d4371

25 files changed

+3193
-1041
lines changed

.env.example

+12
Original file line numberDiff line numberDiff line change
@@ -931,3 +931,15 @@ BTC_FUNDRAISING_CAP=100 # Maximum amount for fundraising
931931
# Trikon Plugin Configuration
932932
TRIKON_WALLET_ADDRESS= # Your Trikon wallet address (must be a valid 64-character hex string starting with '0x')
933933
TRIKON_INITIAL_BALANCE= # (Optional) The initial balance for the wallet. Defaults to "0" if not provided.
934+
935+
####################################
936+
#### Arbitrage Plugin Configuration ####
937+
####################################
938+
939+
ARBITRAGE_ETHEREUM_WS_URL= # WebSocket URL for Ethereum node connection
940+
ARBITRAGE_EVM_PROVIDER_URL= # RPC URL for Ethereum node connection (if WS not available)
941+
ARBITRAGE_EVM_PRIVATE_KEY= # Private key for the wallet executing arbitrage transactions
942+
FLASHBOTS_RELAY_SIGNING_KEY= # Signing key for Flashbots relay interactions
943+
BUNDLE_EXECUTOR_ADDRESS= # Address of the bundle executor contract
944+
945+

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"@elizaos/client-xmtp": "workspace:*",
149149
"@elizaos/plugin-trikon": "workspace:*",
150150
"@elizaos/plugin-zilliqa": "workspace:*",
151+
"@elizaos/plugin-arbitrage": "workspace:*",
151152
"readline": "1.3.0",
152153
"ws": "8.18.0",
153154
"yargs": "17.7.2"

agent/src/index.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ import { MongoClient } from "mongodb";
150150
import { quickIntelPlugin } from "@elizaos/plugin-quick-intel"
151151

152152
import { trikonPlugin } from "@elizaos/plugin-trikon"
153+
import arbitragePlugin from "@elizaos/plugin-arbitrage"
153154
const __filename = fileURLToPath(import.meta.url) // get the resolved path to the file
154155
const __dirname = path.dirname(__filename) // get the name of the directory
155156

@@ -632,9 +633,9 @@ export async function initializeClients(character: Character, runtime: IAgentRun
632633
}
633634

634635
if (clientTypes.includes(Clients.XMTP)) {
635-
const xmtpClient = await XmtpClientInterface.start(runtime);
636-
if (xmtpClient) clients.xmtp = xmtpClient;
637-
}
636+
const xmtpClient = await XmtpClientInterface.start(runtime);
637+
if (xmtpClient) clients.xmtp = xmtpClient;
638+
}
638639

639640
if (clientTypes.includes(Clients.DISCORD)) {
640641
const discordClient = await DiscordClientInterface.start(runtime)
@@ -929,6 +930,11 @@ export async function createAgent(character: Character, db: IDatabaseAdapter, ca
929930
getSecret(character, "QUICKINTEL_API_KEY") ? quickIntelPlugin : null,
930931
getSecret(character, "GELATO_RELAY_API_KEY") ? gelatoPlugin : null,
931932
getSecret(character, "TRIKON_WALLET_ADDRESS") ? trikonPlugin : null,
933+
getSecret(character, "ARBITRAGE_EVM_PRIVATE_KEY") &&
934+
(getSecret(character, "ARBITRAGE_EVM_PROVIDER_URL")
935+
|| getSecret(character, "ARBITRAGE_ETHEREUM_WS_URL"))
936+
&& getSecret(character, "ARBITRAGE_FLASHBOTS_RELAY_SIGNING_KEY")
937+
&& getSecret(character, "ARBITRAGE_BUNDLE_EXECUTOR_ADDRESS") ? arbitragePlugin : null,
932938
].flat().filter(Boolean),
933939
providers: [],
934940
managers: [],

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"@ai-sdk/provider": "1.0.6",
5858
"@ai-sdk/provider-utils": "2.1.2",
5959
"cookie": "0.7.0",
60-
"bs58": "5.0.0"
60+
"bs58": "5.0.0",
61+
"@coral-xyz/anchor": "0.28.0"
6162
}
6263
},
6364
"engines": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{
2+
"name": "Trader",
3+
"description": "A trading bot that specializes in crypto arbitrage",
4+
"clients": ["direct"],
5+
"modelProvider": "anthropic",
6+
"settings": {
7+
"secrets": {
8+
"EVM_PRIVATE_KEY": "YOUR_PRIVATE_KEY_HERE",
9+
"FLASHBOTS_RELAY_SIGNING_KEY": "YOUR_FLASHBOTS_KEY_HERE",
10+
"BUNDLE_EXECUTOR_ADDRESS": "YOUR_EXECUTOR_ADDRESS_HERE"
11+
},
12+
"arbitrage": {
13+
"ethereumWsUrl": "YOUR_ETH_WSS_URL",
14+
"rpcUrl": "YOUR_ETH_RPC_URL"
15+
}
16+
},
17+
"plugins": [
18+
"@elizaos/plugin-arbitrage",
19+
"@elizaos/plugin-evm"
20+
],
21+
"modelSettings": {
22+
"provider": "anthropic",
23+
"model": "claude-3-sonnet-20240229"
24+
},
25+
"bio": [
26+
"Expert in cryptocurrency trading and arbitrage.",
27+
"Specializes in identifying profitable trading opportunities.",
28+
"Monitors multiple exchanges for price differences.",
29+
"Provides real-time market analysis and insights."
30+
],
31+
"lore": [
32+
"Created to help traders identify and execute profitable arbitrage opportunities.",
33+
"Trained on extensive market data and trading patterns."
34+
],
35+
"knowledge": [
36+
"Understands cryptocurrency market dynamics",
37+
"Knows how to identify arbitrage opportunities",
38+
"Can analyze trading pairs across different exchanges",
39+
"Monitors market conditions in real-time"
40+
],
41+
"messageExamples": [
42+
[
43+
{
44+
"user": "{{user1}}",
45+
"content": { "text": "analyze BTC/ETH pair for arbitrage opportunities" }
46+
},
47+
{
48+
"user": "Trader",
49+
"content": {
50+
"text": "I'll analyze the BTC/ETH trading pair for potential arbitrage opportunities across different exchanges."
51+
}
52+
}
53+
],
54+
[
55+
{
56+
"user": "{{user1}}",
57+
"content": { "text": "check current market conditions" }
58+
},
59+
{
60+
"user": "Trader",
61+
"content": {
62+
"text": "I'll check the current market conditions and look for profitable trading opportunities."
63+
}
64+
}
65+
]
66+
],
67+
"postExamples": [
68+
"Market Analysis: Current arbitrage opportunities in the BTC/ETH market",
69+
"Trading Update: Identified profitable arbitrage paths between exchanges",
70+
"Market Alert: Significant price divergence detected between exchanges",
71+
"Strategy Overview: Best practices for arbitrage trading"
72+
],
73+
"topics": [
74+
"cryptocurrency trading",
75+
"arbitrage opportunities",
76+
"market analysis",
77+
"trading strategies",
78+
"price analysis",
79+
"exchange monitoring",
80+
"risk management",
81+
"trading automation"
82+
],
83+
"adjectives": [
84+
"analytical",
85+
"precise",
86+
"professional",
87+
"strategic",
88+
"vigilant",
89+
"data-driven",
90+
"methodical",
91+
"efficient"
92+
],
93+
"style": {
94+
"all": [
95+
"Keep responses clear and data-driven",
96+
"Focus on market opportunities",
97+
"Provide actionable insights",
98+
"Be professional and precise",
99+
"Use clear market terminology",
100+
"Always consider risk management"
101+
],
102+
"chat": [
103+
"Provide detailed market analysis",
104+
"Be direct and informative",
105+
"Focus on actionable opportunities",
106+
"Maintain professional tone"
107+
],
108+
"post": [
109+
"Share clear market insights",
110+
"Highlight significant opportunities",
111+
"Include relevant market data",
112+
"Maintain analytical perspective"
113+
]
114+
}
115+
}
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@elizaos/plugin-arbitrage",
3+
"version": "0.1.0",
4+
"description": "Arbitrage trading plugin for Eliza",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"exports": {
8+
".": {
9+
"types": "./dist/index.d.ts",
10+
"import": "./dist/index.mjs",
11+
"require": "./dist/index.js",
12+
"default": "./dist/index.js"
13+
}
14+
},
15+
"scripts": {
16+
"clean": "rm -rf dist",
17+
"build": "tsup --format esm --dts",
18+
"dev": "tsup --format esm --dts --watch"
19+
},
20+
"dependencies": {
21+
"@elizaos/adapter-sqlite": "^0.1.8",
22+
"@elizaos/core": "workspace:*",
23+
"@ethersproject/abi": "^5.7.0",
24+
"@ethersproject/abstract-provider": "^5.7.0",
25+
"@ethersproject/address": "^5.7.0",
26+
"@ethersproject/bignumber": "^5.7.0",
27+
"@ethersproject/contracts": "^5.7.0",
28+
"@ethersproject/providers": "^5.7.2",
29+
"@ethersproject/units": "^5.7.0",
30+
"@ethersproject/wallet": "^5.7.0",
31+
"@flashbots/ethers-provider-bundle": "0.6.2",
32+
"dotenv": "^16.4.7",
33+
"ethers": "5.7.2",
34+
"lodash": "^4.17.21",
35+
"ws": "^8.18.0"
36+
},
37+
"devDependencies": {
38+
"@types/lodash": "^4.17.14",
39+
"@types/node": "^22.10.9",
40+
"@types/ws": "^8.5.13",
41+
"rimraf": "^5.0.5",
42+
"typescript": "^5.7.3",
43+
"@types/dotenv": "^8.2.0",
44+
"tsup": "^8.0.2"
45+
},
46+
"peerDependencies": {
47+
"@elizaos/core": "workspace:*"
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Action, IAgentRuntime, Memory, ServiceType } from "@elizaos/core";
2+
import { ArbitrageService } from "../services/ArbitrageService";
3+
4+
export const executeArbitrageAction: Action = {
5+
name: "EXECUTE_ARBITRAGE",
6+
similes: ["TRADE_ARBITRAGE", "RUN_ARBITRAGE"],
7+
description: "Execute arbitrage trades across markets",
8+
9+
validate: async (runtime: IAgentRuntime, message: Memory) => {
10+
// Validate settings are present
11+
return runtime.getSetting("arbitrage.walletPrivateKey") !== undefined;
12+
},
13+
14+
handler: async (runtime: IAgentRuntime, message: Memory) => {
15+
const service = runtime.getService(ServiceType.ARBITRAGE) as ArbitrageService;
16+
const markets = await service.evaluateMarkets();
17+
18+
if (markets.length > 0) {
19+
await service.executeArbitrage(markets);
20+
}
21+
22+
return true;
23+
},
24+
25+
examples: [
26+
[
27+
{
28+
user: "{{user1}}",
29+
content: { text: "Find arbitrage opportunities" }
30+
},
31+
{
32+
user: "{{user2}}",
33+
content: {
34+
text: "Scanning for arbitrage trades",
35+
action: "EXECUTE_ARBITRAGE"
36+
}
37+
}
38+
]
39+
]
40+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const UNISWAP_LOOKUP_CONTRACT_ADDRESS = '0x5EF1009b9FCD4fec3094a5564047e190D72Bd511'
2+
//mainnet ^^ goerli vv
3+
//export const UNISWAP_LOOKUP_CONTRACT_ADDRESS = '0xF52FE911458C6a3279832b764cDF0189e49f073A'
4+
export const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
5+
export const SUSHISWAP_FACTORY_ADDRESS = '0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac';
6+
export const UNISWAP_FACTORY_ADDRESS = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f';
7+
8+
//export const CRO_FACTORY_ADDRESS = "0x9DEB29c9a4c7A88a3C0257393b7f3335338D9A9D";
9+
//export const ZEUS_FACTORY_ADDRESS = "0xbdda21dd8da31d5bee0c9bb886c044ebb9b8906a";
10+
//export const LUA_FACTORY_ADDRESS = "0x0388c1e0f210abae597b7de712b9510c6c36c857";
11+
12+
export const FACTORY_ADDRESSES = [
13+
//CRO_FACTORY_ADDRESS,
14+
//ZEUS_FACTORY_ADDRESS,
15+
//LUA_FACTORY_ADDRESS,
16+
SUSHISWAP_FACTORY_ADDRESS,
17+
UNISWAP_FACTORY_ADDRESS,
18+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { BigNumber } from "ethers";
2+
3+
export interface MarketThresholds {
4+
minProfitThreshold: BigNumber;
5+
maxTradeSize: BigNumber;
6+
gasLimit: number;
7+
minerRewardPercentage: number;
8+
}
9+
10+
export const DEFAULT_THRESHOLDS: MarketThresholds = {
11+
minProfitThreshold: BigNumber.from("100000000000000"), // 0.0001 ETH
12+
maxTradeSize: BigNumber.from("1000000000000000000"), // 1 ETH
13+
gasLimit: 500000,
14+
minerRewardPercentage: 90
15+
};

0 commit comments

Comments
 (0)