LONGYKING commited on
Commit
431e9df
·
1 Parent(s): 9ed1d29

added cross chain swap toolkit

Browse files
Procfile CHANGED
@@ -1 +1 @@
1
- web: docker-compose up --build
 
1
+ web: python -m chainlit run chatxbt-assistant.py -h --port 8080
chatxbt-assistant.py CHANGED
@@ -6,10 +6,12 @@ load_dotenv()
6
  import chainlit as cl
7
  from phi.assistant import Assistant
8
  from phi.llm.openai import OpenAIChat
 
9
  from phi.tools.duckduckgo import DuckDuckGo
10
  from phi.tools.yfinance import YFinanceTools
11
  from src.databases.postgres import sqlalchemy_engine
12
  from src.tools.crypto_swap_toolkit import CryptoSwapTools
 
13
  from src.tools.crypto_data_toolkit import CryptoDataTools
14
  from src.tools.crypto_evm_wallet_toolkit import CryptoEVMWalletTools
15
  from src.tools.user_profile_toolkit import UserProfileToolkit
@@ -85,18 +87,18 @@ async def set_starters():
85
  async def start():
86
  is_dev_mode = True if os.getenv("DEV_MODE") else False
87
 
88
- print(instruction)
89
-
90
  # Initialize the assistant
91
  cxbt_assistant = Assistant(
92
  description=description,
93
  instruction=instruction,
94
- llm=OpenAIChat(model="gpt-4o"),
 
95
  tools=[
96
  UserProfileToolkit(),
97
  DuckDuckGo(),
98
  CryptoDataTools(),
99
  CryptoSwapTools(),
 
100
  CryptoEVMWalletTools(),
101
  YFinanceTools(stock_price=True)
102
  ],
 
6
  import chainlit as cl
7
  from phi.assistant import Assistant
8
  from phi.llm.openai import OpenAIChat
9
+ from phi.llm.groq import Groq
10
  from phi.tools.duckduckgo import DuckDuckGo
11
  from phi.tools.yfinance import YFinanceTools
12
  from src.databases.postgres import sqlalchemy_engine
13
  from src.tools.crypto_swap_toolkit import CryptoSwapTools
14
+ from src.tools.crypto_bridge_toolkit import CrossChainSwapTools
15
  from src.tools.crypto_data_toolkit import CryptoDataTools
16
  from src.tools.crypto_evm_wallet_toolkit import CryptoEVMWalletTools
17
  from src.tools.user_profile_toolkit import UserProfileToolkit
 
87
  async def start():
88
  is_dev_mode = True if os.getenv("DEV_MODE") else False
89
 
 
 
90
  # Initialize the assistant
91
  cxbt_assistant = Assistant(
92
  description=description,
93
  instruction=instruction,
94
+ # llm=OpenAIChat(model="gpt-4o"),
95
+ llm=Groq(model="llama3-70b-8192"),
96
  tools=[
97
  UserProfileToolkit(),
98
  DuckDuckGo(),
99
  CryptoDataTools(),
100
  CryptoSwapTools(),
101
+ CrossChainSwapTools(),
102
  CryptoEVMWalletTools(),
103
  YFinanceTools(stock_price=True)
104
  ],
src/libs/rpc_client.py CHANGED
@@ -27,11 +27,15 @@ async def rpc_call(
27
  httpx.RequestError: If an error occurs while making the RPC call.
28
 
29
  """
30
- headers ={
31
  'Content-Type': 'application/json',
32
- "authorization": f'Basic {os.getenv('CHATXBT_RPC_SERVER_BASIC_AUTH_CREDENTIALS')}'
33
  }
34
 
 
 
 
 
 
35
  payload = {
36
  'method': method_name,
37
  'params': params,
@@ -41,7 +45,7 @@ async def rpc_call(
41
 
42
  try:
43
  async with httpx.AsyncClient() as client:
44
- response = await client.post(url, json=payload, headers=headers)
45
  response.raise_for_status()
46
  return response.json()
47
  except httpx.RequestError as e:
 
27
  httpx.RequestError: If an error occurs while making the RPC call.
28
 
29
  """
30
+ headers = {
31
  'Content-Type': 'application/json',
 
32
  }
33
 
34
+ auth = httpx.BasicAuth(
35
+ username=os.getenv('CHATXBT_RPC_SERVER_BASIC_AUTH_USERNAME'),
36
+ password=os.getenv('CHATXBT_RPC_SERVER_BASIC_AUTH_PASSWORD')
37
+ )
38
+
39
  payload = {
40
  'method': method_name,
41
  'params': params,
 
45
 
46
  try:
47
  async with httpx.AsyncClient() as client:
48
+ response = await client.post(url, json=payload, headers=headers, auth=auth)
49
  response.raise_for_status()
50
  return response.json()
51
  except httpx.RequestError as e:
src/tools/crypto_bridge_toolkit.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TypedDict, Any
2
+ import asyncio
3
+ from phi.tools import Toolkit
4
+ from phi.utils.log import logger
5
+ from src.libs.rpc_client import rpc_call
6
+
7
+ # Define TypedDict for wallet
8
+ class WalletParams(TypedDict):
9
+ userEmail: str
10
+ chain: str
11
+ testnet: bool
12
+ gasless: bool
13
+
14
+ # Define TypedDict for tokenIn and tokenOut (since they have identical structures)
15
+ class TokenParams(TypedDict):
16
+ chainId: int
17
+ address: str
18
+ decimals: int
19
+ symbol: str
20
+ name: str
21
+
22
+ class CrossChainSwapTools(Toolkit):
23
+ def __init__(self):
24
+ super().__init__(name="cross_chain_swap_tools")
25
+
26
+ # Registering methods to make them accessible via the toolkit
27
+ self.register(self.cross_chain_swap)
28
+ self.register(self.get_bridge_chains)
29
+ self.register(self.get_cross_chain_swap_token_list)
30
+
31
+ def cross_chain_swap(
32
+ self,
33
+ chainId: int,
34
+ amountLD: str,
35
+ recipient: str,
36
+ wallet: Any,
37
+ tokenIn: Any,
38
+ tokenOut: Any
39
+ ) -> str:
40
+ """
41
+ Performs a cross-chain token swap.
42
+
43
+ Parameters:
44
+ - chainId (int): The ID of the chain.
45
+ - amountLD (str): The amount to be swapped, in lowest denomination (e.g., wei for ETH).
46
+ - recipient (str): The recipient address.
47
+ - wallet (WalletParams): The wallet parameters.
48
+ - tokenIn (TokenParams): The input token parameters.
49
+ - tokenOut (TokenParams): The output token parameters.
50
+
51
+ Returns:
52
+ - str: A string representation of the response from the RPC call.
53
+
54
+ Raises:
55
+ None
56
+
57
+ Note:
58
+ This method uses `asyncio.run()` to run the asynchronous RPC call.
59
+ """
60
+ logger.info(f"Performing cross-chain swap on chain {chainId} for recipient {recipient}")
61
+
62
+ params = {
63
+ 'chainId': chainId,
64
+ 'amountLD': amountLD,
65
+ 'recipient': recipient,
66
+ 'wallet': wallet,
67
+ 'tokenIn': tokenIn,
68
+ 'tokenOut': tokenOut,
69
+ }
70
+ response = asyncio.run(rpc_call(method_name="crossChainSwap", params=params))
71
+ return self._format_response(response)
72
+
73
+ def get_bridge_chains(self, chainId: int) -> str:
74
+ """
75
+ Fetches the list of supported bridge chains.
76
+
77
+ Parameters:
78
+ - chainId (int): The ID of the chain. Supported values:
79
+ - 1: Ethereum
80
+ - 10: Optimism
81
+ - 56: BNB Chain
82
+ - 100: Gnosis Chain
83
+ - 137: Polygon
84
+ - 324: zkSync Era
85
+ - 1088: Metis
86
+ - 8453: Base
87
+ - 42161: Arbitrum
88
+ - 43114: Avalanche
89
+ """
90
+ logger.info(f"Fetching supported bridge chains for chain ID {chainId}")
91
+
92
+ params = {'chainId': chainId}
93
+ response = asyncio.run(rpc_call(method_name="getBridgeChains", params=params))
94
+ return self._format_response(response)
95
+
96
+ def get_cross_chain_swap_token_list(self, chainId: int) -> str:
97
+ """
98
+ Fetches the list of supported cross-chain swap tokens on the specified chainId.
99
+
100
+ Parameters:
101
+ - chainId (int): The ID of the chain.
102
+
103
+ Returns:
104
+ - str: A string representation of the response from the RPC call containing the list of cryptocurrency tokens,
105
+ each with details about their source token and the corresponding destination
106
+ tokens on different blockchain networks.
107
+
108
+ Raises:
109
+ None
110
+
111
+ Note:
112
+ This method uses `asyncio.run()` to run the asynchronous RPC call.
113
+ present data in proper nice readable format
114
+ """
115
+ logger.info(f"Fetching cross-chain swap token list for chain ID {chainId}")
116
+
117
+ params = {'chainId': chainId}
118
+ response = asyncio.run(rpc_call(method_name="getCrossChainSwapTokenList", params=params))
119
+ return self._format_response(response)
120
+
121
+ def _format_response(self, response: dict) -> str:
122
+ """
123
+ Formats the response from the RPC call into a readable string.
124
+
125
+ Parameters:
126
+ - response (dict): The response from the RPC call.
127
+
128
+ Returns:
129
+ - str: A formatted string representation of the response.
130
+ """
131
+ if 'error' in response:
132
+ logger.error(f"RPC call failed with error: {response['error']}")
133
+ return f"Error: {response['error']}"
134
+ return f"Response: {response}"
135
+
136
+ # Example usage:
137
+ # toolkit = CrossChainSwapTools()
138
+ # print(toolkit.get_bridge_chains(1))
139
+ # print(toolkit.get_cross_chain_swap_token_list(1))
140
+ # swap_params = {
141
+ # "chainId": 1,
142
+ # "amountLD": "100",
143
+ # "recipient": "0xRecipientAddress",
144
+ # "wallet": {
145
+ # "userEmail": "user@example.com",
146
+ # "chain": "ethereum",
147
+ # "testnet": True,
148
+ # "gasless": True
149
+ # },
150
+ # "tokenIn": {
151
+ # "chainId": 1,
152
+ # "address": "0xInputTokenAddress",
153
+ # "decimals": 18,
154
+ # "symbol": "TOKEN_IN",
155
+ # "name": "Input Token",
156
+ # "logoUri": "https://example.com/logo_in.png"
157
+ # },
158
+ # "tokenOut": {
159
+ # "chainId": 137,
160
+ # "address": "0xOutputTokenAddress",
161
+ # "decimals": 18,
162
+ # "symbol": "TOKEN_OUT",
163
+ # "name": "Output Token",
164
+ # "logoUri": "https://example.com/logo_out.png"
165
+ # }
166
+ # }
167
+ # print(toolkit.cross_chain_swap(**swap_params))
src/tools/crypto_swap_toolkit.py CHANGED
@@ -115,42 +115,19 @@ class CryptoSwapTools(Toolkit):
115
  Example:
116
  >>> execute_swap('DAI', 'ETH', '1000000000000000000', '0xYourEthereumAddress')
117
  """
118
- # Get the swap quote
119
- quote = json.loads(self.get_swap_quote(buy_token, sell_token, sell_amount))
120
- logger.info(f"Swap quote: {quote}")
121
-
122
- if 'error' in quote:
123
- # return {"error": "Failed to get swap quote"}
124
- return f"Error: Failed to get swap quote"
125
-
126
- # Approve the token if needed (skip if selling ETH)
127
- if sell_token != 'ETH':
128
- approval_receipt = self.token_approval_helper.approve_token(sell_token, quote['allowanceTarget'],
129
- sell_amount, eth_address)
130
- logger.info(f"Approval receipt: {approval_receipt}")
131
-
132
- if 'status' not in approval_receipt or approval_receipt['status'] != 1:
133
- # return {"error": "Token approval failed"}
134
- return f"Error: Token approval failed"
135
-
136
- # Execute the swap
137
  try:
138
- swap_tx = {
139
- 'from': eth_address,
140
- 'to': quote['to'],
141
- 'data': quote['data'],
142
- 'value': int(quote['value']),
143
- 'gas': 200000,
144
- 'gasPrice': self.web3.to_wei('20', 'gwei'),
145
- 'nonce': self.web3.eth.get_transaction_count(eth_address)
146
  }
147
- signed_swap_tx = self.web3.eth.account.signTransaction(swap_tx, private_key=get_private_key())
148
- tx_hash = self.web3.eth.send_raw_transaction(signed_swap_tx.rawTransaction)
149
- receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)
150
- logger.info(f"Swap transaction receipt: {receipt}")
151
- # return receipt
152
- return f"{(receipt)}"
153
- except Exception as e:
154
- logger.warning(f"Failed to execute swap: {e}")
155
  # return {"error": str(e)}
156
  return f"Error: {e}"
 
115
  Example:
116
  >>> execute_swap('DAI', 'ETH', '1000000000000000000', '0xYourEthereumAddress')
117
  """
118
+ logger.info(f"wap token: buying {buy_token} with {sell_token} amount {sell_amount}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  try:
120
+ params = {
121
+ 'buyToken': buy_token,
122
+ 'sellToken': sell_token,
123
+ 'sellAmount': sell_amount,
124
+ 'wallet': {
125
+
126
+ }
 
127
  }
128
+ response = asyncio.run(rpc_call(method_name="swapTokens", params=params))
129
+ return f"{response}"
130
+ except requests.exceptions.RequestException as e:
131
+ logger.warning(f"Failed to swap tokens: {e}")
 
 
 
 
132
  # return {"error": str(e)}
133
  return f"Error: {e}"
src/tools/user_profile_toolkit.py CHANGED
@@ -16,6 +16,7 @@ class UserProfileToolkit(Toolkit):
16
  self.register(self.update_user_name)
17
  self.register(self.update_user_email)
18
  self.register(self.update_user_picture)
 
19
 
20
  @cl.on_chat_start
21
  def get_user_info(self, info_type: str = "name") -> str:
 
16
  self.register(self.update_user_name)
17
  self.register(self.update_user_email)
18
  self.register(self.update_user_picture)
19
+ self.allUserInfo = self.get_all_user_info()
20
 
21
  @cl.on_chat_start
22
  def get_user_info(self, info_type: str = "name") -> str: