From 61b02b19afb474a59ff5fff304d3ab046447bf7c Mon Sep 17 00:00:00 2001 From: marcomariscal <42938673+marcomariscal@users.noreply.github.com> Date: Thu, 14 Sep 2023 08:32:47 -0700 Subject: [PATCH 1/6] integration: hop protocol (#252) * feat: hop protocol * fix: formatting * fix: try again * feat: update widget index num --- chat/display_widgets.py | 3 +++ knowledge_base/widgets.yaml | 23 +++++++++++++++++++++++ utils/constants.py | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 06fa7dd..977ceae 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -171,6 +171,9 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'yield-protocol-borrow-close': items = params.split(",") lines.append(f"yield protocol borrow close action: {items[0]}") + elif command == 'hop-protocol-bridge': + items = params.split(",") + lines.append(f"hop protocol bridge action for amount: {items[0]}, token symbol: {items[1]}, from chain: {items[2]}, to chain: {items[3]}.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 5bf628f..53b2773 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -698,3 +698,26 @@ - borrowToken type: object return_value_description: "" +- _name_: display_hop_protocol_bridge + description: use the hop protocol to bridge/deposit tokens from a supported chain to a different supported chain + parameters: + properties: + amount: + description: token amount + type: string + tokenSymbol: + description: token symbol + type: string + fromChain: + description: chain to bridge from + type: string + toChain: + description: chain to bridge to + type: string + required: + - amount + - tokenSymbol + - fromChain + - toChain + type: object + return_value_description: "" \ No newline at end of file diff --git a/utils/constants.py b/utils/constants.py index cd74a88..6156677 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -53,7 +53,7 @@ WIDGET_INFO_TOKEN_LIMIT = 4000 # Widget Index -WIDGET_INDEX_NAME = "WidgetV20" +WIDGET_INDEX_NAME = "WidgetV21" def get_widget_index_name(): if env.is_local(): From e16cc4ffc26dff60ff7e80c2bf12690b2669d12e Mon Sep 17 00:00:00 2001 From: marcomariscal <42938673+marcomariscal@users.noreply.github.com> Date: Thu, 14 Sep 2023 08:48:22 -0700 Subject: [PATCH 2/6] integration: tx replay (#249) --- chat/display_widgets.py | 3 +++ knowledge_base/widgets.yaml | 11 +++++++++++ utils/constants.py | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 977ceae..67fdc5f 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -174,6 +174,9 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == 'hop-protocol-bridge': items = params.split(",") lines.append(f"hop protocol bridge action for amount: {items[0]}, token symbol: {items[1]}, from chain: {items[2]}, to chain: {items[3]}.") + elif command == 'tx-replay': + items = params.split(",") + lines.append(f"replay transaction with tx hash: {items[0]}") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 53b2773..afeb6c9 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -720,4 +720,15 @@ - fromChain - toChain type: object + return_value_description: "" +- _name_: display_tx_replay + description: replay a transaction with the same parameters + parameters: + properties: + txHash: + description: the transaction with tx hash to replay + type: string + required: + - txHash + type: object return_value_description: "" \ No newline at end of file diff --git a/utils/constants.py b/utils/constants.py index 6156677..20bfb48 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -53,7 +53,7 @@ WIDGET_INFO_TOKEN_LIMIT = 4000 # Widget Index -WIDGET_INDEX_NAME = "WidgetV21" +WIDGET_INDEX_NAME = "WidgetV22" def get_widget_index_name(): if env.is_local(): From 5181d491d927c0b361140f72abf888302b571a29 Mon Sep 17 00:00:00 2001 From: Bruce Donovan Date: Mon, 18 Sep 2023 09:23:41 +0100 Subject: [PATCH 3/6] Use center V2 for assets (#243) * use center V2 for assets * task: update GH workflow file (#244) * task: change GH workflow trigger for widget index (#246) * fix: GH workflow for updating widget index (#247) * new preview image + nft images * use center V2 for assets * new preview image + nft images * render images from center * chcekuser * imageUrl add --------- Co-authored-by: Sagar Shah --- integrations/__init__.py | 0 integrations/center.py | 54 ++++++++++++++++++++++++++++------------ main.py | 7 +++++- 3 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 integrations/__init__.py diff --git a/integrations/__init__.py b/integrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/integrations/center.py b/integrations/center.py index 027a7be..a5e9e2e 100644 --- a/integrations/center.py +++ b/integrations/center.py @@ -7,8 +7,11 @@ import requests import web3 +from fastapi.responses import Response + import env import utils +import auth from utils import ETH_MAINNET_CHAIN_ID, nft, FetchError, ExecError import utils.timing as timing @@ -26,8 +29,7 @@ #"polygon-mainnet", ] -API_URL = "https://api.center.dev/v1" -API_V2_URL = "https://api.center.dev/v2" +API_URL = "https://api.center.dev" MAX_RESULTS = 12 PAGE_LIMIT = 12 @@ -126,6 +128,7 @@ class NFTAsset(ContainerMixin): preview_image_url: str description: str = '' price: Optional[str] = None + attributes: Optional[ List ] = None def container_name(self) -> str: return 'display-nft-asset-container' @@ -214,7 +217,7 @@ def fetch_nft_search(search_str: str) -> Generator[Union[NFTCollection, NFTAsset )) count = 0 for network in NETWORKS: - url = f"{API_V2_URL}/{network}/search?{q}" + url = f"{API_URL}/v2/{network}/search?{q}" timing.log('search_begin') response = requests.get(url, headers=HEADERS) try: @@ -295,7 +298,7 @@ def fetch_nft_search_collection_by_trait(network: str, address: str, trait_name: limit=limit, offset=offset, )) - url = f"{API_URL}/{network}/{address}/assets/searchByTraits?{q}" + url = f"{API_URL}/v1/{network}/{address}/assets/searchByTraits?{q}" response = requests.post(url, headers=headers, json=payload) try: response.raise_for_status() @@ -337,7 +340,7 @@ def fetch_nft_search_collection_by_trait(network: str, address: str, trait_name: def fetch_nft_collection(network: str, address: str) -> NFTCollection: - url = f"{API_V2_URL}/{network}/{address}/nft/metadata" + url = f"{API_URL}/v2/{network}/{address}/nft/metadata" response = requests.get(url, headers=HEADERS) response.raise_for_status() obj = response.json() @@ -345,7 +348,7 @@ def fetch_nft_collection(network: str, address: str) -> NFTCollection: if num_assets == 0: # seems to return 0 incorrectly # use the asset endpoint with dummy token token_id = 1 - url = f"{API_V2_URL}/{network}/{address}/nft/{token_id}/metadata" + url = f"{API_URL}/v2/{network}/{address}/nft/{token_id}/metadata" response = requests.get(url, headers=HEADERS) if response.status_code == 200: token_obj = response.json() @@ -375,7 +378,7 @@ def fetch_nft_collection_assets(network: str, address: str) -> NFTCollectionAsse {"Address": address, "TokenID": token_id} for token_id in token_ids[offset: offset + limit] ]} - url = f"{API_URL}/{network}/assets" + url = f"{API_URL}/v1/{network}/assets" response = requests.post(url, headers=HEADERS, json=payload) try: response.raise_for_status() @@ -400,6 +403,7 @@ def fetch_nft_collection_assets(network: str, address: str) -> NFTCollectionAsse name=item['name'], preview_image_url=item['mediumPreviewImageUrl'], price=price, + ) if not _is_valid_asset(asset): continue @@ -434,7 +438,7 @@ def fetch_nft_collection_assets_for_sale(network: str, address: str) -> Generato {"Address": address, "TokenID": token_id} for token_id in token_ids[offset: offset + limit] ]} - url = f"{API_URL}/{network}/assets" + url = f"{API_URL}/v1/{network}/assets" response = requests.post(url, headers=HEADERS, json=payload) try: response.raise_for_status() @@ -482,7 +486,7 @@ def fetch_nft_collection_traits(network: str, address: str) -> NFTCollectionTrai limit=limit, offset=offset, )) - url = f"{API_URL}/{network}/{address}/traits?{q}" + url = f"{API_URL}/v1/{network}/{address}/traits?{q}" response = requests.get(url, headers=HEADERS) try: response.raise_for_status() @@ -525,7 +529,7 @@ def fetch_nft_collection_trait_values(network: str, address: str, trait: str) -> limit=limit, offset=offset, )) - url = f"{API_URL}/{network}/{address}/traits/{trait}?{q}" + url = f"{API_URL}/v1/{network}/{address}/traits/{trait}?{q}" response = requests.get(url, headers=HEADERS) try: response.raise_for_status() @@ -557,7 +561,7 @@ def fetch_nft_collection_trait_values(network: str, address: str, trait: str) -> def fetch_nft_asset(network: str, address: str, token_id: str) -> NFTAsset: - url = f"{API_URL}/{network}/{address}/{token_id}" + url = f"{API_URL}/v2/{network}/{address}/nft/{token_id}/metadata" response = requests.get(url, headers=HEADERS) response.raise_for_status() obj = response.json() @@ -565,16 +569,18 @@ def fetch_nft_asset(network: str, address: str, token_id: str) -> NFTAsset: network=network, address=address, token_id=token_id, - collection_name=obj['collectionName'], - name=obj['name'], - preview_image_url=obj['mediumPreviewImageUrl'], + collection_name=obj['collection']['name'], + name=obj['metadata']['name'], + preview_image_url=f"{API_URL}{obj['media']['small']}", + # image_url=f"{API_URL}{obj['media']['medium']}", description=obj['metadata']['description'], + attributes=obj['metadata']['attributes'], ) def fetch_nft_asset_traits(network: str, address: str, token_id: str) -> NFTAssetTraits: price = _fetch_nft_asset_price_str(network, address, token_id) - url = f"{API_URL}/{network}/{address}/{token_id}" + url = f"{API_URL}/v1/{network}/{address}/{token_id}" response = requests.get(url, headers=HEADERS) response.raise_for_status() obj = response.json() @@ -622,7 +628,7 @@ def fetch_nfts_owned_by_address_or_domain(network: str, address_or_domain: str) limit=limit, offset=offset, )) - url = f"{API_V2_URL}/{normalized_network}/{address_or_domain}/nfts-owned?{q}" + url = f"{API_URL}/v2/{normalized_network}/{address_or_domain}/nfts-owned?{q}" try: response = requests.get(url, headers=HEADERS) @@ -691,3 +697,19 @@ def _fetch_nft_asset_price_str(network: str, address: str, token_id: str) -> Opt else: price = None return price + +@auth.authenticate_user_id() +def fetch_center_image(response, network: str, address: str, token_id: str, size: str, user_id:str=None): + + url = f"{API_URL}/v2/{network}/{address}/nft/{token_id}/render/{size}" + resp = requests.get(url, headers=HEADERS) + + # Check if user is authenticated + if not user_id: return None + + # return response.content + if resp.status_code != 200: + # Handle error appropriately, return a message, or another status code + return Response(content="Could not retrieve image", status_code=resp.status_code) + + return Response(content=resp.content) \ No newline at end of file diff --git a/main.py b/main.py index 16a4eb9..faeb9ee 100644 --- a/main.py +++ b/main.py @@ -3,9 +3,10 @@ from dataclasses import dataclass from typing import Any, Dict, Optional, Set -from fastapi import FastAPI, Request, Response, Body, WebSocket, WebSocketDisconnect +from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.sessions import SessionMiddleware +from fastapi.responses import StreamingResponse import server import chat @@ -15,6 +16,7 @@ from app import chat as app_chat from app import share as app_share +from integrations import center app = FastAPI() @@ -80,6 +82,9 @@ class ClientState: async def api_nonce(request: Request): return auth.api_nonce(request) +@app.get("/center_image/{network}/{address}/{token_id}/{size}") +async def fetch_nft_image(request: Request, network: str, address: str, token_id: str, size: str): + return center.fetch_center_image(request, network, address, token_id, size) @app.post("/login") async def api_login(request: Request, data: auth.AcceptJSON): From 7ee1d254d7c01aad3992615447643768b91cc670 Mon Sep 17 00:00:00 2001 From: Kah Keng Tay Date: Mon, 18 Sep 2023 08:27:31 -0700 Subject: [PATCH 4/6] server: be more relaxed for db lookups --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index e977cbe..950917b 100644 --- a/server.py +++ b/server.py @@ -49,7 +49,7 @@ def _register_system(system_config_id, system_config_json): def _get_default_system_config_id(): # we query the db but return the identifier to store, so that we can still # reference the id even after the session has been closed. - default_system_config = SystemConfig.query.filter_by(json=config.default_config).one_or_none() + default_system_config = SystemConfig.query.filter_by(json=config.default_config).first() if not default_system_config: default_system_config = SystemConfig(json=config.default_config) db_session.add(default_system_config) From 2c34b57fee0d7fb9279b467d0dd0ce78c9b08731 Mon Sep 17 00:00:00 2001 From: Prafful <46891804+iamsahu@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:50:39 +0200 Subject: [PATCH 5/6] Updated Lido integration (#159) * Update widgets.yaml * feat: reth * tested * Update README.md * Update display_widgets.py * update for reth * Update constants.py * chore: update widget idx * fix: use items --------- Co-authored-by: marcomariscal --- README.md | 1 + chat/display_widgets.py | 14 ++++++++++- knowledge_base/widgets.yaml | 46 ++++++++++++++++++++++++++++++++++++- utils/constants.py | 2 +- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f41a81..a4f7c80 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ The usage guide for the Cacti chatbot is available [here](./usage_guide.md) ## Steps to add new widget command - Update `widgets.yaml` with the widget command details + - If no value has to be returned please specify an empty string '' for `return_value_description` - Increment the numeric version in `WIDGET_INDEX_NAME` constant in `utils/constants.py` - For local env, the widget index name would use your OS login name to create an isolated index. For dev/prod, the widget index would be the numeric version mentioned above. (more info in `scripts/check_update_widget_index.py`) - Run this Python command to update your widget index with the new widget `python -m scripts.check_update_widget_index` diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 67fdc5f..9236f0d 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -170,13 +170,25 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: lines.append(f"yield protocol borrow action for borrow token: {items[0]}, borrow amount: {items[1]}, collateral token: {items[2]}, collateral amount: {items[3]}.") elif command == 'yield-protocol-borrow-close': items = params.split(",") - lines.append(f"yield protocol borrow close action: {items[0]}") + lines.append(f"yield protocol borrow close action: {items[0]}") elif command == 'hop-protocol-bridge': items = params.split(",") lines.append(f"hop protocol bridge action for amount: {items[0]}, token symbol: {items[1]}, from chain: {items[2]}, to chain: {items[3]}.") elif command == 'tx-replay': items = params.split(",") lines.append(f"replay transaction with tx hash: {items[0]}") + elif command == "deposit-eth-lido": + items = params.split(",") + lines.append(f"deposit eth to lido action for amount: {items[0]}.") + elif command == "withdraw-eth-lido": + items = params.split(",") + lines.append(f"withdraw eth from lido action for amount: {items[0]}.") + elif command == "deposit-eth-reth": + items = params.split(",") + lines.append(f"deposit eth to reth action for amount: {items[0]}.") + elif command == "withdraw-eth-reth": + items = params.split(",") + lines.append(f"withdraw eth from reth action for amount: {items[0]}.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index afeb6c9..099f4ba 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -731,4 +731,48 @@ required: - txHash type: object - return_value_description: "" \ No newline at end of file + return_value_description: "" +- _name_: display_deposit_eth_lido + description: This widget is only used when the user wants to deposit/stake ETH into the Lido protocol to get steth + parameters: + properties: + amount: + description: Amount of ETH to deposit/stake into Lido (and obtain stETH) + type: string + required: + - amount + type: object + return_value_description: '' +- _name_: display_withdraw_eth_lido + description: This widget is only used when the user wants to withdraw ETH from Lido (convert stETH to ETH) + parameters: + properties: + amount: + description: Amount of ETH to withdraw from Lido + type: string + required: + - amount + type: object + return_value_description: '' +- _name_: deposit_eth_reth + description: This widget is only used when the user wants to deposit ETH into the rocket pool protocol to get rETH + parameters: + properties: + amount: + description: Amount of ETH to deposit into rocket pool (and obtain rETH) + type: string + required: + - amount + type: object + return_value_description: '' +- _name_: display_withdraw_eth_reth + description: This widget is only used when the user wants to withdraw ETH from rETH (convert rETH to ETH) + parameters: + properties: + amount: + description: Amount of ETH to withdraw from rocket pool + type: string + required: + - amount + type: object + return_value_description: '' \ No newline at end of file diff --git a/utils/constants.py b/utils/constants.py index 20bfb48..b65c534 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -53,7 +53,7 @@ WIDGET_INFO_TOKEN_LIMIT = 4000 # Widget Index -WIDGET_INDEX_NAME = "WidgetV22" +WIDGET_INDEX_NAME = "WidgetV23" def get_widget_index_name(): if env.is_local(): From af0bc08f3dd7cd43d036b05921ec42ec79bf672d Mon Sep 17 00:00:00 2001 From: Prafful <46891804+iamsahu@users.noreply.github.com> Date: Thu, 21 Sep 2023 21:06:21 +0200 Subject: [PATCH 6/6] Generalized 4626 vault integration (#161) * Update widgets.yaml * feat: deposit vault widget * Update widgets.yaml * Update widgets.yaml * Update display_widgets.py * Update display_widgets.py * Update display_widgets.py * fix: move to end * fix: use items * chore: widget index bump --------- Co-authored-by: marcomariscal --- chat/display_widgets.py | 12 ++++++++ knowledge_base/widgets.yaml | 60 +++++++++++++++++++++++++++++++++++++ utils/constants.py | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/chat/display_widgets.py b/chat/display_widgets.py index 9236f0d..2d7fb05 100644 --- a/chat/display_widgets.py +++ b/chat/display_widgets.py @@ -189,6 +189,18 @@ def _widgetize_inner(command: str, params: str, depth: int = 0) -> str: elif command == "withdraw-eth-reth": items = params.split(",") lines.append(f"withdraw eth from reth action for amount: {items[0]}.") + elif command == 'savings-dai-deposit': + items = params.split(",") + lines.append(f"DAI deposit action for amount: {items[0]}.") + elif command == 'savings-dai-withdraw': + items = params.split(",") + lines.append(f"DAI withdraw action for amount: {items[0]}.") + elif command == 'deposit-vault': + items = params.split(",") + lines.append(f"Deposit vault action for token: {items[0]}, amount: {items[1]}.") + elif command == 'withdraw-vault': + items = params.split(",") + lines.append(f"Withdraw vault action for token: {items[0]}, amount: {items[1]}.") else: # assert 0, f'unrecognized command: {command}({params})' lines.append(f"An unrecognized command: {command}({params})") diff --git a/knowledge_base/widgets.yaml b/knowledge_base/widgets.yaml index 099f4ba..b178db4 100644 --- a/knowledge_base/widgets.yaml +++ b/knowledge_base/widgets.yaml @@ -775,4 +775,64 @@ required: - amount type: object + return_value_description: '' +- _name_: display_savings_dai_deposit + description: Used to deposit DAI into the DAI savings account. + parameters: + properties: + amount: + description: Amount of DAI to deposit. + type: string + required: + - amount + type: object + return_value_description: '' +- _name_: display_savings_dai_withdraw + description: Used to withdraw DAI from the DAI savings account. + parameters: + properties: + amount: + description: Amount of DAI to withdraw. + type: string + required: + - amount + type: object + return_value_description: '' +- _name_: display_deposit_vault + description: Used to deposit a token into a vault + parameters: + properties: + depositToken: + description: Token to deposit into the vault. + type: string + amount: + description: Amount of token to deposit. + type: string + vault: + description: Vault to deposit the token in. + type: string + required: + - depositToken + - amount + - vault + type: object + return_value_description: '' +- _name_: display_withdraw_vault + description: Used to withdraw a token from a vault + parameters: + properties: + withdrawToken: + description: Token to withdraw from the vault. + type: string + amount: + description: Amount of token to withdraw. + type: string + vault: + description: Vault to withdraw the token in. + type: string + required: + - withdrawToken + - amount + - vault + type: object return_value_description: '' \ No newline at end of file diff --git a/utils/constants.py b/utils/constants.py index b65c534..d908dee 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -53,7 +53,7 @@ WIDGET_INFO_TOKEN_LIMIT = 4000 # Widget Index -WIDGET_INDEX_NAME = "WidgetV23" +WIDGET_INDEX_NAME = "WidgetV24" def get_widget_index_name(): if env.is_local():