├── .docs ├── 1672973855007.jpeg ├── 1684025179170.jpeg ├── 1685121810573.jpeg ├── 1712878340984.jpeg ├── 5be57833-5a4f-41a1-849c-260b660df496-2.png ├── landing.png └── swqdiagram.png ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SWQuery.code-workspace ├── ai-agent ├── .env.example ├── .gitignore ├── Dockerfile ├── __init__.py ├── configs │ ├── __init__.py │ └── settings.py ├── controllers │ ├── __init__.py │ └── query │ │ ├── __init__.py │ │ └── query_controller.py ├── main.py ├── middlewares │ ├── __init__.py │ └── api_key_middleware.py ├── requirements.txt └── services │ ├── __init__.py │ └── query │ ├── __init__.py │ └── query_service.py ├── credit-sales ├── Cargo.toml └── src │ ├── constants.rs │ ├── errors.rs │ ├── instructions │ ├── buy_credits.rs │ ├── mod.rs │ ├── refund_credits.rs │ └── withdraw_usdc.rs │ ├── lib.rs │ ├── state │ ├── credits_account.rs │ └── mod.rs │ └── tests │ ├── buy_credits_test.rs │ ├── mod.rs │ ├── refund_credits_test.rs │ └── withdraw_usdc_test.rs ├── deployment └── compose.yml ├── frontend ├── .gitignore ├── Dockerfile ├── README.md ├── components.json ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── horizontal-logo.svg │ ├── logo-horizontal.png │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ ├── app │ │ ├── chatbot │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ └── page.tsx │ │ ├── demo │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── hooks │ │ │ └── useTokenAccess.ts │ │ ├── layout.tsx │ │ ├── not-found.tsx │ │ ├── page.tsx │ │ ├── pitchdeck │ │ │ └── page.tsx │ │ ├── program │ │ │ └── page.tsx │ │ ├── real-time-monitor │ │ │ └── page.tsx │ │ └── videodemo │ │ │ └── page.tsx │ ├── assets │ │ └── images │ │ │ ├── logo-complete.png │ │ │ ├── logo-horizontal.png │ │ │ └── new-logo-horizontal.png │ ├── components │ │ ├── Atoms │ │ │ ├── AvaiableSoon │ │ │ │ └── index.tsx │ │ │ ├── Buttons │ │ │ │ └── button.tsx │ │ │ ├── CardComponent │ │ │ │ └── index.tsx │ │ │ ├── ChatModal │ │ │ │ └── index.tsx │ │ │ ├── CodeExample │ │ │ │ └── index.tsx │ │ │ ├── CoreFeaturesSection.tsx │ │ │ ├── FeatureSection.tsx │ │ │ ├── FeatureSection │ │ │ │ └── index.tsx │ │ │ ├── PricingModal │ │ │ │ └── index.tsx │ │ │ ├── SDKArchitectureSection.tsx │ │ │ ├── SectionItem │ │ │ │ └── index.tsx │ │ │ ├── TokenCard │ │ │ │ └── index.tsx │ │ │ ├── TokenDisplay │ │ │ │ └── TokenDisplay.tsx │ │ │ ├── TokenLaunchCard │ │ │ │ └── index.tsx │ │ │ ├── TutorialModal │ │ │ │ └── index.tsx │ │ │ ├── TypeWriterMarkdown │ │ │ │ └── index.tsx │ │ │ ├── WalletCard │ │ │ │ └── index.tsx │ │ │ ├── WebSocketComponent │ │ │ │ └── index.tsx │ │ │ ├── card.tsx │ │ │ ├── input.tsx │ │ │ └── scroll-area.tsx │ │ ├── Molecules │ │ │ ├── CreditSidebar │ │ │ │ └── index.tsx │ │ │ ├── Demo │ │ │ │ └── History │ │ │ │ │ └── index.tsx │ │ │ ├── Landing │ │ │ │ ├── Footer │ │ │ │ │ └── index.tsx │ │ │ │ ├── Intro │ │ │ │ │ ├── Explanation │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── Section │ │ │ │ │ └── index.tsx │ │ │ ├── Navbar │ │ │ │ └── index.tsx │ │ │ ├── Sidebar │ │ │ │ └── index.tsx │ │ │ ├── TokenLaunchTab │ │ │ │ └── index.tsx │ │ │ ├── TokenTab │ │ │ │ └── index.tsx │ │ │ ├── TransactionPrev │ │ │ │ └── TransactionPreview.tsx │ │ │ ├── TrendingTokenPreview │ │ │ │ └── index.tsx │ │ │ └── WalletTab │ │ │ │ └── index.tsx │ │ ├── Organisms │ │ │ ├── Demo │ │ │ │ └── index.tsx │ │ │ ├── Landing │ │ │ │ └── index.tsx │ │ │ ├── PitchDeck │ │ │ │ └── index.tsx │ │ │ └── VideoDemo │ │ │ │ └── index.tsx │ │ └── ui │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ ├── input.tsx │ │ │ └── popover.tsx │ ├── lib │ │ └── utils.ts │ ├── providers │ │ └── SolanaWalletProvider.tsx │ ├── services │ │ ├── agent.ts │ │ ├── config │ │ │ └── api.ts │ │ ├── credits.ts │ │ ├── program.ts │ │ ├── users.ts │ │ └── wallet.ts │ └── utils │ │ └── constants.tsx ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock ├── justfile ├── result.json ├── rustfmt.toml ├── server ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── README.md ├── database │ └── init.sql ├── example_frontend_integration.md ├── src │ ├── db.rs │ ├── main.rs │ ├── middlewares │ │ ├── mod.rs │ │ └── rate_limiter.rs │ ├── models │ │ ├── chat.rs │ │ ├── credits.rs │ │ ├── mod.rs │ │ ├── package.rs │ │ ├── query.rs │ │ └── user.rs │ ├── routes │ │ ├── agent.rs │ │ ├── chatbot.rs │ │ ├── credits.rs │ │ ├── mod.rs │ │ ├── packages.rs │ │ ├── token.rs │ │ └── users.rs │ └── utils │ │ └── mod.rs └── test.sh └── swquery ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── client.rs ├── errors.rs ├── lib.rs ├── llm │ ├── mod.rs │ └── transformer.rs ├── models │ ├── mod.rs │ ├── transactions.rs │ └── trending.rs └── utils.rs └── test.sh /.docs/1672973855007.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/1672973855007.jpeg -------------------------------------------------------------------------------- /.docs/1684025179170.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/1684025179170.jpeg -------------------------------------------------------------------------------- /.docs/1685121810573.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/1685121810573.jpeg -------------------------------------------------------------------------------- /.docs/1712878340984.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/1712878340984.jpeg -------------------------------------------------------------------------------- /.docs/5be57833-5a4f-41a1-849c-260b660df496-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/5be57833-5a4f-41a1-849c-260b660df496-2.png -------------------------------------------------------------------------------- /.docs/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/landing.png -------------------------------------------------------------------------------- /.docs/swqdiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/.docs/swqdiagram.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | # DS_Store 4 | **/**/.DS_Store 5 | .DS_Store 6 | .env -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["credit-sales"] 4 | exclude = ["swquery", "frontend","server"] 5 | 6 | [workspace.package] 7 | edition = "2021" 8 | license = "Apache-2.0" 9 | authors = [ 10 | "Arthur Bretas ", 11 | "Marcelo G Feitoza ", 12 | "Pedro Hagge Baptista ", 13 | "Victor Carvalho ", 14 | ] 15 | repository = "https://github.com/vict0rcarvalh0/swquery" 16 | 17 | [workspace.dependencies] 18 | serde = { version = "1.0", features = ["derive"] } 19 | tokio = { version = "1.0", features = ["full"] } 20 | tracing = "0.1" 21 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 22 | axum = { version = "0.7.9" } 23 | serde_json = "1.0" 24 | sqlx = { version = "0.8.2", features = [ 25 | "runtime-tokio-native-tls", 26 | "postgres", 27 | "chrono", 28 | ] } 29 | dotenvy = "0.15" 30 | chrono = { version = "0.4", features = ["serde"] } 31 | tower = { version = "0.5.1", features = ["util"] } 32 | http-body-util = "0.1.0" 33 | hyper-util = { version = "0.1", features = [ 34 | "client", 35 | "http1", 36 | "client-legacy", 37 | ] } 38 | tower-http = { version = "0.6.1", features = ["trace"] } 39 | solana-sdk = "2.0.8" 40 | thiserror = "1.0" 41 | reqwest = { version = "0.11", features = ["json"] } 42 | -------------------------------------------------------------------------------- /SWQuery.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "swquery" 5 | }, 6 | { 7 | "path": "server" 8 | }, 9 | { 10 | "path": "ai-agent" 11 | }, 12 | { 13 | "path": "deployment" 14 | }, 15 | { 16 | "path": "frontend" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /ai-agent/.env.example: -------------------------------------------------------------------------------- 1 | API_AI_KEY_SECRET=secret_example_key 2 | OPENAI_KEY_SECRET=openai_key_secret_example 3 | HELIUS_KEY_SECRET=helius_key_secret_example -------------------------------------------------------------------------------- /ai-agent/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | 164 | best.pt 165 | .env.production 166 | -------------------------------------------------------------------------------- /ai-agent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | libgl1-mesa-glx \ 5 | libglib2.0-0 \ 6 | git 7 | 8 | WORKDIR /app 9 | 10 | COPY requirements.txt requirements.txt 11 | 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | COPY . . 15 | 16 | EXPOSE 8000 17 | 18 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] -------------------------------------------------------------------------------- /ai-agent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/ai-agent/__init__.py -------------------------------------------------------------------------------- /ai-agent/configs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/ai-agent/configs/__init__.py -------------------------------------------------------------------------------- /ai-agent/configs/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import lru_cache 3 | 4 | from pydantic_settings import BaseSettings, SettingsConfigDict 5 | 6 | 7 | class Settings(BaseSettings): 8 | api_ai_key_secret: str 9 | api_ai_key_name: str 10 | openai_key_secret: str 11 | 12 | model_config = ( 13 | SettingsConfigDict(env_file=".env") if ".env" in os.listdir(".") else None 14 | ) 15 | 16 | 17 | @lru_cache 18 | def get_settings(): 19 | return Settings() 20 | 21 | 22 | settings = get_settings() 23 | -------------------------------------------------------------------------------- /ai-agent/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | from middlewares.api_key_middleware import validate_api_key 3 | 4 | from .query.query_controller import router as QueryRouter 5 | 6 | EndPointsRouter = APIRouter() 7 | 8 | EndPointsRouter.include_router(QueryRouter, dependencies=[ 9 | # Depends(validate_api_key) 10 | ]) 11 | -------------------------------------------------------------------------------- /ai-agent/controllers/query/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/ai-agent/controllers/query/__init__.py -------------------------------------------------------------------------------- /ai-agent/controllers/query/query_controller.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, File, Form, UploadFile, Header, HTTPException 2 | from pydantic import BaseModel, Field 3 | from fastapi.responses import StreamingResponse 4 | import json 5 | from configs.settings import settings 6 | 7 | from services.query.query_service import ( 8 | query_generator_openai, 9 | generate_visualization 10 | ) 11 | 12 | router = APIRouter( 13 | prefix="/query", 14 | tags=["Query"] 15 | ) 16 | 17 | 18 | class QueryBody(BaseModel): 19 | input_user: str | None = Field(None, alias="inputUser") 20 | inputUser: str | None = None 21 | address: str 22 | 23 | def get_input(self) -> str: 24 | return self.input_user or self.inputUser or "" 25 | 26 | model_config = { 27 | "json_schema_extra": { 28 | "examples": [ 29 | { 30 | "inputUser": "What was the trending tokens today?", 31 | "address": "9unenHYtwUowNkWdZmSYTwzGxxdzKVJh7npk6W6uqRF3" 32 | } 33 | ] 34 | } 35 | } 36 | 37 | 38 | class VisualizationBody(BaseModel): 39 | jsonReturned: str 40 | question: str 41 | address: str 42 | 43 | 44 | @router.post("/generate-query") 45 | async def generate_query_route( 46 | body: QueryBody, 47 | x_api_key: str = Header(..., alias="x-api-key") 48 | ): 49 | # Debug logging 50 | print("Received request body:", body.model_dump(exclude_none=True)) 51 | 52 | input_user = body.get_input() 53 | print(f"Processed input: {input_user}") 54 | 55 | try: 56 | result = query_generator_openai( 57 | input_user, 58 | body.address, 59 | ) 60 | # result = { 61 | # "result": { 62 | # "response": "getTrendingTokens", 63 | # "status": "success", 64 | # "params": { 65 | # "address": body.address, 66 | # "filters": [] 67 | # } 68 | # }, 69 | # "tokens": 100 70 | # } 71 | 72 | print(result) 73 | 74 | return result 75 | except Exception as e: 76 | print(f"Error in generate_query_route: {str(e)}") 77 | raise HTTPException(status_code=500, detail=str(e)) 78 | 79 | 80 | @router.post("/generate-visualization") 81 | async def generate_visualization_route( 82 | body: VisualizationBody, 83 | x_api_key: str = Header(..., alias="x-api-key") 84 | ): 85 | # Debug logging 86 | print("Received request body:", body.model_dump(exclude_none=True)) 87 | 88 | result = generate_visualization( 89 | body.jsonReturned, 90 | body.question, 91 | settings.openai_key_secret 92 | ) 93 | # result = { 94 | # "result": "## Trending Tokens Analysis\n- **Token 1**: SWQ (Trading Volume: 1.2M SOL)\n- **Token 2**: HLS (24h Change: +15.2%)", 95 | # "tokens": 100 96 | # } 97 | 98 | return result 99 | -------------------------------------------------------------------------------- /ai-agent/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | from fastapi.middleware.cors import CORSMiddleware 3 | 4 | from controllers import EndPointsRouter 5 | 6 | app = FastAPI() 7 | 8 | app.add_middleware( 9 | CORSMiddleware, 10 | allow_origins=["*"], 11 | allow_methods=["*"], 12 | allow_headers=["*"], 13 | allow_credentials=True, 14 | ) 15 | 16 | # Add logging middleware: Print request headers and body 17 | @app.middleware("http") 18 | async def logging_middleware(request: Request, call_next): 19 | print(f"Request headers: {request.headers}") 20 | print(f"Request body: {await request.body()}") 21 | response = await call_next(request) 22 | return response 23 | 24 | app.include_router(EndPointsRouter) 25 | -------------------------------------------------------------------------------- /ai-agent/middlewares/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/ai-agent/middlewares/__init__.py -------------------------------------------------------------------------------- /ai-agent/middlewares/api_key_middleware.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, HTTPException, Security 2 | from fastapi.security.api_key import APIKeyHeader 3 | 4 | from configs import settings 5 | 6 | settings = settings.get_settings() 7 | 8 | api_key_header = APIKeyHeader(name=settings.api_ai_key_name, auto_error=False) 9 | 10 | async def validate_api_key(api_key: str = Security(api_key_header)): 11 | # if api_key != settings.api_ai_key_secret: 12 | # raise HTTPException(status_code=403, detail="Forbidden") 13 | 14 | return api_key -------------------------------------------------------------------------------- /ai-agent/requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.7.0 3 | certifi==2024.12.14 4 | charset-normalizer==3.4.0 5 | click==8.1.7 6 | distro==1.9.0 7 | fastapi==0.115.6 8 | h11==0.14.0 9 | httpcore==1.0.7 10 | httptools==0.6.4 11 | httpx==0.28.1 12 | idna==3.10 13 | jiter==0.8.2 14 | openai==1.57.4 15 | pydantic==2.10.3 16 | pydantic-settings==2.7.0 17 | pydantic_core==2.27.1 18 | python-dotenv==1.0.1 19 | PyYAML==6.0.2 20 | requests==2.32.3 21 | sniffio==1.3.1 22 | starlette==0.41.3 23 | tqdm==4.67.1 24 | typing_extensions==4.12.2 25 | urllib3==2.2.3 26 | uvicorn==0.34.0 27 | uvloop==0.21.0 28 | watchfiles==1.0.3 29 | websockets==14.1 30 | -------------------------------------------------------------------------------- /ai-agent/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/ai-agent/services/__init__.py -------------------------------------------------------------------------------- /ai-agent/services/query/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/ai-agent/services/query/__init__.py -------------------------------------------------------------------------------- /credit-sales/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "credit-sales" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | 9 | [dependencies] 10 | pinocchio = { git = "https://github.com/febo/pinocchio" } 11 | pinocchio-system = { git = "https://github.com/febo/pinocchio" } 12 | pinocchio-token = { git = "https://github.com/febo/pinocchio" } 13 | five8_const = "0.1.3" 14 | 15 | [dev-dependencies] 16 | mollusk-svm = { git = "https://github.com/deanmlittle/mollusk" } 17 | # mollusk-svm = { path = "../../mollusk/harness" } 18 | mollusk-token = { git = "https://github.com/deanmlittle/mollusk" } 19 | # mollusk-token = { path = "../../mollusk/programs/token" } 20 | spl-token = "6.0.0" 21 | solana-sdk = { workspace = true } 22 | 23 | [features] 24 | default = [] 25 | -------------------------------------------------------------------------------- /credit-sales/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const ADMIN: [u8; 32] = 2 | five8_const::decode_32_const("3n5KbkZv1Zyu661dTzPNCqKzLyeYu9uuaqLExpLnz3w4"); 3 | // pub const USDC_MINT: [u8; 32] = 4 | // five8_const::decode_32_const("111111111111111111111111111111111111111111"); 5 | pub const TREASURY: [u8; 32] = 6 | five8_const::decode_32_const("CyKcXpxkyai33oMXq3GBXHrkACP1QPjALcg3i3xzEQCZ"); 7 | 8 | // pub const TIME_TO_REFUND: i64 = 60 * 60 * 24 * 7; // 1 week 9 | 10 | pub const USDC_TO_CREDIT: u64 = 100_000; // 1 USD -> 100.000 credits 11 | -------------------------------------------------------------------------------- /credit-sales/src/errors.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::program_error::ProgramError; 2 | 3 | pub enum CreditSalesError { 4 | InvalidInstructionData, 5 | NotEnoughAccountKeys, 6 | UninitializedAccount, 7 | InvalidAccountData, 8 | Unauthorized, 9 | } 10 | 11 | impl From for ProgramError { 12 | fn from(e: CreditSalesError) -> Self { 13 | ProgramError::Custom(e as u32) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /credit-sales/src/instructions/buy_credits.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | constants::{TREASURY, USDC_TO_CREDIT}, 4 | errors::CreditSalesError, 5 | }, 6 | pinocchio::{ 7 | account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, 8 | }, 9 | pinocchio_token::instructions::Transfer, 10 | }; 11 | 12 | pub fn process_buy_credits_instruction(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { 13 | if data.len() != 9 { 14 | return Err(ProgramError::InvalidInstructionData); 15 | } 16 | 17 | let [buyer, _buyer_ta, treasury, credits_account, _clock_sysvar, _token_program] = accounts 18 | else { 19 | return Err(ProgramError::NotEnoughAccountKeys); 20 | }; 21 | assert!(buyer.is_signer()); 22 | 23 | if treasury.key() != &TREASURY { 24 | return Err(CreditSalesError::InvalidAccountData.into()); 25 | } 26 | 27 | let amount_usdc = unsafe { *(data.as_ptr().add(0) as *const u64) }; 28 | let bump = unsafe { *(data.as_ptr().add(8) as *const [u8; 1]) }; 29 | 30 | Transfer { 31 | from: _buyer_ta, 32 | to: treasury, 33 | authority: buyer, 34 | amount: amount_usdc, 35 | } 36 | .invoke()?; // 5,949 CUs - Can happen off of the instruction??? 37 | 38 | let mut credits_account_data = credits_account.try_borrow_mut_data()?; 39 | let credits_account_ptr = credits_account_data.as_mut_ptr(); 40 | unsafe { 41 | *(credits_account_ptr.add(0) as *mut i64) = 0; // timestamp 42 | *(credits_account_ptr.add(8) as *mut u64) = amount_usdc * USDC_TO_CREDIT; // credits_amount 43 | *(credits_account_ptr.add(16) as *mut u64) = 0; // credits_amount_refunded 44 | *(credits_account_ptr.add(24) as *mut [u8; 1]) = bump; // bump 45 | *(credits_account_ptr.add(25) as *mut Pubkey) = *buyer.key(); // owner 46 | } 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /credit-sales/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buy_credits; 2 | pub use buy_credits::process_buy_credits_instruction; 3 | pub mod refund_credits; 4 | pub use refund_credits::process_refund_credits_instruction; 5 | pub mod withdraw_usdc; 6 | use pinocchio::program_error::ProgramError; 7 | pub use withdraw_usdc::process_withdraw_usdc_instruction; 8 | 9 | #[derive(Clone, Copy, Debug)] 10 | pub enum CreditSalesInstruction { 11 | BuyCredits = 0, 12 | RefundCredits = 1, 13 | WithdrawUSDC = 2, 14 | } 15 | 16 | impl TryFrom<&u8> for CreditSalesInstruction { 17 | type Error = ProgramError; 18 | 19 | fn try_from(value: &u8) -> Result { 20 | match value { 21 | 0 => Ok(Self::BuyCredits), 22 | 1 => Ok(Self::RefundCredits), 23 | 2 => Ok(Self::WithdrawUSDC), 24 | _ => Err(ProgramError::InvalidInstructionData), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /credit-sales/src/instructions/refund_credits.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{constants::USDC_TO_CREDIT, state::CreditsAccount}, 3 | pinocchio::{ 4 | account_info::AccountInfo, 5 | instruction::{Seed, Signer}, 6 | program_error::ProgramError, 7 | ProgramResult, 8 | }, 9 | pinocchio_token::instructions::Transfer, 10 | }; 11 | 12 | pub fn process_refund_credits_instruction(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { 13 | if data.len() != 9 { 14 | return Err(ProgramError::InvalidInstructionData); 15 | } 16 | 17 | let [admin, buyer, buyer_ta, treasury, credits_account, _token_program] = accounts else { 18 | return Err(ProgramError::NotEnoughAccountKeys); 19 | }; 20 | assert!(admin.is_signer()); 21 | 22 | let credits_account_data = CreditsAccount::from_account_info(credits_account)?; 23 | assert_eq!(buyer.key(), &credits_account_data.owner(),); 24 | 25 | let amount_of_credits = unsafe { *(data.as_ptr().add(0) as *const u64) }; // 8 bytes 26 | let bump = unsafe { *(data.as_ptr().add(8)) }; // 1 byte 27 | 28 | let binding = [bump]; 29 | let seeds = [ 30 | Seed::from(b"treasury"), 31 | Seed::from(admin.key()), 32 | Seed::from(&binding[..]), 33 | ]; 34 | let signer = [Signer::from(&seeds)]; 35 | 36 | Transfer { 37 | from: treasury, 38 | to: buyer_ta, 39 | authority: treasury, 40 | amount: amount_of_credits / USDC_TO_CREDIT, 41 | } 42 | .invoke_signed(&signer)?; 43 | 44 | unsafe { 45 | *(credits_account 46 | .borrow_mut_data_unchecked() 47 | .as_mut_ptr() 48 | .add(16) as *mut u64) += amount_of_credits; // credits_amount_refunded 49 | } 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /credit-sales/src/instructions/withdraw_usdc.rs: -------------------------------------------------------------------------------- 1 | use { 2 | pinocchio::{ 3 | account_info::AccountInfo, 4 | instruction::{Seed, Signer}, 5 | program_error::ProgramError, 6 | ProgramResult, 7 | }, 8 | pinocchio_token::instructions::Transfer, 9 | }; 10 | 11 | pub fn process_withdraw_usdc_instruction(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { 12 | if data.len() != 9 { 13 | return Err(ProgramError::InvalidInstructionData); 14 | } 15 | 16 | let amount = unsafe { *(data.as_ptr().add(0) as *const u64) }; 17 | let bump = unsafe { *(data.as_ptr().add(8)) }; 18 | 19 | let [treasury_account, admin, admin_usdc_account, _token_program] = accounts else { 20 | return Err(ProgramError::NotEnoughAccountKeys); 21 | }; 22 | // assert_eq!(admin.key(), &ADMIN); // Necessary? 23 | assert!(admin.is_signer()); // Necessary 24 | 25 | let binding = [bump]; 26 | let seeds = [ 27 | Seed::from(b"treasury"), 28 | Seed::from(admin.key()), 29 | Seed::from(&binding), 30 | ]; 31 | let signer = [Signer::from(&seeds)]; 32 | 33 | let transfer_instruction = Transfer { 34 | from: treasury_account, 35 | to: admin_usdc_account, 36 | authority: treasury_account, 37 | amount, 38 | }; 39 | transfer_instruction.invoke_signed(&signer)?; 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /credit-sales/src/lib.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey, 3 | ProgramResult, 4 | }; 5 | 6 | mod instructions; 7 | use instructions::*; 8 | mod constants; 9 | mod errors; 10 | mod state; 11 | 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | pub const ID: [u8; 32] = 16 | five8_const::decode_32_const("99999999999999999999999999999999999999999999"); 17 | entrypoint!(process_instruction); 18 | 19 | pub fn process_instruction( 20 | program_id: &Pubkey, 21 | accounts: &[AccountInfo], 22 | instruction_data: &[u8], 23 | ) -> ProgramResult { 24 | assert_eq!(program_id, &ID); 25 | 26 | let (instruction_discriminant, instruction_data) = instruction_data 27 | .split_first() 28 | .ok_or(ProgramError::InvalidInstructionData)?; 29 | 30 | let instruction = CreditSalesInstruction::try_from(instruction_discriminant)?; 31 | 32 | match instruction { 33 | instructions::CreditSalesInstruction::BuyCredits => { 34 | process_buy_credits_instruction(accounts, instruction_data) 35 | } 36 | instructions::CreditSalesInstruction::RefundCredits => { 37 | process_refund_credits_instruction(accounts, instruction_data) 38 | } 39 | instructions::CreditSalesInstruction::WithdrawUSDC => { 40 | process_withdraw_usdc_instruction(accounts, instruction_data) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /credit-sales/src/state/credits_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; 2 | 3 | pub struct CreditsAccount(*mut u8); 4 | 5 | impl CreditsAccount { 6 | pub const LEN: usize = 8 // timestamp i64 7 | + 8 // credits_amount u64 8 | + 8 // credits_amount_refunded u64 9 | + 1 // bump [u8; 1] 10 | + 32; // owner [u8; 32] 11 | 12 | #[inline(always)] 13 | pub fn from_account_info_unchecked(account_info: &AccountInfo) -> Self { 14 | unsafe { Self(account_info.borrow_mut_data_unchecked().as_mut_ptr()) } 15 | } 16 | 17 | #[inline(always)] 18 | pub fn from_account_info(account_info: &AccountInfo) -> Result { 19 | assert_eq!(*account_info.owner(), crate::ID); 20 | assert_eq!(account_info.data_len(), Self::LEN); 21 | Ok(Self::from_account_info_unchecked(account_info)) 22 | } 23 | 24 | #[inline(always)] 25 | pub fn owner(&self) -> [u8; 32] { 26 | unsafe { *(self.0.add(25) as *const [u8; 32]) } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /credit-sales/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod credits_account; 2 | pub use credits_account::*; 3 | -------------------------------------------------------------------------------- /credit-sales/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod buy_credits_test; 2 | mod refund_credits_test; 3 | mod withdraw_usdc_test; 4 | 5 | use { 6 | mollusk_svm::Mollusk, 7 | solana_sdk::{ 8 | account::{AccountSharedData, WritableAccount}, 9 | program_option::COption, 10 | program_pack::Pack, 11 | pubkey::Pubkey, 12 | }, 13 | spl_token::state::AccountState, 14 | }; 15 | 16 | fn setup() -> (Pubkey, Mollusk) { 17 | let program_id = Pubkey::new_from_array(five8_const::decode_32_const( 18 | "99999999999999999999999999999999999999999999", 19 | )); 20 | let mut mollusk = Mollusk::new(&program_id, "../target/deploy/credit_sales"); 21 | mollusk_token::token::add_program(&mut mollusk); 22 | 23 | (program_id, mollusk) 24 | } 25 | 26 | pub fn create_account(lamports: u64, data_len: usize, owner: &Pubkey) -> AccountSharedData { 27 | AccountSharedData::new(lamports, data_len, owner) 28 | } 29 | 30 | pub fn pack_mint(mint_authority: &Pubkey, supply: u64) -> AccountSharedData { 31 | let mut account = create_account(0, spl_token::state::Mint::LEN, &spl_token::id()); 32 | spl_token::state::Mint { 33 | mint_authority: COption::Some(*mint_authority), 34 | supply, 35 | decimals: 9, 36 | is_initialized: true, 37 | freeze_authority: COption::None, 38 | } 39 | .pack_into_slice(account.data_as_mut_slice()); 40 | account 41 | } 42 | 43 | pub fn pack_token_account(authority: &Pubkey, mint: &Pubkey, amount: u64) -> AccountSharedData { 44 | let mut account = create_account(0, spl_token::state::Account::LEN, &spl_token::id()); 45 | spl_token::state::Account { 46 | mint: *mint, 47 | owner: *authority, 48 | amount, 49 | delegate: COption::None, 50 | state: AccountState::Initialized, 51 | is_native: COption::None, 52 | delegated_amount: 0, 53 | close_authority: COption::None, 54 | } 55 | .pack_into_slice(account.data_as_mut_slice()); 56 | account 57 | } 58 | -------------------------------------------------------------------------------- /credit-sales/src/tests/refund_credits_test.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::state::CreditsAccount, 4 | mollusk_svm::result::Check, 5 | solana_sdk::{ 6 | account::{AccountSharedData, ReadableAccount}, 7 | instruction::{AccountMeta, Instruction}, 8 | program_pack::Pack, 9 | pubkey::Pubkey, 10 | system_program, 11 | sysvar::SysvarId, 12 | }, 13 | spl_token::state::Account as TokenAccount, 14 | }; 15 | 16 | #[test] 17 | fn test_refund_credits() { 18 | // Setup the initial environment 19 | let (program_id, mollusk) = setup(); 20 | let (token_program, token_program_account) = mollusk_token::token::keyed_account(); 21 | let _clock_sysvar = Pubkey::new_unique(); 22 | let _clock_sysvar_account = create_account( 23 | 0, 24 | std::mem::size_of::(), 25 | &solana_sdk::clock::Clock::id(), 26 | ); 27 | 28 | // Admin and buyer accounts 29 | let admin = Pubkey::new_from_array(five8_const::decode_32_const( 30 | "3n5KbkZv1Zyu661dTzPNCqKzLyeYu9uuaqLExpLnz3w4", 31 | )); 32 | let buyer = Pubkey::new_unique(); 33 | 34 | // Mint authority and USDC mint 35 | let mint_authority = Pubkey::new_unique(); 36 | let usdc_mint = Pubkey::new_from_array(five8_const::decode_32_const( 37 | "11111111111111111111111111111111111111111111", 38 | )); 39 | let _usdc_mint_account = pack_mint(&mint_authority, 1_000_000); 40 | 41 | // Initialize the treasury PDA with initial balance 42 | let (treasury_pda, treasury_bump) = 43 | Pubkey::find_program_address(&[b"treasury", &admin.to_bytes()], &program_id); 44 | let treasury_account = pack_token_account( 45 | &treasury_pda, // Owner is treasury PDA 46 | &usdc_mint, // USDC mint 47 | 1_000_000, // Initial USDC balance in treasury 48 | ); 49 | 50 | // Create buyer's USDC account 51 | let buyer_token_account = Pubkey::new_unique(); 52 | let buyer_token_account_data = pack_token_account( 53 | &buyer, // Owner 54 | &usdc_mint, // USDC mint 55 | 0, // Initial balance (will receive refund) 56 | ); 57 | 58 | // Initialize credits account with some existing credits 59 | let (credits_account, _) = 60 | Pubkey::find_program_address(&[b"credits_account", &buyer.to_bytes()], &program_id); 61 | let mut credits_account_data = AccountSharedData::new( 62 | mollusk.sysvars.rent.minimum_balance(CreditsAccount::LEN), 63 | CreditsAccount::LEN, 64 | &program_id, 65 | ); 66 | 67 | // Set initial credits state 68 | let credits_data = credits_account_data.data_as_mut_slice(); 69 | credits_data[0..8].copy_from_slice(&0i64.to_le_bytes()); // timestamp 70 | credits_data[8..16].copy_from_slice(&1_000u64.to_le_bytes()); // credits_amount 71 | credits_data[16..24].copy_from_slice(&0u64.to_le_bytes()); // credits_amount_refunded 72 | credits_data[24] = treasury_bump; // bump 73 | credits_data[25..57].copy_from_slice(&buyer.to_bytes()); // owner 74 | 75 | // Create refund instruction 76 | let refund_amount = 500_000u64; 77 | let instruction_data = [ 78 | vec![1], 79 | refund_amount.to_le_bytes().to_vec(), 80 | vec![treasury_bump], 81 | ] 82 | .concat(); 83 | 84 | let instruction = Instruction { 85 | program_id, 86 | accounts: vec![ 87 | AccountMeta::new(admin, true), 88 | AccountMeta::new(buyer, false), 89 | AccountMeta::new(buyer_token_account, false), 90 | AccountMeta::new(treasury_pda, false), 91 | AccountMeta::new(credits_account, false), 92 | AccountMeta::new_readonly(token_program, false), 93 | ], 94 | data: instruction_data, 95 | }; 96 | 97 | let accounts = vec![ 98 | ( 99 | admin, 100 | AccountSharedData::new(1_000_000, 0, &system_program::id()), 101 | ), 102 | ( 103 | buyer, 104 | AccountSharedData::new(1_000_000, 0, &system_program::id()), 105 | ), 106 | (buyer_token_account, buyer_token_account_data), 107 | (treasury_pda, treasury_account.clone()), 108 | (credits_account, credits_account_data), 109 | (token_program, token_program_account), 110 | ]; 111 | 112 | // Log initial treasury amount 113 | let initial_treasury_amount = TokenAccount::unpack(treasury_account.data()) 114 | .expect("unpack") 115 | .amount; 116 | 117 | // Process refund instruction 118 | let result = 119 | mollusk.process_and_validate_instruction(&instruction, &accounts, &[Check::success()]); 120 | 121 | // Log final treasury amount 122 | let final_treasury_amount = 123 | TokenAccount::unpack(result.get_account(&treasury_pda).unwrap().data()) 124 | .expect("unpack") 125 | .amount; 126 | 127 | assert!( 128 | final_treasury_amount < initial_treasury_amount, 129 | "Treasury balance should decrease by refund amount divided by USDC_TO_CREDIT" 130 | ); 131 | } 132 | -------------------------------------------------------------------------------- /deployment/compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | swquery-db: 3 | image: postgres:12 4 | environment: 5 | POSTGRES_DB: swquery 6 | POSTGRES_USER: swquery 7 | POSTGRES_PASSWORD: swquery 8 | ports: 9 | - "5432:5432" 10 | volumes: 11 | - "../server/database/init.sql:/docker-entrypoint-initdb.d/init.sql" 12 | healthcheck: 13 | test: [ "CMD-SHELL", "pg_isready -U swquery" ] 14 | interval: 10s 15 | timeout: 5s 16 | retries: 5 17 | networks: 18 | - swquery-network 19 | 20 | server: 21 | build: 22 | context: .. 23 | dockerfile: server/Dockerfile 24 | ports: 25 | - "5500:5500" 26 | depends_on: 27 | swquery-db: 28 | condition: service_healthy 29 | agent: 30 | condition: service_started 31 | env_file: 32 | - ../server/.env 33 | networks: 34 | - swquery-network 35 | 36 | agent: 37 | build: 38 | context: ../ai-agent 39 | dockerfile: Dockerfile 40 | ports: 41 | - "8000:8000" 42 | env_file: 43 | - ../ai-agent/.env 44 | networks: 45 | - swquery-network 46 | 47 | networks: 48 | swquery-network: 49 | driver: bridge 50 | 51 | volumes: 52 | swquery-db-data: 53 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine AS build 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | 14 | RUN npm install -g serve 15 | 16 | EXPOSE 3000 17 | 18 | CMD ["serve", "-s", "build"] 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /frontend/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | domains: [ 6 | "via.placeholder.com", 7 | "assets.coingecko.com", 8 | "cdn.helius-rpc.com", 9 | "ipfs.io", 10 | "coin-images.coingecko.com", 11 | "gateway.pinata.cloud" 12 | ], 13 | }, 14 | }; 15 | 16 | export default nextConfig; -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-sw-query", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@coral-xyz/anchor": "^0.30.1", 13 | "@emotion/react": "^11.14.0", 14 | "@emotion/styled": "^11.14.0", 15 | "@headlessui/react": "^2.2.0", 16 | "@jup-ag/react-hook": "^6.2.0", 17 | "@mui/material": "^6.4.1", 18 | "@project-serum/anchor": "^0.26.0", 19 | "@radix-ui/react-dialog": "^1.1.6", 20 | "@radix-ui/react-popover": "^1.1.6", 21 | "@radix-ui/react-scroll-area": "^1.2.2", 22 | "@radix-ui/react-slot": "^1.1.2", 23 | "@solana/spl-token": "^0.4.12", 24 | "@solana/wallet-adapter-base": "^0.9.23", 25 | "@solana/wallet-adapter-react": "^0.15.35", 26 | "@solana/wallet-adapter-react-ui": "^0.9.35", 27 | "@solana/wallet-adapter-wallets": "^0.19.32", 28 | "@solana/web3.js": "^1.98.0", 29 | "axios": "^1.7.9", 30 | "chart.js": "^4.4.7", 31 | "class-variance-authority": "^0.7.1", 32 | "clsx": "^2.1.1", 33 | "cookies-next": "^5.0.2", 34 | "framer-motion": "^11.14.4", 35 | "lucide-react": "^0.468.0", 36 | "next": "15.1.0", 37 | "react": "^19.0.0", 38 | "react-chartjs-2": "^5.3.0", 39 | "react-code-blocks": "^0.1.6", 40 | "react-dom": "^19.0.0", 41 | "react-hook-form": "^7.54.2", 42 | "react-hot-toast": "^2.5.1", 43 | "react-icons": "^5.4.0", 44 | "react-markdown": "^9.0.1", 45 | "remark-gfm": "^4.0.0", 46 | "solana-bankrun": "^0.4.0", 47 | "solana-react-auth": "^2.0.5", 48 | "sonner": "^2.0.1", 49 | "tailwind-merge": "^2.5.5", 50 | "tailwindcss-animate": "^1.0.7", 51 | "zod": "^3.24.1" 52 | }, 53 | "devDependencies": { 54 | "@eslint/eslintrc": "^3", 55 | "@types/babel__generator": "^7.6.8", 56 | "@types/babel__template": "^7.4.4", 57 | "@types/istanbul-lib-report": "^3.0.3", 58 | "@types/node": "^20", 59 | "@types/prop-types": "^15.7.14", 60 | "@types/react": "^19", 61 | "@types/react-dom": "^19", 62 | "@types/yargs-parser": "^21.0.3", 63 | "eslint": "^9", 64 | "eslint-config-next": "15.1.0", 65 | "postcss": "^8", 66 | "tailwindcss": "^3.4.1", 67 | "typescript": "^5" 68 | }, 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /frontend/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/logo-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/frontend/public/logo-horizontal.png -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/demo/page.tsx: -------------------------------------------------------------------------------- 1 | import { DemoPage } from "@/components/Organisms/Demo"; 2 | 3 | export default function Home() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #0a0a0a; 7 | --foreground: #ededed; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | 23 | @layer base { 24 | :root { 25 | --background: 0 0% 100%; 26 | --foreground: 0 0% 3.9%; 27 | --card: 0 0% 100%; 28 | --card-foreground: 0 0% 3.9%; 29 | --popover: 0 0% 100%; 30 | --popover-foreground: 0 0% 3.9%; 31 | --primary: 0 0% 9%; 32 | --primary-foreground: 0 0% 98%; 33 | --secondary: 0 0% 96.1%; 34 | --secondary-foreground: 0 0% 9%; 35 | --muted: 0 0% 96.1%; 36 | --muted-foreground: 0 0% 45.1%; 37 | --accent: 0 0% 96.1%; 38 | --accent-foreground: 0 0% 9%; 39 | --destructive: 0 84.2% 60.2%; 40 | --destructive-foreground: 0 0% 98%; 41 | --border: 0 0% 89.8%; 42 | --input: 0 0% 89.8%; 43 | --ring: 0 0% 3.9%; 44 | --chart-1: 12 76% 61%; 45 | --chart-2: 173 58% 39%; 46 | --chart-3: 197 37% 24%; 47 | --chart-4: 43 74% 66%; 48 | --chart-5: 27 87% 67%; 49 | --radius: 0.5rem; 50 | } 51 | .dark { 52 | --background: 0 0% 3.9%; 53 | --foreground: 0 0% 98%; 54 | --card: 0 0% 3.9%; 55 | --card-foreground: 0 0% 98%; 56 | --popover: 0 0% 3.9%; 57 | --popover-foreground: 0 0% 98%; 58 | --primary: 0 0% 98%; 59 | --primary-foreground: 0 0% 9%; 60 | --secondary: 0 0% 14.9%; 61 | --secondary-foreground: 0 0% 98%; 62 | --muted: 0 0% 14.9%; 63 | --muted-foreground: 0 0% 63.9%; 64 | --accent: 0 0% 14.9%; 65 | --accent-foreground: 0 0% 98%; 66 | --destructive: 0 62.8% 30.6%; 67 | --destructive-foreground: 0 0% 98%; 68 | --border: 0 0% 14.9%; 69 | --input: 0 0% 14.9%; 70 | --ring: 0 0% 83.1%; 71 | --chart-1: 220 70% 50%; 72 | --chart-2: 160 60% 45%; 73 | --chart-3: 30 80% 55%; 74 | --chart-4: 280 65% 60%; 75 | --chart-5: 340 75% 55%; 76 | } 77 | 78 | * { 79 | @apply border-border; 80 | } 81 | body { 82 | @apply bg-background text-foreground; 83 | } 84 | } 85 | 86 | /* Add these styles to remove wallet icons from the modal */ 87 | .wallet-adapter-modal-list { 88 | list-style: none; 89 | } 90 | 91 | .wallet-adapter-modal-list li button img, 92 | .wallet-adapter-modal-list-more-icon-container { 93 | display: none !important; 94 | } 95 | 96 | /* Adjust the button padding since we're hiding the end icon */ 97 | .wallet-adapter-button { 98 | padding-right: 1rem !important; 99 | } 100 | 101 | ::-webkit-scrollbar { 102 | width: 0; 103 | height: 0; 104 | background: transparent; 105 | } 106 | 107 | * { 108 | scrollbar-width: none; 109 | -ms-overflow-style: none; 110 | } 111 | 112 | 113 | 114 | @layer base { 115 | * { 116 | @apply border-border outline-ring/50; 117 | } 118 | body { 119 | @apply bg-background text-foreground; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /frontend/src/app/hooks/useTokenAccess.ts: -------------------------------------------------------------------------------- 1 | import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token"; 2 | import { useWallet } from "@solana/wallet-adapter-react"; 3 | import { Connection, PublicKey } from "@solana/web3.js"; 4 | import { useState, useMemo, useEffect } from "react"; 5 | 6 | export const useTokenAccess = (tokenMintAddress: string, rpcUrl: string) => { 7 | const wallet = useWallet(); 8 | const [hasAccess, setHasAccess] = useState(null); 9 | const [isLoading, setIsLoading] = useState(true); 10 | 11 | const memoizedRpcUrl = useMemo(() => rpcUrl, [rpcUrl]); 12 | 13 | useEffect(() => { 14 | let mounted = true; 15 | setIsLoading(true); 16 | 17 | const checkOwnership = async () => { 18 | if (!wallet.publicKey) { 19 | setHasAccess(false); 20 | setIsLoading(false); 21 | return; 22 | } 23 | 24 | try { 25 | const connection = new Connection(memoizedRpcUrl, { 26 | commitment: "confirmed", 27 | confirmTransactionInitialTimeout: 60000, 28 | }); 29 | 30 | const tokenMint = new PublicKey(tokenMintAddress); 31 | const associatedTokenAddress = await getAssociatedTokenAddress( 32 | tokenMint, 33 | wallet.publicKey 34 | ); 35 | 36 | try { 37 | const tokenAccount = await getAccount( 38 | connection, 39 | associatedTokenAddress 40 | ); 41 | if (mounted) { 42 | setHasAccess(tokenAccount.amount >= 20000); 43 | setIsLoading(false); 44 | } 45 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 46 | } catch (err) { 47 | if (mounted) { 48 | setHasAccess(false); 49 | setIsLoading(false); 50 | } 51 | } 52 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 53 | } catch (err) { 54 | if (mounted) { 55 | setHasAccess(false); 56 | setIsLoading(false); 57 | } 58 | } 59 | }; 60 | 61 | checkOwnership(); 62 | 63 | return () => { 64 | mounted = false; 65 | }; 66 | }, [wallet.publicKey, tokenMintAddress, memoizedRpcUrl]); 67 | 68 | return { hasAccess, isLoading }; 69 | }; 70 | -------------------------------------------------------------------------------- /frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react"; 2 | import type { Metadata } from "next"; 3 | import { Geist, Geist_Mono } from "next/font/google"; 4 | import "./globals.css"; 5 | import SolanaWalletProvider from "@/providers/SolanaWalletProvider"; 6 | import { Toaster } from "react-hot-toast"; 7 | 8 | const geistSans = Geist({ 9 | variable: "--font-geist-sans", 10 | subsets: ["latin"], 11 | }); 12 | 13 | const geistMono = Geist_Mono({ 14 | variable: "--font-geist-mono", 15 | subsets: ["latin"], 16 | }); 17 | 18 | export const metadata: Metadata = { 19 | title: "SWQuery", 20 | description: "SWQuery is a Solana-based data query system.", 21 | }; 22 | 23 | export default function RootLayout({ 24 | children, 25 | }: Readonly<{ 26 | children: React.ReactNode; 27 | }>) { 28 | 29 | return ( 30 | 31 | 32 | <> 33 | {children} 34 | 35 | 36 | 37 | 38 | ) 39 | } -------------------------------------------------------------------------------- /frontend/src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export const dynamic = "force-dynamic"; 4 | 5 | import { motion, AnimatePresence } from "framer-motion"; 6 | import { useRouter } from "next/navigation"; 7 | import { ArrowLeft } from "lucide-react"; 8 | import { Button } from "@/components/Atoms/Buttons/button"; 9 | 10 | export default function NotFound() { 11 | const router = useRouter(); 12 | 13 | return ( 14 |
15 | 26 | 35 | 46 | 47 |
48 |
49 | 50 | 58 |
59 |
60 |
61 | 71 | 72 | 73 | 74 |
75 |
76 |

87 | Coming Soon 88 |

89 |

90 | We are working on something exciting. Stay 91 | tuned! 92 |

93 | 99 | 107 | 108 |
109 |
110 |
111 |
112 |
113 | 126 |
127 | ); 128 | } -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Landing } from "@/components/Organisms/Landing"; 4 | 5 | export default function Home() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/app/pitchdeck/page.tsx: -------------------------------------------------------------------------------- 1 | import { PitchDeck } from "@/components/Organisms/PitchDeck"; 2 | 3 | export default function VideoDemo() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/app/program/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | import { Keypair, PublicKey } from "@solana/web3.js"; 4 | import { buyCredits } from "../../services/program"; 5 | 6 | const BuyCredits = () => { 7 | const [status, setStatus] = useState(null); 8 | 9 | const handleBuyCredits = async () => { 10 | try { 11 | setStatus("Enviando transação..."); 12 | 13 | const buyerKeypair = Keypair.generate(); 14 | 15 | const buyerTokenAccount = new PublicKey( 16 | "9Ld2bRLUC7PrP8NSHK3WgBe3khHgtxAnjDr79YJHDxKF" 17 | ); 18 | 19 | const amountUSDC = 10; 20 | 21 | const signature = await buyCredits( 22 | buyerKeypair, 23 | buyerTokenAccount, 24 | amountUSDC 25 | ); 26 | setStatus(`Sucesso! Transação: ${signature}`); 27 | } catch (error) { 28 | setStatus(`Erro: ${error}`); 29 | } 30 | }; 31 | 32 | return ( 33 |
34 |

Comprar Créditos

35 | 49 | {status &&

{status}

} 50 |
51 | ); 52 | }; 53 | 54 | export default BuyCredits; 55 | -------------------------------------------------------------------------------- /frontend/src/app/real-time-monitor/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react"; 5 | import { Navbar } from "@/components/Molecules/Navbar"; 6 | import TokenLaunchTab from "@/components/Molecules/TokenLaunchTab"; 7 | 8 | export default function RealTimeMonitor() { 9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 10 | const [activeTab, setActiveTab] = useState(0); 11 | const tabItems = [ 12 | { label: "Token Launches", disabled: false }, 13 | { label: "My Tokens", disabled: true }, 14 | { label: "My Wallets", disabled: true }, 15 | ]; 16 | 17 | return ( 18 | <> 19 | 20 |
21 |

22 | Real-Time Transaction Monitor 23 |

24 | 25 | 26 | 27 | {tabItems.map((item, index) => ( 28 | 32 | `px-4 py-2 rounded-lg transition-all ${ 33 | selected 34 | ? "bg-purple-600 text-white" 35 | : "bg-gray-700 text-gray-300 hover:bg-gray-600" 36 | } ${item.disabled ? "opacity-50 cursor-not-allowed" : ""}` 37 | } 38 | > 39 | {item.label} 40 | 41 | ))} 42 | 43 | 44 | 45 | 46 | 47 | 48 | {/* 49 | 50 | 51 | 52 | 53 | */} 54 | 55 | 56 |
57 | 58 | ); 59 | } -------------------------------------------------------------------------------- /frontend/src/app/videodemo/page.tsx: -------------------------------------------------------------------------------- 1 | import { VideoDemoPage } from "@/components/Organisms/VideoDemo"; 2 | 3 | export default function VideoDemo() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/assets/images/logo-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/frontend/src/assets/images/logo-complete.png -------------------------------------------------------------------------------- /frontend/src/assets/images/logo-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/frontend/src/assets/images/logo-horizontal.png -------------------------------------------------------------------------------- /frontend/src/assets/images/new-logo-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SWQuery/swquery/e13cf2ae0864b0eeedc6aec2bbf440f891865741/frontend/src/assets/images/new-logo-horizontal.png -------------------------------------------------------------------------------- /frontend/src/components/Atoms/AvaiableSoon/index.tsx: -------------------------------------------------------------------------------- 1 | import { Hourglass } from "lucide-react"; 2 | 3 | export default function AvaiableSoon() { 4 | return ( 5 |
6 | 7 |

Feature Available Soon...

8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/Buttons/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/CardComponent/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-object-type */ 2 | import React from 'react'; 3 | 4 | interface CardProps extends React.HTMLAttributes {} 5 | interface CardContentProps extends React.HTMLAttributes {} 6 | 7 | export const Card = React.forwardRef( 8 | ({ className, ...props }, ref) => ( 9 |
14 | ) 15 | ); 16 | Card.displayName = "Card"; 17 | 18 | export const CardContent = React.forwardRef( 19 | ({ className, ...props }, ref) => ( 20 |
25 | ) 26 | ); 27 | CardContent.displayName = "CardContent"; 28 | 29 | export default Card; -------------------------------------------------------------------------------- /frontend/src/components/Atoms/CodeExample/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { motion } from "framer-motion"; 3 | import { CopyBlock, obsidian } from "react-code-blocks"; 4 | import { Fira_Code } from "next/font/google"; 5 | 6 | interface CodeExampleProps { 7 | code: string; 8 | } 9 | 10 | const firaCode = Fira_Code({ 11 | weight: "400", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const CodeExample: React.FC = ({ code }) => { 16 | const customTheme = { 17 | ...obsidian, 18 | backgroundColor: "#0A0A0A", 19 | lineNumberColor: "#666", 20 | lineNumberBgColor: "#0A0A0A", 21 | }; 22 | 23 | return ( 24 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | main.rs 39 |
40 |
41 | 47 |
48 |
49 |
50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/CoreFeaturesSection.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Server, TrendingUp, Lock } from "lucide-react"; 3 | 4 | interface CoreFeature { 5 | Icon: ReactNode; 6 | title: string; 7 | description: string; 8 | } 9 | 10 | const coreFeatures: CoreFeature[] = [ 11 | { 12 | Icon: , 13 | title: "Scalable Architecture", 14 | description: 15 | "SWQuery leverages robust, scalable infrastructure to handle high-frequency blockchain queries with ease, ensuring consistent performance even at scale.", 16 | }, 17 | { 18 | Icon: , 19 | title: "Actionable Insights", 20 | description: 21 | "Unlock the power of blockchain data by transforming raw transactions into actionable insights, driving better decisions in DeFi and beyond.", 22 | }, 23 | { 24 | Icon: , 25 | title: "Enterprise-Grade Security", 26 | description: 27 | "Security is at the core of SWQuery. Advanced encryption and secure protocols safeguard user queries and sensitive data at every touchpoint.", 28 | }, 29 | ]; 30 | 31 | export const CoreFeaturesSection = () => { 32 | return ( 33 |
34 |
35 |

36 | Why SWQuery Stands Out 37 |

38 |

39 | SWQuery isn't just an SDK; it's a performance-driven 40 | platform designed for developers and enterprises to harness blockchain 41 | data with unmatched speed and reliability. 42 |

43 | 44 |
45 | {coreFeatures.map((feature, index) => ( 46 |
51 |
{feature.Icon}
52 |

53 | {feature.title} 54 |

55 |

56 | {feature.description} 57 |

58 |
59 | ))} 60 |
61 |
62 |
63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/FeatureSection.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { motion } from "framer-motion"; 4 | import { CopyBlock, obsidian } from "react-code-blocks"; 5 | import { Fira_Code } from "next/font/google"; 6 | 7 | const firaCode = Fira_Code({ 8 | weight: "400", 9 | subsets: ["latin"], 10 | }); 11 | 12 | interface FeatureSectionProps { 13 | title: string; 14 | subtitle: string; 15 | description: string; 16 | buttonText: string; 17 | buttonLink: string; 18 | codeSnippet?: string; 19 | codeLanguage?: string; 20 | codeFileName?: string; 21 | reversed?: boolean; 22 | } 23 | 24 | export const FeatureSection: React.FC = ({ 25 | title, 26 | subtitle, 27 | description, 28 | buttonText, 29 | buttonLink, 30 | codeSnippet, 31 | codeLanguage = "rust", 32 | codeFileName = "example.rs", 33 | reversed = false, 34 | }) => { 35 | const customTheme = { 36 | ...obsidian, 37 | backgroundColor: "#0A0A0A", 38 | lineNumberColor: "#666", 39 | lineNumberBgColor: "#0A0A0A", 40 | }; 41 | 42 | return ( 43 |
44 |
45 |
52 | 53 | 59 |

60 | {title} {subtitle} 61 |

62 |

{description}

63 |
64 | 70 | {buttonText} 71 | 72 |
73 |
74 | 75 | {/* Code Snippet */} 76 | {codeSnippet && ( 77 | 83 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | 99 | {codeFileName} 100 | 101 |
102 |
103 | 108 |
109 |
110 |
111 |
112 |
113 | )} 114 |
115 |
116 |
117 | ); 118 | }; 119 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/FeatureSection/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CodeExample } from '../CodeExample'; 3 | 4 | interface FeatureSectionProps { 5 | title: string; 6 | subtitle: string; 7 | description: string; 8 | buttonText: string; 9 | buttonLink: string; 10 | codeSnippet: string; 11 | reversed?: boolean; 12 | } 13 | 14 | export const FeatureSection: React.FC = ({ 15 | title, 16 | subtitle, 17 | description, 18 | buttonText, 19 | buttonLink, 20 | codeSnippet, 21 | reversed = false, 22 | }) => { 23 | return ( 24 |
25 |
26 |
27 |

{title}

28 |

{subtitle}

29 |

{description}

30 | 34 | {buttonText} 35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 | ); 45 | }; -------------------------------------------------------------------------------- /frontend/src/components/Atoms/SectionItem/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "framer-motion"; 3 | import { LucideIcon } from "lucide-react"; 4 | 5 | interface CardItemProps extends React.HTMLAttributes { 6 | iconColor: string; 7 | title: string; 8 | description: string; 9 | Icon: LucideIcon; 10 | } 11 | 12 | export const CardItem: React.FC = ({ 13 | Icon, 14 | iconColor, 15 | title, 16 | description, 17 | className = "", 18 | }) => { 19 | return ( 20 | 26 | {/* Gradient shadow effect */} 27 |
28 | 29 | {/* Card content */} 30 |
31 |
32 |
33 | 38 |

39 | {title} 40 |

41 |
42 |

43 | {description} 44 |

45 |
46 |
47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/TokenCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { motion } from "framer-motion"; 3 | import { 4 | Twitter, 5 | Monitor, 6 | PiggyBank, 7 | CircleDollarSign, 8 | ChartCandlestick, 9 | Activity, 10 | Clock, 11 | } from "lucide-react"; 12 | 13 | interface Launch { 14 | name: string; 15 | mint: string; 16 | symbol?: string; 17 | uri?: string; 18 | pool?: string; 19 | initialBuy: number; 20 | vTokensInBondingCurve: number; 21 | vSolInBondingCurve: number; 22 | marketCapSol: number; 23 | timestamp: number; 24 | } 25 | 26 | interface TokenDetails { 27 | name: string; 28 | symbol: string; 29 | description: string; 30 | image: string; 31 | twitter: string; 32 | website: string; 33 | } 34 | 35 | interface TokenCard { 36 | launch: Launch; 37 | } 38 | 39 | export default function TokenLaunchCard({ launch }: TokenCard) { 40 | const [details, setDetails] = useState(null); 41 | 42 | useEffect(() => { 43 | if (launch.uri) { 44 | fetch(launch.uri) 45 | .then((response) => response.json()) 46 | .then((data) => { 47 | setDetails({ 48 | name: data.name || launch.name, 49 | symbol: data.symbol || launch.symbol || "N/A", 50 | description: data.description || "No description available", 51 | image: data.image || "", 52 | twitter: data.twitter || "", 53 | website: data.website || "", 54 | }); 55 | }) 56 | .catch((error) => console.error("Failed to fetch token details:", error)); 57 | } 58 | }, [launch.uri, launch.name, launch.symbol]); 59 | 60 | return ( 61 | 67 | {/* Coluna 1: Imagem */} 68 |
69 | {details?.image ? ( 70 | {details.name} 75 | ) : ( 76 |
77 | No Image 78 |
79 | )} 80 |
81 | 82 | {/* Coluna 2: Dados Gerais e Detalhes */} 83 |
84 |

{details?.name || launch.name}

85 |

Pool: {launch.pool || "Unknown"}

86 |

Mint: {launch.mint}

87 |

{details?.description}

88 | 89 | {/* Links */} 90 |
91 | {details?.twitter && ( 92 | 98 | 99 | Twitter 100 | 101 | )} 102 | {details?.website && ( 103 | 109 | 110 | Website 111 | 112 | )} 113 |
114 |
115 | 116 | {/* Coluna 3: Dados Principais */} 117 |
118 |

119 | 120 | Initial Buy: {launch.initialBuy.toFixed(2)} 121 |

122 |

123 | 124 | Market Cap: {launch.marketCapSol.toFixed(2)} SOL 125 |

126 |

127 | 128 | Tokens in Bonding Curve:{" "} 129 | {launch.vTokensInBondingCurve.toFixed(2)} 130 |

131 |

132 | 133 | SOL in Bonding Curve: {launch.vSolInBondingCurve.toFixed(2)} 134 |

135 |

136 | 137 | Last Update:{" "} 138 | {new Date(launch.timestamp).toLocaleString()} 139 |

140 |
141 |
142 | ); 143 | } -------------------------------------------------------------------------------- /frontend/src/components/Atoms/TokenDisplay/TokenDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { Coins } from 'lucide-react'; 2 | import { motion } from 'framer-motion'; 3 | 4 | interface TokenDisplayProps { 5 | balance: number; 6 | } 7 | 8 | export const TokenDisplay = ({ balance }: TokenDisplayProps) => { 9 | const formattedBalance = Number.isFinite(balance) ? balance.toLocaleString() : '0'; 10 | 11 | return ( 12 | 18 |
27 | 28 | {formattedBalance} SWQ 29 | 30 | {/* Hover glow layer */} 31 | 38 |
39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/TokenLaunchCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import Image from "next/image"; 3 | import { motion } from "framer-motion"; 4 | import { 5 | Twitter, 6 | Monitor, 7 | PiggyBank, 8 | CircleDollarSign, 9 | ChartCandlestick, 10 | Activity, 11 | Clock, 12 | } from "lucide-react"; 13 | 14 | interface Launch { 15 | name: string; 16 | mint: string; 17 | symbol?: string; 18 | uri?: string; 19 | pool?: string; 20 | initialBuy: number; 21 | vTokensInBondingCurve: number; 22 | vSolInBondingCurve: number; 23 | marketCapSol: number; 24 | timestamp: number; 25 | } 26 | 27 | interface TokenDetails { 28 | name: string; 29 | symbol: string; 30 | description: string; 31 | image: string; 32 | twitter: string; 33 | website: string; 34 | } 35 | 36 | interface TokenLaunchCardProps { 37 | launch: Launch; 38 | } 39 | 40 | export default function TokenLaunchCard({ launch }: TokenLaunchCardProps) { 41 | const [details, setDetails] = useState(null); 42 | const [liveData, setLiveData] = useState>(new Map()); 43 | const formatCurrencyUSD = (value: number) => { 44 | return new Intl.NumberFormat("en-US", { 45 | style: "currency", 46 | currency: "USD", 47 | maximumFractionDigits: 2, 48 | }).format(value); 49 | }; 50 | 51 | useEffect(() => { 52 | const ws = new WebSocket("wss://pumpportal.fun/api/data"); 53 | 54 | ws.onopen = () => console.log(`🔗 WebSocket connected for ${launch.mint}`); 55 | 56 | ws.onmessage = (event) => { 57 | const data = JSON.parse(event.data); 58 | 59 | // Atualiza os dados apenas se o mint corresponder ao card atual 60 | if (data.mint) { 61 | setLiveData((prevData) => { 62 | const updatedData = new Map(prevData); 63 | updatedData.set(data.mint, data); // Atualiza apenas esse mint 64 | return updatedData; 65 | }); 66 | } 67 | }; 68 | 69 | ws.onerror = (error) => console.error("⚠️ WebSocket error:", error); 70 | ws.onclose = () => console.log(`🔌 WebSocket closed for ${launch.mint}`); 71 | 72 | return () => { 73 | ws.close(); 74 | }; 75 | }, []); 76 | 77 | useEffect(() => { 78 | if (launch.uri) { 79 | fetch(launch.uri) 80 | .then((response) => response.json()) 81 | .then((data) => { 82 | setDetails({ 83 | name: data.name || launch.name, 84 | symbol: data.symbol || launch.symbol || "N/A", 85 | description: data.description || "No description available", 86 | image: data.image || "", 87 | twitter: data.twitter || "", 88 | website: data.website || "", 89 | }); 90 | }) 91 | .catch((error) => 92 | console.error( 93 | `❌ Error fetching token details for ${launch.mint}:`, 94 | error 95 | ) 96 | ); 97 | } 98 | }, [launch.uri]); 99 | 100 | const currentData = liveData.get(launch.mint) || launch; 101 | 102 | return ( 103 | 111 |
112 | {details?.image ? ({details.name} 119 | ) : ( 120 |
121 | No Image 122 |
123 | )} 124 |
125 | 126 |
127 |

128 | {details?.name || launch.name} 129 |

130 |

131 | Pool: {launch.pool || "Unknown"} 132 |

133 |

Mint: {launch.mint}

134 |

{details?.description}

135 | 136 |
137 | {details?.twitter && ( 138 | 144 | 145 | Twitter 146 | 147 | )} 148 | {details?.website && ( 149 | 155 | 156 | Website 157 | 158 | )} 159 |
160 |
161 | 162 |
163 |

164 | 165 | Initial Buy:{" "} 166 | {formatCurrencyUSD(currentData.initialBuy)} 167 |

168 | 169 |

170 | 171 | Market Cap:{" "} 172 | {currentData.marketCapSol.toFixed(2)} SOL 173 |

174 |

175 | 176 | Tokens in Bonding Curve:{" "} 177 | {currentData.vTokensInBondingCurve.toFixed(2)} 178 |

179 |

180 | 181 | SOL in Bonding Curve:{" "} 182 | {currentData.vSolInBondingCurve.toFixed(2)} 183 |

184 |

185 | 186 | Last Update:{" "} 187 | {new Date(currentData.timestamp).toLocaleString()} 188 |

189 |
190 |
191 | ); 192 | } 193 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/TutorialModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { X, ChevronLeft, ChevronRight, MessageSquareText, Search, BarChart2 } from 'lucide-react'; 3 | import { setCookie, getCookie } from 'cookies-next/client'; 4 | import { Card, CardContent } from '@/components/Atoms/CardComponent'; 5 | 6 | interface TutorialModalProps { 7 | isOpen: boolean; 8 | onClose: () => void; 9 | } 10 | 11 | const tutorialSteps = [ 12 | { 13 | title: "Welcome to Solana Account Analysis", 14 | content: "Our natural language chat interface allows you to analyze Solana account transactions with simple English queries. Let's walk through how to make the most of this tool.", 15 | icon: MessageSquareText 16 | }, 17 | { 18 | title: "Ask Questions Naturally", 19 | content: "Simply type your questions about any Solana account. For example: 'Show me all transactions above 5 SOL from last week' or 'What's the most common interaction pattern for this account?'", 20 | icon: Search 21 | }, 22 | { 23 | title: "Understanding Results", 24 | content: "We'll break down complex blockchain data into clear, readable insights. You'll see transaction patterns, frequent interactions, and notable activities all explained in plain English.", 25 | icon: BarChart2 26 | } 27 | ]; 28 | 29 | export const TutorialModal = ({ isOpen, onClose }: TutorialModalProps) => { 30 | const [currentStep, setCurrentStep] = useState(0); 31 | 32 | useEffect(() => { 33 | const hasSeenTutorial = getCookie('tutorial_completed'); 34 | if (hasSeenTutorial === 'true') { 35 | onClose(); 36 | } 37 | }, [onClose]); 38 | 39 | const completeTutorial = () => { 40 | setCookie('tutorial_completed', 'true', { 41 | maxAge: 60 * 60 * 24 * 365, // 1 year 42 | }); 43 | onClose(); 44 | }; 45 | 46 | const skipTutorial = () => { 47 | completeTutorial(); 48 | }; 49 | 50 | const nextStep = () => { 51 | if (currentStep < tutorialSteps.length - 1) { 52 | setCurrentStep(currentStep + 1); 53 | } else { 54 | completeTutorial(); 55 | } 56 | }; 57 | 58 | const prevStep = () => { 59 | if (currentStep > 0) { 60 | setCurrentStep(currentStep - 1); 61 | } 62 | }; 63 | 64 | if (!isOpen) return null; 65 | 66 | const CurrentIcon = tutorialSteps[currentStep].icon; 67 | 68 | return ( 69 |
70 |
71 | 72 | 78 | 79 | 80 | {/* Progress indicator */} 81 |
82 | {tutorialSteps.map((_, index) => ( 83 |
89 | ))} 90 |
91 | 92 | {/* Tutorial content */} 93 |
94 |

95 | {tutorialSteps[currentStep].title} 96 |

97 | 98 |
99 |
100 | 101 |
102 |
103 | 104 |

105 | {tutorialSteps[currentStep].content} 106 |

107 |
108 | 109 | {/* Navigation buttons */} 110 |
111 | 123 | 124 | 130 | 131 | 138 |
139 | 140 | 141 |
142 |
143 | ); 144 | }; 145 | 146 | export default TutorialModal; -------------------------------------------------------------------------------- /frontend/src/components/Atoms/TypeWriterMarkdown/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import React, { useState, useEffect } from "react"; 3 | import ReactMarkdown from "react-markdown"; 4 | import remarkGfm from "remark-gfm"; 5 | 6 | export default function TypewriterMarkdown({ content }: { content: string }) { 7 | const [typedText, setTypedText] = useState(""); 8 | 9 | useEffect(() => { 10 | let i = 0; 11 | setTypedText(""); 12 | const timer = setInterval(() => { 13 | setTypedText((prev) => prev + content[i]); 14 | i++; 15 | if (i >= content.length) clearInterval(timer); 16 | }, 5); 17 | 18 | return () => clearInterval(timer); 19 | }, [content]); 20 | 21 | return ( 22 | { 26 | const { ...rest } = props; 27 | return

; 28 | }, 29 | h2: (props) => { 30 | const { ...rest } = props; 31 | return

; 32 | }, 33 | h3: (props) => { 34 | const { ...rest } = props; 35 | return

; 36 | }, 37 | h4: (props) => { 38 | const { ...rest } = props; 39 | return

; 40 | }, 41 | p: (props) => { 42 | const { ...rest } = props; 43 | return

; 44 | }, 45 | ul: (props) => { 46 | const { ...rest } = props; 47 | return

    ; 48 | }, 49 | li: (props) => { 50 | const { ...rest } = props; 51 | return
  • ; 52 | }, 53 | code: ({ children, ...props }) => { 54 | const { ...rest } = props; 55 | return ( 56 | 57 | {children} 58 | 59 | ); 60 | }, 61 | strong: ({ children, ...props }) => { 62 | const { ...rest } = props; 63 | return {children}; 64 | }, 65 | }} 66 | > 67 | {typedText} 68 | 69 | ); 70 | } -------------------------------------------------------------------------------- /frontend/src/components/Atoms/WalletCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | interface Wallet { 5 | address: string; 6 | } 7 | 8 | interface LatestData { 9 | traderPublicKey: string; 10 | txType: string; 11 | solAmount: number; 12 | timestamp: number; 13 | } 14 | 15 | interface WalletCardProps { 16 | wallet: Wallet; 17 | } 18 | 19 | export default function WalletCard({ wallet }: WalletCardProps) { 20 | const [latestData, setLatestData] = useState(null); 21 | 22 | useEffect(() => { 23 | const handleWebSocketMessage = (event: MessageEvent) => { 24 | try { 25 | const data: LatestData = JSON.parse(event.data); 26 | if (data.traderPublicKey === wallet.address) { 27 | setLatestData(data); 28 | } 29 | } catch (error) { 30 | console.error("Failed to parse WebSocket message", error); 31 | } 32 | }; 33 | 34 | window.addEventListener("message", handleWebSocketMessage); 35 | 36 | return () => { 37 | window.removeEventListener("message", handleWebSocketMessage); 38 | }; 39 | }, [wallet]); 40 | 41 | return ( 42 | 48 |

    Wallet

    49 |

    Address: {wallet.address}

    50 | {latestData && ( 51 |
    52 |

    Last Transaction Type: {latestData.txType}

    53 |

    Amount: {latestData.solAmount} SOL

    54 |

    Timestamp: {new Date(latestData.timestamp).toLocaleString()}

    55 |
    56 | )} 57 |
    58 | ); 59 | } -------------------------------------------------------------------------------- /frontend/src/components/Atoms/WebSocketComponent/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from "react"; 4 | 5 | interface WebSocketComponentProps { 6 | onDataUpdate: (data: unknown) => void; 7 | } 8 | 9 | const WebSocketComponent = ({ onDataUpdate }: WebSocketComponentProps) => { 10 | const [ws, setWs] = useState(null); 11 | const [keys, setKeys] = useState(""); 12 | 13 | useEffect(() => { 14 | const socket = new WebSocket("wss://pumpportal.fun/api/data"); 15 | 16 | socket.onopen = () => { 17 | console.log("WebSocket conectado!"); 18 | }; 19 | 20 | socket.onmessage = (event) => { 21 | const data = JSON.parse(event.data); 22 | onDataUpdate(data); 23 | }; 24 | 25 | socket.onerror = (error) => { 26 | console.error("Erro no WebSocket:", error); 27 | }; 28 | 29 | socket.onclose = () => { 30 | console.log("Conexão WebSocket fechada."); 31 | }; 32 | 33 | setWs(socket); 34 | 35 | return () => { 36 | socket.close(); 37 | }; 38 | }, [onDataUpdate]); 39 | 40 | const sendWebSocketMessage = (method: string) => { 41 | if (!ws || !keys) return; 42 | 43 | const payload = { 44 | method, 45 | keys: keys.split(",").map((key) => key.trim()), 46 | }; 47 | 48 | ws.send(JSON.stringify(payload)); 49 | }; 50 | 51 | return ( 52 |
    53 | setKeys(e.target.value)} 57 | placeholder="Enter keys" 58 | /> 59 | 62 |
    63 | ); 64 | }; 65 | 66 | export default WebSocketComponent; -------------------------------------------------------------------------------- /frontend/src/components/Atoms/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
    17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
    29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
    41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
    53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
    61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
    73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | interface InputProps extends React.ComponentProps<"input"> { 6 | onEnterPress?: () => void 7 | } 8 | 9 | const Input = React.forwardRef( 10 | ({ className, type, onEnterPress, ...props }, ref) => { 11 | const handleKeyDown = (event: React.KeyboardEvent) => { 12 | if (event.key === 'Enter') { 13 | onEnterPress?.() 14 | } 15 | } 16 | 17 | return ( 18 | 28 | ) 29 | } 30 | ) 31 | Input.displayName = "Input" 32 | 33 | export { Input } 34 | -------------------------------------------------------------------------------- /frontend/src/components/Atoms/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /frontend/src/components/Molecules/Landing/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | import { Twitter, Github, MessageCircle, Pill } from "lucide-react"; 4 | import LogoComplete from "../../../../assets/images/logo-complete.png"; 5 | 6 | export const Footer = () => { 7 | const currentYear = new Date().getFullYear(); 8 | 9 | return ( 10 |
    11 |
    12 |
    13 |
    14 | SWquery Logo 21 |
    22 |
    23 |

    Learn

    24 |
      25 |
    • 26 | 30 | SWquery Whitepaper 31 | 32 |
    • 33 |
    • 34 | 38 | Blog 39 | 40 |
    • 41 |
    • 42 | 46 | Privacy Policy 47 | 48 |
    • 49 |
    50 |
    51 |
    52 |

    Developers

    53 |
      54 |
    • 55 | 59 | Documentation 60 | 61 |
    • 62 |
    • 63 | 67 | GitHub 68 | 69 |
    • 70 |
    71 |
    72 |
    73 |

    Community

    74 |
      75 |
    • 76 | 80 | SWquery Dashboard 81 | 82 |
    • 83 |
    • 84 | 88 | Join the Discord 89 | 90 |
    • 91 |
    92 |
    93 |
    94 |
    95 |

    96 | © {currentYear} SWquery. Powered by{" "} 97 | Solana. All rights reserved. 98 |

    99 | 126 |
    127 |
    128 |
    129 | ); 130 | }; 131 | -------------------------------------------------------------------------------- /frontend/src/components/Molecules/Landing/Intro/Explanation/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { motion } from "framer-motion"; 3 | import { Database, Layers, ZoomIn } from "lucide-react"; 4 | import Link from "next/link"; 5 | 6 | export const Explanation = () => { 7 | return ( 8 | <> 9 | 15 |

    16 | Query transactions in your wallet with natural language 17 |

    18 |

    19 | Utilize the power of Solana blockchain technology to easily and 20 | quickly query and visualize transactions using the intelligence of our 21 | platform. 22 |

    23 | 29 |
    30 |
    31 | 32 |
    33 | 34 | Simplified Data Extraction 35 | 36 |
    37 |
    38 |
    39 | 40 |
    41 | 42 | Intelligent Data Transformation 43 | 44 |
    45 |
    46 |
    47 | 48 |
    49 | 50 | Comprehensive Data Visualization 51 | 52 |
    53 |
    54 |
    55 | 56 | 62 | Join Chatbot 63 | 64 | 65 | 66 | 72 | 76 | Learn More 77 | 78 | 79 | 80 | 86 | 87 | Watch Demo 88 | 89 | 90 |
    91 |
    92 | 93 | ); 94 | }; 95 | -------------------------------------------------------------------------------- /frontend/src/components/Molecules/Landing/Intro/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { CodeExample } from "@/components/Atoms/CodeExample"; 4 | import { Explanation } from "./Explanation"; 5 | 6 | export const Intro: React.FC = () => { 7 | return ( 8 |
    9 |
    10 |
    11 | 12 |
    13 |
    14 | println!("Query Result: {:?}", response), 26 | Err(e) => eprintln!("Error: {:?}", e), 27 | } 28 | }`} 29 | /> 30 |
    31 |
    32 | 33 |
    34 |
    35 |
    36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/components/Molecules/Landing/Section/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion } from "framer-motion"; 3 | import { CardItem } from "@/components/Atoms/SectionItem"; 4 | import { LucideIcon } from "lucide-react"; 5 | 6 | interface SectionProps extends React.HTMLAttributes { 7 | title: string; 8 | items?: { 9 | Icon: LucideIcon; 10 | iconColor: string; 11 | title: string; 12 | description: string; 13 | className?: string; 14 | }[]; 15 | columns?: number; 16 | textAlign?: string; 17 | } 18 | 19 | export const Section: React.FC = ({ 20 | title, 21 | items = [], 22 | columns = 3, 23 | textAlign = "center", 24 | className = "", 25 | ...props 26 | }) => { 27 | const columnsClass = columns === 3 ? "md:grid-cols-3" : "md:grid-cols-2"; 28 | const textAlignClass = textAlign === "left" ? "text-left" : "text-center"; 29 | 30 | return ( 31 |
    35 | 41 |

    42 | {title} 43 |

    44 |
    45 | 46 | 53 | {items.map((item, index) => ( 54 | 62 | ))} 63 | 64 |
    65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /frontend/src/components/Molecules/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react" 2 | import { MessageSquare, Wallet, BarChart2, Activity } from "lucide-react" 3 | import { cn } from "@/lib/utils" 4 | 5 | interface SidebarProps { 6 | activeItem: string 7 | onItemClick: (item: string) => void 8 | } 9 | 10 | const Sidebar: React.FC = ({ activeItem, onItemClick }) => { 11 | const menuItems = [ 12 | { id: "chatbot", icon: MessageSquare, label: "On-Chain Chatbot" }, 13 | { id: "wallet", icon: Wallet, label: "Wallet Manager", comingSoon: true }, 14 | { id: "token", icon: BarChart2, label: "Token Analysis", comingSoon: true }, 15 | { id: "monitor", icon: Activity, label: "Real Time Monitor", comingSoon: true }, 16 | ] 17 | 18 | return ( 19 |
    20 |
    21 | 44 |
    45 |
    46 | ) 47 | } 48 | 49 | export default Sidebar -------------------------------------------------------------------------------- /frontend/src/components/Molecules/TokenTab/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | import AvaiableSoon from "@/components/Atoms/AvaiableSoon"; 4 | import TokenCard from "@/components/Atoms/TokenCard"; 5 | 6 | const mockTokenLaunches = [ 7 | { 8 | signature: 9 | "4ikuYPcK75jWMDoqMqrb2qyBjYG26ZtD5CZyS2D2sXBmzheTotV1EWe6cXmsyHncQdzTea9jiNUnKUzTp2zAsvye", 10 | mint: "3acpPNa9CJt8Grne5grf6e3Dej8oZkHTtCtB3hugXYRf", 11 | traderPublicKey: "ELdfNbHR4HgmiaQ44UyRxGWTjFpTuwqfCyy6oq8CMDaJ", 12 | txType: "create", 13 | initialBuy: 2000000.45, 14 | solAmount: 10.25, 15 | bondingCurveKey: "DYRDgYy7JYYvKGu7xDMuQA6K4zo8PnJWcjPQHAJTf3yo", 16 | vTokensInBondingCurve: 500000.34, 17 | vSolInBondingCurve: 25.45, 18 | marketCapSol: 50.2, 19 | name: "DeepDive", 20 | symbol: "DeepGPT", 21 | uri: "https://ipfs.io/ipfs/QmYaA88yjqyXU7c4D3cYUBwPkZym7QhZiayQeDrvZNXZuW", 22 | pool: "pump", 23 | timestamp: 1672531200000, 24 | }, 25 | { 26 | signature: 27 | "2mktXPfC12345DoqMqrb2qyBjYG26ZtD5CZyS2D2sXBmzheTovWE6cXmsyHncQdzTea9jiNUnKUzTp2zAsvye", 28 | mint: "1bcpPQ3Jt9CJt8Grne5grf6e3Dej8oZkHTtCtB3hugXYRf", 29 | traderPublicKey: "VLdfNbHR4HgmiaQ44UyRxGWTjFpTuwqfCyy6oq8CMDaL", 30 | txType: "buy", 31 | initialBuy: 1500000.67, 32 | solAmount: 5.8, 33 | bondingCurveKey: "JTYRgXrY7JYYvKGu7xDMuQA6K4zo8PnJWcjPQHAJTf3yo", 34 | vTokensInBondingCurve: 800000.99, 35 | vSolInBondingCurve: 30.65, 36 | marketCapSol: 35.89, 37 | name: "MoonToken", 38 | symbol: "MOON", 39 | uri: "https://ipfs.io/ipfs/QmYaC79Djz4HgmG4YQUkZYAYfRNfCqAukDrLyRkmBZL3Qo", 40 | pool: "pump", 41 | timestamp: 1672617600000, 42 | }, 43 | ]; 44 | 45 | type FilterKey = 46 | | "initialBuy" 47 | | "marketCapSol" 48 | | "vTokensInBondingCurve" 49 | | "vSolInBondingCurve"; 50 | 51 | interface Filter { 52 | min: number; 53 | max: number; 54 | } 55 | 56 | export default function TokenTab() { 57 | const [tokenLaunches] = useState(mockTokenLaunches); 58 | const [filters, setFilters] = useState>({ 59 | initialBuy: { min: 0, max: Infinity }, 60 | marketCapSol: { min: 0, max: Infinity }, 61 | vTokensInBondingCurve: { min: 0, max: Infinity }, 62 | vSolInBondingCurve: { min: 0, max: Infinity }, 63 | }); 64 | const [appliedFilters, setAppliedFilters] = useState(filters); 65 | 66 | const handleFilterChange = ( 67 | key: FilterKey, 68 | type: "min" | "max", 69 | value: number 70 | ) => { 71 | setFilters((prevFilters) => ({ 72 | ...prevFilters, 73 | [key]: { 74 | ...prevFilters[key], 75 | [type]: value, 76 | }, 77 | })); 78 | }; 79 | 80 | const handleApplyFilters = () => { 81 | setAppliedFilters(filters); 82 | }; 83 | 84 | const handleResetFilters = () => { 85 | const defaultFilters = { 86 | initialBuy: { min: 0, max: Infinity }, 87 | marketCapSol: { min: 0, max: Infinity }, 88 | vTokensInBondingCurve: { min: 0, max: Infinity }, 89 | vSolInBondingCurve: { min: 0, max: Infinity }, 90 | }; 91 | setFilters(defaultFilters); 92 | setAppliedFilters(defaultFilters); 93 | }; 94 | 95 | const filteredLaunches = tokenLaunches.filter((launch) => { 96 | return ( 97 | launch.initialBuy >= appliedFilters.initialBuy.min && 98 | launch.initialBuy <= appliedFilters.initialBuy.max && 99 | launch.marketCapSol >= appliedFilters.marketCapSol.min && 100 | launch.marketCapSol <= appliedFilters.marketCapSol.max && 101 | launch.vTokensInBondingCurve >= 102 | appliedFilters.vTokensInBondingCurve.min && 103 | launch.vTokensInBondingCurve <= 104 | appliedFilters.vTokensInBondingCurve.max && 105 | launch.vSolInBondingCurve >= appliedFilters.vSolInBondingCurve.min && 106 | launch.vSolInBondingCurve <= appliedFilters.vSolInBondingCurve.max 107 | ); 108 | }); 109 | 110 | return ( 111 |
    112 | {/* Sidebar de Filtros */} 113 |
    114 |

    Filters

    115 |
    116 | {( 117 | [ 118 | "initialBuy", 119 | "marketCapSol", 120 | "vTokensInBondingCurve", 121 | "vSolInBondingCurve", 122 | ] as FilterKey[] 123 | ).map((key) => ( 124 |
    125 | 128 |
    129 | 134 | handleFilterChange(key, "min", Number(e.target.value)) 135 | } 136 | /> 137 | 142 | handleFilterChange(key, "max", Number(e.target.value)) 143 | } 144 | /> 145 |
    146 |
    147 | ))} 148 |
    149 | 155 | 161 |
    162 | 163 | {/* Cards de Token Launch */} 164 | 171 | {filteredLaunches.map((launch, index) => ( 172 | 173 | ))} 174 | 175 | 176 |
    177 | ); 178 | } 179 | -------------------------------------------------------------------------------- /frontend/src/components/Molecules/WalletTab/index.tsx: -------------------------------------------------------------------------------- 1 | // import { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | // import WalletCard from "@/components/Atoms/WalletCard"; 4 | import AvaiableSoon from "@/components/Atoms/AvaiableSoon"; 5 | 6 | // const mockWallets = [{ address: "Wallet_1" }, { address: "Wallet_2" }]; 7 | 8 | export default function WalletTab() { 9 | // const [wallets] = useState(mockWallets); 10 | 11 | return ( 12 | 19 | {/* {wallets.map((wallet, index) => ( 20 | 21 | ))} */} 22 | 23 | 24 | ); 25 | } -------------------------------------------------------------------------------- /frontend/src/components/Organisms/Demo/index.tsx: -------------------------------------------------------------------------------- 1 | import { History } from "@/components/Molecules/Demo/History"; 2 | import { Navbar } from "@/components/Molecules/Navbar"; 3 | 4 | export const DemoPage = () => { 5 | return ( 6 |
    7 | 8 | 9 | 10 |
    11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/components/Organisms/PitchDeck/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { motion } from "framer-motion"; 3 | import Link from "next/link"; 4 | import { Navbar } from "@/components/Molecules/Navbar"; 5 | 6 | export const PitchDeck = () => { 7 | return ( 8 | <> 9 | 10 |
    11 | 17 | 18 | Go to Pitch Deck PDF 19 | 20 | 21 |
    22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/src/components/Organisms/VideoDemo/index.tsx: -------------------------------------------------------------------------------- 1 | import { Navbar } from "@/components/Molecules/Navbar"; 2 | 3 | export const VideoDemoPage = () => { 4 | return ( 5 |
    6 | 7 | 8 |
    9 | 18 |
    19 |
    20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
    33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /frontend/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
    17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
    29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
    41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
    53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
    61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
    73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /frontend/src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
    67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
    81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogTrigger, 116 | DialogClose, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /frontend/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverAnchor = PopoverPrimitive.Anchor 13 | 14 | const PopoverContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 18 | 19 | 29 | 30 | )) 31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 32 | 33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } 34 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/providers/SolanaWalletProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { FC, ReactNode } from "react"; 4 | import { 5 | ConnectionProvider, 6 | WalletProvider, 7 | } from "@solana/wallet-adapter-react"; 8 | import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; 9 | import "@solana/wallet-adapter-react-ui/styles.css"; 10 | // import { clusterApiUrl } from "@solana/web3.js"; 11 | import { 12 | PhantomWalletAdapter, 13 | SolflareWalletAdapter, 14 | MathWalletAdapter, 15 | TrustWalletAdapter, 16 | CoinbaseWalletAdapter, 17 | } from "@solana/wallet-adapter-wallets"; 18 | import { useMemo } from "react"; 19 | // import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"; 20 | 21 | const SolanaWalletProvider: FC<{ children: ReactNode }> = ({ children }) => { 22 | // const network = WalletAdapterNetwork.Devnet; 23 | const network = 24 | "https://mainnet.helius-rpc.com/?api-key=c34a7f87-00f1-4691-ba9a-5daddb36588a"; 25 | 26 | const endpoint = useMemo( 27 | () => 28 | // clusterApiUrl(network), 29 | network, 30 | [network] 31 | ); 32 | 33 | const wallets = useMemo( 34 | () => [ 35 | new PhantomWalletAdapter(), 36 | new SolflareWalletAdapter(), 37 | new MathWalletAdapter(), 38 | new TrustWalletAdapter(), 39 | new CoinbaseWalletAdapter(), 40 | ], 41 | // eslint-disable-next-line react-hooks/exhaustive-deps 42 | [network] 43 | ); 44 | 45 | return ( 46 | 47 | 48 | {children} 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default SolanaWalletProvider; 55 | -------------------------------------------------------------------------------- /frontend/src/services/agent.ts: -------------------------------------------------------------------------------- 1 | import api from "./config/api"; 2 | 3 | export interface QueryRequest { 4 | inputUser: string; 5 | address: string; 6 | openai_key: string; 7 | } 8 | 9 | export interface QueryResponse { 10 | result: QueryResult; 11 | tokens: number; 12 | } 13 | 14 | export interface QueryResult { 15 | response: string; 16 | status: string; 17 | params: unknown; 18 | } 19 | 20 | export const generateQuery = async (apiKey: string, payload: QueryRequest) => { 21 | try { 22 | const response = await api.post( 23 | "/agent/generate-query", 24 | payload, 25 | { 26 | headers: { 27 | "x-api-key": apiKey, // TODO: not needed anymore 28 | }, 29 | } 30 | ); 31 | return response.data; 32 | } catch (error: unknown) { 33 | // console.logerror("Error generating query:", error); 34 | throw error; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /frontend/src/services/config/api.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const api = axios.create({ 4 | baseURL: "https://api.swquery.xyz", 5 | // baseURL: "http://localhost:5500", 6 | headers: { 7 | "Content-Type": "application/json", 8 | }, 9 | }); 10 | 11 | export default api; 12 | -------------------------------------------------------------------------------- /frontend/src/services/credits.ts: -------------------------------------------------------------------------------- 1 | import api from "./config/api"; 2 | 3 | export const buyCredits = async (user_pubkey: string, amount: number) => { 4 | try { 5 | const response = await api.post( 6 | "/credits/buy", 7 | { user_pubkey, amount }, 8 | { 9 | headers: { 10 | "Content-Type": "application/json", 11 | "x-api-key": "WDAO4Z1Z503DWJH7060GIYGR0TWIIPBM", // TODO: Not needed anymore 12 | }, 13 | } 14 | ); 15 | return response.data; 16 | } catch (error: unknown) { 17 | // console.logerror("Error buying credits:", error); 18 | throw error; 19 | } 20 | }; 21 | 22 | export const refundCredits = async (user_pubkey: string, amount: number) => { 23 | try { 24 | const response = await api.post( 25 | "/credits/refund", 26 | { user_pubkey, amount }, 27 | { 28 | headers: { 29 | "Content-Type": "application/json", 30 | "x-api-key": "WDAO4Z1Z503DWJH7060GIYGR0TWIIPBM", // TODO: Not needed anymore 31 | }, 32 | } 33 | ); 34 | return response.data; 35 | } catch (error) { 36 | // console.logerror("Error refunding credits:", error); 37 | throw error; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /frontend/src/services/program.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | PublicKey, 5 | Transaction, 6 | TransactionInstruction, 7 | } from "@solana/web3.js"; 8 | import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; 9 | 10 | // TODO: Transform in global constants 11 | const SOLANA_NETWORK = "https://api.devnet.solana.com"; 12 | const PROGRAM_ID = new PublicKey("CTi1Genj9Ev3rRXGHfaqqMHtjh2uSBEXSyfVxG6dyGBQ"); 13 | const TREASURY_PUBKEY = new PublicKey("2nuW7MWYsGdLmsSf5mHrjgn6NqyrS5USai6fdisnUQc4"); 14 | 15 | export const buyCredits = async ( 16 | buyerKeypair: Keypair, 17 | buyerTokenAccount: PublicKey, 18 | amountUSDC: number 19 | ) => { 20 | const connection = new Connection(SOLANA_NETWORK, "confirmed"); 21 | 22 | const [creditsAccount, bump] = await PublicKey.findProgramAddress( 23 | [Buffer.from("credits_account"), buyerKeypair.publicKey.toBuffer()], 24 | PROGRAM_ID 25 | ); 26 | 27 | const instructionData = Buffer.alloc(9); 28 | const writeBigUInt64LE = (buffer: Buffer, value: bigint, offset: number) => { 29 | const low = Number(value & BigInt(0xffffffff)); 30 | const high = Number(value >> BigInt(32)); 31 | buffer.writeUInt32LE(low, offset); 32 | buffer.writeUInt32LE(high, offset + 4); 33 | }; 34 | 35 | writeBigUInt64LE(instructionData, BigInt(amountUSDC), 0); 36 | instructionData.writeUInt8(bump, 8); 37 | 38 | const instruction = new TransactionInstruction({ 39 | programId: PROGRAM_ID, 40 | keys: [ 41 | { pubkey: buyerKeypair.publicKey, isSigner: true, isWritable: true }, 42 | { pubkey: buyerTokenAccount, isSigner: false, isWritable: true }, 43 | { pubkey: TREASURY_PUBKEY, isSigner: false, isWritable: true }, 44 | { pubkey: creditsAccount, isSigner: false, isWritable: true }, 45 | { pubkey: PublicKey.default, isSigner: false, isWritable: false }, 46 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 47 | ], 48 | data: instructionData, 49 | }); 50 | 51 | const transaction = new Transaction().add(instruction); 52 | 53 | transaction.feePayer = buyerKeypair.publicKey; 54 | const { blockhash } = await connection.getLatestBlockhash(); 55 | transaction.recentBlockhash = blockhash; 56 | transaction.sign(buyerKeypair); 57 | 58 | const signature = await connection.sendTransaction(transaction, [buyerKeypair]); 59 | await connection.confirmTransaction(signature, "confirmed"); 60 | 61 | return signature; 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /frontend/src/services/users.ts: -------------------------------------------------------------------------------- 1 | import apiClient from "./config/api"; 2 | 3 | export async function createUser(pubkey: string) { 4 | try { 5 | const response = await apiClient.post("/users", { pubkey }); 6 | return response.data; 7 | } catch (error: unknown) { 8 | // console.logerror("Error creating user:", error); 9 | throw error; 10 | } 11 | } 12 | 13 | export async function getUsers() { 14 | try { 15 | const response = await apiClient.get("/users"); 16 | return response.data; 17 | } catch (error: unknown) { 18 | // console.logerror("Error fetching users:", error); 19 | throw error; 20 | } 21 | } 22 | 23 | export async function getUserByPubkey(pubkey: string) { 24 | try { 25 | const response = await apiClient.get(`/users/${pubkey}`); 26 | return response.data; 27 | } catch (error: unknown) { 28 | // console.logerror("Error fetching user by pubkey:", error); 29 | throw error; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/services/wallet.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PublicKey, 3 | Connection, 4 | Transaction, 5 | SystemProgram, 6 | } from "@solana/web3.js"; 7 | import { WalletContextState } from "@solana/wallet-adapter-react"; 8 | 9 | export class ProgramService { 10 | private connection: Connection; 11 | 12 | constructor(endpoint: string) { 13 | this.connection = new Connection(endpoint, "confirmed"); 14 | } 15 | 16 | async buyCredits( 17 | wallet: WalletContextState, 18 | recipient: string, 19 | amount: number 20 | ): Promise { 21 | if (!wallet.publicKey) { 22 | throw new Error("Wallet not connected"); 23 | } 24 | 25 | const recipientPublicKey = new PublicKey(recipient); 26 | 27 | const transaction = new Transaction().add( 28 | SystemProgram.transfer({ 29 | fromPubkey: wallet.publicKey, 30 | toPubkey: recipientPublicKey, 31 | lamports: amount, 32 | }) 33 | ); 34 | 35 | const blockhash = await this.connection.getLatestBlockhash(); 36 | transaction.recentBlockhash = blockhash.blockhash; 37 | transaction.feePayer = wallet.publicKey; 38 | 39 | const signedTransaction = await wallet.signTransaction?.(transaction); 40 | if (!signedTransaction) { 41 | throw new Error("Transaction signing failed"); 42 | } 43 | 44 | const txid = await this.connection.sendRawTransaction( 45 | signedTransaction.serialize() 46 | ); 47 | 48 | await this.connection.confirmTransaction(txid); 49 | return txid; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/utils/constants.tsx: -------------------------------------------------------------------------------- 1 | export const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; 2 | 3 | export const SWQUERY_WALLET = "BXVjUeXZ5GgbPvqCsUXdGz2G7zsg436GctEC3HkNLABK"; 4 | 5 | export const API_URL = "https://api.swquery.xyz"; 6 | 7 | // export const API_URL = "http://localhost:5500"; 8 | -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | darkMode: ["class"], 5 | content: [ 6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: 'hsl(var(--background))', 14 | foreground: 'hsl(var(--foreground))', 15 | card: { 16 | DEFAULT: 'hsl(var(--card))', 17 | foreground: 'hsl(var(--card-foreground))' 18 | }, 19 | popover: { 20 | DEFAULT: 'hsl(var(--popover))', 21 | foreground: 'hsl(var(--popover-foreground))' 22 | }, 23 | primary: { 24 | DEFAULT: 'hsl(var(--primary))', 25 | foreground: 'hsl(var(--primary-foreground))' 26 | }, 27 | secondary: { 28 | DEFAULT: 'hsl(var(--secondary))', 29 | foreground: 'hsl(var(--secondary-foreground))' 30 | }, 31 | muted: { 32 | DEFAULT: 'hsl(var(--muted))', 33 | foreground: 'hsl(var(--muted-foreground))' 34 | }, 35 | accent: { 36 | DEFAULT: 'hsl(var(--accent))', 37 | foreground: 'hsl(var(--accent-foreground))' 38 | }, 39 | destructive: { 40 | DEFAULT: 'hsl(var(--destructive))', 41 | foreground: 'hsl(var(--destructive-foreground))' 42 | }, 43 | border: 'hsl(var(--border))', 44 | input: 'hsl(var(--input))', 45 | ring: 'hsl(var(--ring))', 46 | chart: { 47 | '1': 'hsl(var(--chart-1))', 48 | '2': 'hsl(var(--chart-2))', 49 | '3': 'hsl(var(--chart-3))', 50 | '4': 'hsl(var(--chart-4))', 51 | '5': 'hsl(var(--chart-5))' 52 | } 53 | }, 54 | borderRadius: { 55 | lg: 'var(--radius)', 56 | md: 'calc(var(--radius) - 2px)', 57 | sm: 'calc(var(--radius) - 4px)' 58 | } 59 | } 60 | }, 61 | // eslint-disable-next-line @typescript-eslint/no-require-imports 62 | plugins: [require("tailwindcss-animate")], 63 | } satisfies Config; 64 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Default target listing all commands 2 | default: 3 | @echo "Available commands:" 4 | @just --list 5 | 6 | # ========= Container Commands ========= 7 | # Run compose 8 | run-compose: 9 | @echo "Running compose..." 10 | docker compose -f deployment/compose.yml up --build -d 11 | 12 | # ========= Build Commands ========= 13 | # Build SDK 14 | build-sdk: 15 | @echo "Building SDK..." 16 | cargo build -p sdk --release 17 | 18 | # Build Server 19 | build-server: 20 | @echo "Building Server..." 21 | cargo build -p server --release 22 | 23 | # Build Credit-Sales Program 24 | build-credit-sales: 25 | @echo "Building Credit-Sales Program..." 26 | cargo build-sbf --manifest-path credit-sales/Cargo.toml 27 | 28 | # Build Frontend 29 | build-frontend: 30 | @echo "Building Frontend..." 31 | cd frontend && yarn build 32 | 33 | # Build All 34 | build-all: 35 | @echo "Building all projects..." 36 | just build-sdk 37 | just build-credit-sales 38 | just build-server 39 | just build-frontend 40 | 41 | # ========= Run Commands ========= 42 | # Run Server 43 | run-server: 44 | @echo "Running Server..." 45 | cd server && cargo run --release 46 | 47 | # Run-watch 48 | run-watch: 49 | @echo "Running Server with watch..." 50 | cd server && cargo watch -x 'run --release' 51 | 52 | # Run Frontend 53 | run-frontend: 54 | @echo "Running Frontend..." 55 | cd frontend && yarn && yarn dev 56 | 57 | # Run All 58 | run-all: 59 | @echo "Running all services..." 60 | just run-server & just run-frontend 61 | 62 | # Run AI Agent 63 | run-agent: 64 | @echo "Running AI Agent..." 65 | cd ai-agent && \ 66 | python3 -m venv .venv && \ 67 | source .venv/bin/activate && \ 68 | pip install -r requirements.txt && \ 69 | uvicorn main:app --host 0.0.0.0 --port 8000 --reload 70 | 71 | # ========= Test Commands ========= 72 | # Test SDK 73 | test-sdk: 74 | @echo "Testing SDK..." 75 | cargo test -p sdk -- --nocapture 76 | 77 | # Test Credit-Sales 78 | test-credit-sales: 79 | @echo "Testing Credit-Sales Program..." 80 | cargo test-sbf --manifest-path credit-sales/Cargo.toml -- --nocapture 81 | 82 | # Test Server 83 | test-server: 84 | @echo "Testing Server..." 85 | cargo test -p server 86 | 87 | # Test All 88 | test-all: 89 | @echo "Testing all projects..." 90 | just test-sdk 91 | just test-credit-sales 92 | just test-server 93 | 94 | # ========= Clean Commands ========= 95 | # Clean SDK 96 | clean-sdk: 97 | @echo "Cleaning SDK..." 98 | cargo clean -p sdk 99 | 100 | # Clean Credit-Sales Program 101 | clean-credit-sales: 102 | @echo "Cleaning Credit-Sales Program..." 103 | cargo clean --manifest-path credit-sales/Cargo.toml 104 | 105 | # Clean Server 106 | clean-server: 107 | @echo "Cleaning Server..." 108 | cargo clean -p server 109 | 110 | # Clean All 111 | clean-all: 112 | @echo "Cleaning all projects..." 113 | cargo clean 114 | just clean-sdk 115 | just clean-credit-sales 116 | just clean-server 117 | 118 | # ========= Check and Format Commands ========= 119 | # Check and Format All 120 | check-fmt: 121 | @echo "Checking and formatting..." 122 | cargo check -p swquery 123 | cargo check --manifest-path credit-sales/Cargo.toml 124 | cargo check -p server 125 | cargo +nightly fmt --all 126 | 127 | # ========= Database Commands ========= 128 | # Reset Database (completely removes container, image, volumes and starts fresh) 129 | reset-db: 130 | @echo "Completely resetting database..." 131 | docker compose -f deployment/compose.yml down swquery-db 132 | docker container rm -f swquery-db || true 133 | docker volume rm -f swquery_swquery-db-data || true 134 | docker rmi postgres:12 || true 135 | docker compose -f deployment/compose.yml up -d swquery-db 136 | @echo "Database has been completely reset and reinitialized" 137 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | wrap_comments = true 2 | comment_width = 80 3 | format_strings = true 4 | imports_granularity = "One" 5 | group_imports = "One" -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | license = "Apache-2.0" 7 | authors = [ 8 | "Arthur Bretas ", 9 | "Marcelo G Feitoza ", 10 | "Pedro Hagge Baptista ", 11 | "Victor Carvalho ", 12 | ] 13 | repository = "https://github.com/vict0rcarvalh0/swquery" 14 | 15 | [dependencies] 16 | reqwest = { version = "0.11", features = ["json", "multipart"] } 17 | uuid = { version = "1.11.0", features = ["v4"] } 18 | serde = { version = "1.0", features = ["derive"] } 19 | tokio = { version = "1.0", features = ["full"] } 20 | tokio-tungstenite = "0.17" 21 | futures-util = "0.3" 22 | tracing = "0.1" 23 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 24 | axum = { version = "0.7.9" } 25 | serde_json = "1.0" 26 | sqlx = { version = "0.8.2", features = [ 27 | "runtime-tokio-native-tls", 28 | "postgres", 29 | "chrono", 30 | "rust_decimal", 31 | ] } 32 | dotenvy = "0.15" 33 | chrono = { version = "0.4", features = ["serde"] } 34 | tower = { version = "0.5.1", features = ["util"] } 35 | http-body-util = "0.1.0" 36 | hyper-util = { version = "0.1", features = [ 37 | "client", 38 | "http1", 39 | "client-legacy", 40 | ] } 41 | tower-http = { version = "0.6.2", features = ["full"] } 42 | hyper = { version = "0.14", features = ["full"] } 43 | futures = "0.3" 44 | solana-sdk = "2.0.17" 45 | thiserror = "1.0" 46 | rand = "0.8.5" 47 | bs58 = "0.4" 48 | swquery = { path = "../swquery" } 49 | rust_decimal = { version = "1.32", features = ["serde-with-float"] } 50 | solana-client = "2.0.17" 51 | solana-transaction-status = "2.0.17" 52 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.82 AS builder 2 | 3 | WORKDIR /usr/src/server 4 | 5 | RUN apt-get update && apt-get install -y libclang-dev cmake 6 | 7 | COPY server/Cargo.toml server/Cargo.lock ./ 8 | COPY server/src ./src 9 | COPY swquery /usr/src/swquery 10 | 11 | RUN cargo build --release 12 | 13 | # Runtime 14 | FROM debian:bookworm-slim AS runtime 15 | 16 | RUN apt-get update && apt-get install -y clang libssl-dev ca-certificates 17 | 18 | WORKDIR /app 19 | COPY --from=builder /usr/src/server/target/release/server /app/server 20 | 21 | EXPOSE 5500 22 | 23 | CMD ["/app/server"] -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # SWQuery Backend Server 2 | 3 | A Rust-based backend server for SWQuery, providing API endpoints for user management, chat interactions, and token operations on the Solana blockchain. 4 | 5 | ## Features 6 | 7 | - User management with Solana public keys 8 | - Credit system with API key authentication 9 | - Real-time chat interactions with AI 10 | - WebSocket connections for token and trade monitoring 11 | - Rate limiting middleware 12 | - Token creation and management on Solana 13 | - PostgreSQL database integration 14 | 15 | ## Tech Stack 16 | 17 | - **Runtime**: Rust 1.82 18 | - **Web Framework**: Axum 0.7.9 19 | - **Database**: PostgreSQL (via SQLx) 20 | - **Authentication**: API Key-based 21 | - **WebSocket**: tokio-tungstenite 22 | - **Other Key Dependencies**: 23 | - tokio (async runtime) 24 | - serde (serialization) 25 | - tower-http (middleware) 26 | - sqlx (database) 27 | - reqwest (HTTP client) 28 | 29 | ## Project Structure 30 | 31 | ``` 32 | server/ 33 | ├── src/ 34 | │ ├── db/ # Database connection and utilities 35 | │ ├── middlewares/ # Custom middleware (rate limiting) 36 | │ ├── models/ # Data models and database schemas 37 | │ ├── routes/ # API route handlers 38 | │ └── main.rs # Application entry point 39 | ├── database/ 40 | │ └── init.sql # Database initialization script 41 | └── Dockerfile # Container configuration 42 | ``` 43 | 44 | ## API Endpoints 45 | 46 | ### Users 47 | - `GET /users` - Get all users 48 | - `POST /users` - Create new user 49 | - `GET /users/:pubkey` - Get user by public key 50 | 51 | ### Credits 52 | - `POST /credits/buy` - Purchase credits 53 | - `POST /credits/refund` - Refund credits 54 | 55 | ### Chatbot 56 | - `POST /chatbot/interact` - Interact with AI chatbot 57 | - `GET /chatbot/chats` - Get user chat history 58 | - `GET /chatbot/chats/:id` - Get specific chat details 59 | 60 | ### Agent 61 | - `POST /agent/generate-query` - Generate query 62 | - `POST /agent/generate-report` - Generate report 63 | 64 | ### Token 65 | - `POST /token/create-token` - Create new token (currently disabled) 66 | 67 | ## Database Schema 68 | 69 | The application uses three main tables: 70 | 71 | ### Users Table 72 | ```sql 73 | CREATE TABLE users ( 74 | id SERIAL PRIMARY KEY, 75 | pubkey VARCHAR NOT NULL UNIQUE 76 | ); 77 | ``` 78 | 79 | ### Credits Table 80 | ```sql 81 | CREATE TABLE credits ( 82 | id SERIAL PRIMARY KEY, 83 | user_id INTEGER NOT NULL REFERENCES users (id) UNIQUE, 84 | balance BIGINT NOT NULL DEFAULT 0, 85 | api_key VARCHAR, 86 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 87 | ); 88 | ``` 89 | 90 | ### Chats Table 91 | ```sql 92 | CREATE TABLE chats ( 93 | id SERIAL PRIMARY KEY, 94 | user_id INTEGER NOT NULL REFERENCES users (id), 95 | input_user TEXT NOT NULL, 96 | response TEXT, 97 | tokens_used BIGINT NOT NULL DEFAULT 0, 98 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 99 | ); 100 | ``` 101 | 102 | ## Rate Limiting 103 | 104 | The application implements rate limiting through a custom middleware that allows: 105 | - 100 requests per minute per IP address 106 | - Configurable window size and request limits 107 | - IP-based tracking using X-Forwarded-For header 108 | 109 | ## WebSocket Integration 110 | 111 | The server maintains a WebSocket connection to `wss://pumpportal.fun/api/data` for real-time monitoring of: 112 | - New token creation 113 | - Account trades 114 | - Token trades 115 | 116 | Yes, let's add the new package verification functionality to the README. I'll update the API Endpoints and Database Schema sections: 117 | 118 | ### Packages 119 | - `GET /packages` - Get all available packages 120 | - `POST /packages/verify` - Verify USDC transaction and add credits 121 | - Verifies Solana USDC transfers 122 | - Retries up to 10 times with 2s delay 123 | - Checks recipient (BXVjUeXZ5GgbPvqCsUXdGz2G7zsg436GctEC3HkNLABK) 124 | - Validates exact USDC amount 125 | - Returns updated credit balance 126 | 127 | ### Database Schema 128 | 129 | Add this new table: 130 | 131 | ```sql 132 | CREATE TABLE packages ( 133 | id SERIAL PRIMARY KEY, 134 | name VARCHAR NOT NULL, 135 | price_usdc DECIMAL NOT NULL, 136 | requests_amount INTEGER NOT NULL 137 | ); 138 | 139 | CREATE TABLE transactions ( 140 | id SERIAL PRIMARY KEY, 141 | user_id INTEGER NOT NULL REFERENCES users(id), 142 | package_id INTEGER NOT NULL REFERENCES packages(id), 143 | signature VARCHAR NOT NULL UNIQUE, 144 | status VARCHAR NOT NULL, 145 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 146 | ); 147 | ``` 148 | 149 | The packages system allows users to: 150 | 1. Purchase predefined credit packages with USDC 151 | 2. Verify their Solana transactions automatically 152 | 3. Get credits added to their account instantly 153 | 4. Track all purchases with transaction history 154 | 155 | ## Environment Variables 156 | 157 | Required environment variables: 158 | - `DATABASE_URL`: PostgreSQL connection string 159 | - Additional configuration can be set via `.env` file 160 | 161 | ## Running the Application 162 | 163 | ### Using Docker 164 | 165 | ```bash 166 | docker build -t swquery-server . 167 | docker run -p 5500:5500 swquery-server 168 | ``` 169 | 170 | ### Local Development 171 | 172 | ```bash 173 | cargo build 174 | cargo run 175 | ``` 176 | 177 | The server will start on `http://0.0.0.0:5500` 178 | 179 | ## License 180 | 181 | Apache-2.0 182 | 183 | ## Authors 184 | 185 | - Arthur Bretas 186 | - Marcelo G Feitoza 187 | - Pedro Hagge Baptista 188 | - Victor Carvalho -------------------------------------------------------------------------------- /server/database/init.sql: -------------------------------------------------------------------------------- 1 | -- SQLBook: Code 2 | CREATE TABLE IF NOT EXISTS users ( 3 | id SERIAL PRIMARY KEY, 4 | pubkey VARCHAR NOT NULL UNIQUE, 5 | subscriptions JSONB NOT NULL DEFAULT '{}' 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS credits ( 9 | id SERIAL PRIMARY KEY, 10 | user_id INTEGER NOT NULL REFERENCES users (id) UNIQUE, -- UNIQUE constraint here 11 | api_key VARCHAR, 12 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 13 | remaining_requests INTEGER NOT NULL DEFAULT 0 14 | ); 15 | 16 | CREATE TABLE IF NOT EXISTS chats ( 17 | id SERIAL PRIMARY KEY, 18 | user_id INTEGER NOT NULL REFERENCES users (id), 19 | input_user TEXT NOT NULL, 20 | response TEXT, 21 | tokens_used BIGINT NOT NULL DEFAULT 0, 22 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 23 | ); 24 | 25 | CREATE TABLE IF NOT EXISTS packages ( 26 | id SERIAL PRIMARY KEY, 27 | name VARCHAR NOT NULL UNIQUE, 28 | price_usdc NUMERIC(10,2) NOT NULL, 29 | requests_amount INTEGER NOT NULL, 30 | description TEXT 31 | ); 32 | 33 | CREATE TABLE IF NOT EXISTS transactions ( 34 | id SERIAL PRIMARY KEY, 35 | user_id INTEGER NOT NULL REFERENCES users(id), 36 | package_id INTEGER NOT NULL REFERENCES packages(id), 37 | signature VARCHAR NOT NULL UNIQUE, 38 | status VARCHAR NOT NULL DEFAULT 'pending', 39 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 40 | ); 41 | 42 | CREATE INDEX IF NOT EXISTS idx_user_id ON chats(user_id); 43 | 44 | CREATE INDEX idx_users_pubkey ON users(pubkey); 45 | CREATE INDEX idx_credits_user_id ON credits(user_id); 46 | CREATE INDEX idx_chats_user_id_created_at ON chats(user_id, created_at); 47 | 48 | -- INSERT INTO 49 | -- users (pubkey) 50 | -- VALUES ( 51 | -- 'GtJHNhKQnnJZQTHq2Vh49HpR4yKKJmUonVYbLeS1RPs8' 52 | -- ); 53 | 54 | -- INSERT INTO 55 | -- credits (user_id, api_key) 56 | -- VALUES ( 57 | -- 1, 58 | -- 'WDAO4Z1Z503DWJH7060GIYGR0TWIIPBM' 59 | -- ); 60 | 61 | -- INSERT INTO 62 | -- chats (user_id, input_user, response, tokens_used) 63 | -- VALUES ( 64 | -- 1, 65 | -- 'Hello', 66 | -- 'Hi there!', 67 | -- 1 68 | -- ); 69 | 70 | INSERT INTO packages (name, price_usdc, requests_amount, description) VALUES 71 | ('Starter', 10, 20, 'Perfect for getting started with basic queries'), 72 | ('Basic', 30, 50, 'Great for regular usage with multiple queries'), 73 | ('Pro', 50, 80, 'Professional package for power users'); -------------------------------------------------------------------------------- /server/example_frontend_integration.md: -------------------------------------------------------------------------------- 1 | # SWQuery Frontend Integration Guide 2 | 3 | This guide demonstrates how to integrate the SWQuery package verification system with a Next.js frontend using the Solana wallet adapter. 4 | 5 | ## Prerequisites 6 | 7 | - Next.js 13+ 8 | - @solana/web3.js 9 | - @solana/wallet-adapter-react 10 | - @solana/wallet-adapter-wallets 11 | - @solana/spl-token 12 | 13 | ## Setup 14 | 15 | First, create the necessary components and utilities: 16 | 17 | ```ts 18 | export const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; 19 | export const SWQUERY_WALLET = "BXVjUeXZ5GgbPvqCsUXdGz2G7zsg436GctEC3HkNLABK"; 20 | // export const API_URL = "http://localhost:5500"; 21 | export const API_URL = "https://api.swquery.xyz"; 22 | ``` 23 | 24 | ```tsx 25 | import { useState } from "react"; 26 | import { Package } from "../types"; 27 | 28 | export function PackageSelector({ packages, onSelect }) { 29 | const [selected, setSelected] = useState(null); 30 | 31 | return ( 32 |
    33 | {packages.map((pkg) => ( 34 |
    { 42 | setSelected(pkg); 43 | onSelect(pkg); 44 | }} 45 | > 46 |

    {pkg.name}

    47 |

    48 | {pkg.requests_amount} Requests 49 |

    50 |

    {pkg.price_usdc} USDC

    51 |
    52 | ))} 53 |
    54 | ); 55 | } 56 | ``` 57 | 58 | ```tsx 59 | import { useConnection, useWallet } from "@solana/wallet-adapter-react"; 60 | import { 61 | createTransferInstruction, 62 | getAssociatedTokenAddress, 63 | } from "@solana/spl-token"; 64 | import { PublicKey, Transaction } from "@solana/web3.js"; 65 | import { useState } from "react"; 66 | import { USDC_MINT, SWQUERY_WALLET } from "../utils/constants"; 67 | 68 | export function PackagePurchase({ selectedPackage, onSuccess }) { 69 | const { connection } = useConnection(); 70 | const { publicKey, sendTransaction } = useWallet(); 71 | const [loading, setLoading] = useState(false); 72 | const [error, setError] = useState(null); 73 | 74 | const handlePurchase = async () => { 75 | try { 76 | setLoading(true); 77 | setError(null); 78 | 79 | // Get token accounts 80 | const senderAta = await getAssociatedTokenAddress( 81 | new PublicKey(USDC_MINT), 82 | publicKey! 83 | ); 84 | const recipientAta = await getAssociatedTokenAddress( 85 | new PublicKey(USDC_MINT), 86 | new PublicKey(SWQUERY_WALLET) 87 | ); 88 | 89 | // Create transfer instruction 90 | const transferInstruction = createTransferInstruction( 91 | senderAta, 92 | recipientAta, 93 | publicKey!, 94 | BigInt(selectedPackage.price_usdc * 1_000_000) // Convert to USDC decimals 95 | ); 96 | 97 | // Create and send transaction 98 | const transaction = new Transaction().add(transferInstruction); 99 | const signature = await sendTransaction(transaction, connection); 100 | 101 | // Wait for confirmation 102 | await connection.confirmTransaction(signature); 103 | 104 | // Verify with backend 105 | const { data } = await axios.post(`${API_URL}/packages/verify`, { 106 | package_id: selectedPackage.id, 107 | signature, 108 | user_pubkey: publicKey!.toString(), 109 | }); 110 | 111 | onSuccess(data); 112 | } catch (err) { 113 | setError(err.response?.data || err.message); 114 | console.error("Purchase failed:", err); 115 | } finally { 116 | setLoading(false); 117 | } 118 | }; 119 | 120 | return ( 121 |
    122 | 129 | {error &&

    {error}

    } 130 |
    131 | ); 132 | } 133 | ``` 134 | 135 | ## Usage 136 | 137 | In your page component: 138 | 139 | ```tsx 140 | import { useEffect, useState } from "react"; 141 | import axios from "axios"; 142 | import { PackageSelector } from "../components/PackageSelector"; 143 | import { PackagePurchase } from "../components/PackagePurchase"; 144 | import { API_URL } from "../utils/constants"; 145 | 146 | export default function PackagesPage() { 147 | const [packages, setPackages] = useState([]); 148 | const [selected, setSelected] = useState(null); 149 | const [purchaseResult, setPurchaseResult] = useState(null); 150 | 151 | useEffect(() => { 152 | axios 153 | .get(`${API_URL}/packages`) 154 | .then((response) => setPackages(response.data)) 155 | .catch((error) => 156 | console.error("Failed to fetch packages:", error) 157 | ); 158 | }, []); 159 | 160 | return ( 161 |
    162 |

    Purchase Credits

    163 | 164 | 165 | 166 | {selected && ( 167 | 171 | )} 172 | 173 | {purchaseResult && ( 174 |
    175 |

    Purchase Successful!

    176 |

    {purchaseResult.message}

    177 |

    178 | Remaining Credits: {purchaseResult.remaining_requests} 179 |

    180 |

    Added Credits: {purchaseResult.package_requests}

    181 |
    182 | )} 183 |
    184 | ); 185 | } 186 | ``` 187 | 188 | ## Flow Explanation 189 | 190 | 1. User connects their wallet 191 | 2. Selects a package from the available options 192 | 3. Clicks purchase, which: 193 | - Creates a USDC transfer transaction 194 | - Sends it to the Solana network 195 | - Waits for confirmation 196 | - Sends signature to backend for verification 197 | - Displays updated credit balance 198 | 4. Shows success message with updated credits 199 | 200 | ## Error Handling 201 | 202 | The integration handles various error cases: 203 | 204 | - Wallet not connected 205 | - Insufficient USDC balance 206 | - Transaction failure 207 | - Backend verification issues 208 | 209 | ## Testing 210 | 211 | Use the test signature from `test.sh` (lines 67-69) for development testing: 212 | 213 | ```typescript 214 | const testSignature = 215 | "3dMe8itJ7Rbc3E42aFMDWyrJJPv4dHUpXgoqWFKhHNKB4mbd2veFp8LMEdfzEAoYS9XbXTTQSpQszwSpmY33q9Ky"; 216 | ``` 217 | -------------------------------------------------------------------------------- /server/src/db.rs: -------------------------------------------------------------------------------- 1 | use { 2 | sqlx::{postgres::PgPoolOptions, Pool, Postgres}, 3 | std::env, 4 | std::time::Duration, 5 | tokio::time::sleep, 6 | }; 7 | 8 | pub type DbPool = Pool; 9 | 10 | pub async fn connect() -> DbPool { 11 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 12 | let mut attempts = 0; 13 | let max_attempts = 10; 14 | 15 | loop { 16 | match PgPoolOptions::new() 17 | .max_connections(5) 18 | .acquire_timeout(Duration::from_secs(30)) 19 | .connect(&database_url) 20 | .await 21 | { 22 | Ok(pool) => return pool, 23 | Err(e) => { 24 | attempts += 1; 25 | if attempts >= max_attempts { 26 | panic!( 27 | "Failed to connect to the database after {} attempts: {}", 28 | max_attempts, e 29 | ); 30 | } 31 | eprintln!( 32 | "Failed to connect to database (attempt {}), retrying in 10 seconds...", 33 | attempts 34 | ); 35 | sleep(Duration::from_secs(10)).await; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod db; 2 | mod middlewares; 3 | mod models; 4 | mod routes; 5 | mod utils; 6 | 7 | use { 8 | axum::{ 9 | http::Method, 10 | middleware::from_fn_with_state, 11 | routing::{get, post}, 12 | Router, 13 | }, 14 | db::connect, 15 | dotenvy::dotenv, 16 | routes::{ 17 | agent::{generate_query, generate_report}, 18 | chatbot::{chatbot_interact, get_chat_by_id, get_chats_for_user}, 19 | credits::{buy_credits, refund_credits}, 20 | packages::{get_packages, get_user_usage, verify_transaction}, 21 | users::{create_user, get_usage, get_user_by_pubkey, get_users, manage_subscription}, 22 | token::get_token_info 23 | }, 24 | std::time::Duration, 25 | tower_http::cors::{Any, CorsLayer}, 26 | }; 27 | 28 | pub const AGENT_API_URL: &str = "http://agent:8000"; 29 | // pub const AGENT_API_URL: &str = "http://localhost:8000"; 30 | 31 | #[tokio::main] 32 | async fn main() { 33 | dotenv().ok(); 34 | tracing_subscriber::fmt::init(); 35 | 36 | let pool = connect().await; 37 | 38 | let cors = CorsLayer::new() 39 | .allow_origin(Any) 40 | .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) 41 | .allow_headers(Any); 42 | 43 | let rate_limiter = middlewares::rate_limiter::RateLimiter::new(100, Duration::from_secs(60)); 44 | 45 | let agent_router = Router::new() 46 | .route("/generate-query", post(generate_query)) 47 | .route("/generate-report", post(generate_report)); 48 | let chatbot_router = Router::new() 49 | .route("/interact", post(chatbot_interact)) 50 | .route("/chats", get(get_chats_for_user)) 51 | .route("/chats/:id", get(get_chat_by_id)); 52 | let users_router = Router::new() 53 | .route("/", get(get_users).post(create_user)) 54 | .route("/:pubkey", get(get_user_by_pubkey)) 55 | .route("/:pubkey/subscriptions", post(manage_subscription)) 56 | .route("/usage", post(get_user_usage)) 57 | .route("/:pubkey/usage", get(get_usage)); 58 | let token_router = Router::new() 59 | .route("/token_info/:name", get(get_token_info)); 60 | 61 | let app = Router::new() 62 | .route("/health", get(|| async { "ok" })) 63 | // .route("/credits/buy", post(buy_credits)) 64 | // .route("/credits/refund", post(refund_credits)) 65 | .route("/packages/:pubkey", get(get_packages)) 66 | .route("/packages/verify", post(verify_transaction)) 67 | .nest("/agent", agent_router) 68 | .nest("/chatbot", chatbot_router) 69 | .nest("/users", users_router) 70 | .nest("/token", token_router) 71 | .route("/:api_key/helius", get(|| async { "ok" })) 72 | .with_state(pool) 73 | .layer(cors) 74 | .layer(from_fn_with_state( 75 | rate_limiter.clone(), 76 | middlewares::rate_limiter::rate_limit_middleware, 77 | )); 78 | 79 | let listener = tokio::net::TcpListener::bind("0.0.0.0:5500").await.unwrap(); 80 | 81 | println!("Listening on: http://{}", listener.local_addr().unwrap()); 82 | 83 | axum::serve(listener, app).await.unwrap(); 84 | } 85 | -------------------------------------------------------------------------------- /server/src/middlewares/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rate_limiter; 2 | -------------------------------------------------------------------------------- /server/src/middlewares/rate_limiter.rs: -------------------------------------------------------------------------------- 1 | use { 2 | axum::{ 3 | body::Body, 4 | extract::State, 5 | http::{Request, StatusCode}, 6 | middleware::Next, 7 | response::Response, 8 | }, 9 | std::{ 10 | collections::HashMap, 11 | sync::Arc, 12 | time::{Duration, Instant}, 13 | }, 14 | tokio::sync::Mutex, 15 | }; 16 | 17 | #[derive(Clone)] 18 | pub struct RateLimiter { 19 | requests: Arc>>, 20 | max_requests: u32, 21 | window_size: Duration, 22 | } 23 | 24 | impl RateLimiter { 25 | pub fn new(max_requests: u32, window_size: Duration) -> Self { 26 | Self { 27 | requests: Arc::new(Mutex::new(HashMap::new())), 28 | max_requests, 29 | window_size, 30 | } 31 | } 32 | 33 | pub async fn check_rate_limit(&self, key: &str) -> bool { 34 | let mut requests = self.requests.lock().await; 35 | let now = Instant::now(); 36 | 37 | if let Some((window_start, count)) = requests.get_mut(key) { 38 | if now.duration_since(*window_start) > self.window_size { 39 | *window_start = now; 40 | *count = 1; 41 | true 42 | } else if *count >= self.max_requests { 43 | false 44 | } else { 45 | *count += 1; 46 | true 47 | } 48 | } else { 49 | requests.insert(key.to_string(), (now, 1)); 50 | true 51 | } 52 | } 53 | } 54 | 55 | pub async fn rate_limit_middleware( 56 | State(rate_limiter): State, 57 | req: Request, 58 | next: Next, 59 | ) -> Result { 60 | let key = req 61 | .headers() 62 | .get("x-forwarded-for") 63 | .and_then(|hv| hv.to_str().ok()) 64 | .unwrap_or("unknown") 65 | .to_string(); 66 | 67 | if rate_limiter.check_rate_limit(&key).await { 68 | Ok(next.run(req).await) 69 | } else { 70 | Err(StatusCode::TOO_MANY_REQUESTS) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /server/src/models/chat.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDateTime; 2 | 3 | #[derive(sqlx::FromRow)] 4 | pub struct ChatModel { 5 | pub id: i32, 6 | pub user_id: i32, 7 | pub input_user: String, 8 | pub response: Option, 9 | pub tokens_used: i64, 10 | pub created_at: NaiveDateTime, 11 | } 12 | -------------------------------------------------------------------------------- /server/src/models/credits.rs: -------------------------------------------------------------------------------- 1 | #[derive(sqlx::FromRow)] 2 | #[allow(dead_code)] 3 | pub struct CreditModel { 4 | pub id: i32, 5 | pub user_id: i32, 6 | pub balance: i64, 7 | pub created_at: chrono::NaiveDateTime, 8 | } 9 | -------------------------------------------------------------------------------- /server/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chat; 2 | pub mod credits; 3 | pub mod package; 4 | pub mod user; 5 | 6 | pub use {chat::*, credits::*, package::*, user::*}; 7 | -------------------------------------------------------------------------------- /server/src/models/package.rs: -------------------------------------------------------------------------------- 1 | use { 2 | chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}, 3 | rust_decimal::Decimal, 4 | serde::{Deserialize, Serialize}, 5 | sqlx::types::Decimal as DecimalType, 6 | }; 7 | 8 | #[derive(sqlx::FromRow, Serialize)] 9 | pub struct Package { 10 | pub id: i32, 11 | pub name: String, 12 | pub price_usdc: DecimalType, 13 | pub requests_amount: i32, 14 | pub description: String, 15 | } 16 | 17 | #[derive(sqlx::FromRow, Serialize)] 18 | pub struct Transaction { 19 | pub id: i32, 20 | pub user_id: i32, 21 | pub package_id: i32, 22 | pub signature: String, 23 | pub status: String, 24 | pub created_at: NaiveDateTime, 25 | } 26 | 27 | #[derive(Deserialize)] 28 | pub struct VerifyTransactionRequest { 29 | pub package_id: i32, 30 | pub signature: String, 31 | pub user_pubkey: String, 32 | } 33 | 34 | #[derive(Serialize)] 35 | pub struct PackageResponse { 36 | pub id: i32, 37 | pub name: String, 38 | #[serde(with = "rust_decimal::serde::float")] 39 | pub price_usdc: Decimal, 40 | pub description: String, 41 | pub requests_amount: i32, 42 | } 43 | 44 | #[derive(Deserialize)] 45 | pub struct GetUserUsageRequest { 46 | pub user_pubkey: String, 47 | } 48 | 49 | #[derive(sqlx::FromRow, Serialize, Deserialize)] 50 | pub struct ChatQuantity { 51 | chat_date: NaiveDate, 52 | chats_per_day: i64, 53 | } 54 | 55 | #[derive(Serialize)] 56 | pub struct GetUserUsageResponse { 57 | pub remaining_requests: i64, 58 | pub total_requests: i64, 59 | pub chats_per_day: Vec, 60 | } 61 | 62 | #[derive(Serialize)] 63 | pub struct VerifyTransactionResponse { 64 | pub message: String, 65 | pub remaining_requests: i64, 66 | pub package_requests: i32, 67 | } 68 | 69 | impl From for PackageResponse { 70 | fn from(package: Package) -> Self { 71 | Self { 72 | id: package.id, 73 | name: package.name, 74 | price_usdc: package.price_usdc, 75 | description: package.description, 76 | requests_amount: package.requests_amount, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /server/src/models/query.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Serialize, Deserialize)] 2 | pub struct QueryRequest { 3 | pub input_user: String, 4 | pub address: String, 5 | #[serde(skip_serializing_if = "Option::is_none")] 6 | pub helius_key: Option, 7 | } 8 | -------------------------------------------------------------------------------- /server/src/models/user.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDateTime; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_json::Value; 4 | 5 | #[derive(sqlx::FromRow, Serialize, Deserialize, Clone, Debug)] 6 | pub struct User { 7 | pub id: i32, 8 | pub pubkey: String, 9 | pub subscriptions: Value, 10 | } 11 | 12 | #[derive(Serialize, Deserialize)] 13 | pub struct UserWithApiKey { 14 | pub id: i32, 15 | pub pubkey: String, 16 | pub subscriptions: Value, 17 | pub api_key: Option, 18 | } 19 | 20 | #[derive(Serialize)] 21 | pub struct UserUsage { 22 | pub remaining_credits: i64, 23 | pub last_purchases: Vec, 24 | pub last_7_days_usage: i64, 25 | } 26 | 27 | #[derive(Serialize, sqlx::FromRow)] 28 | pub struct Transaction { 29 | pub id: i64, 30 | pub package_id: i32, 31 | pub created_at: NaiveDateTime, 32 | pub name: String, 33 | pub price_usdc: rust_decimal::Decimal, 34 | pub requests_amount: i32, 35 | } 36 | -------------------------------------------------------------------------------- /server/src/routes/agent.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use { 3 | axum::{ 4 | extract::State, 5 | http::{HeaderMap, StatusCode}, 6 | Json, 7 | }, 8 | reqwest::Client, 9 | serde_json::Value, 10 | sqlx::PgPool, 11 | }; 12 | 13 | #[derive(Deserialize, Serialize, Debug)] 14 | pub struct QueryRequest { 15 | #[serde(rename = "inputUser")] 16 | pub input_user: String, 17 | pub address: String, 18 | } 19 | 20 | #[derive(Deserialize, Serialize, Debug)] 21 | pub struct QueryRequestReport { 22 | #[serde(rename = "jsonReturned")] 23 | pub input_user: String, 24 | #[serde(rename = "question")] 25 | pub chatted: String, 26 | pub address: String, 27 | pub openai_key: String, 28 | } 29 | 30 | #[derive(Serialize, Deserialize)] 31 | pub struct QueryResponse { 32 | pub result: QueryResult, 33 | pub tokens: i64, 34 | } 35 | 36 | #[derive(Serialize, Deserialize)] 37 | pub struct QueryResponseReport { 38 | pub result: String, 39 | pub tokens: i64, 40 | } 41 | 42 | #[derive(Serialize, Deserialize)] 43 | pub struct QueryResult { 44 | pub response: String, 45 | status: String, 46 | params: Value, 47 | } 48 | 49 | pub async fn fetch_credit_info( 50 | pool: &PgPool, 51 | api_key: &str, 52 | ) -> Result<(i32, String, i64, String), (StatusCode, String)> { 53 | sqlx::query_as::<_, (i32, String, i64, String)>( 54 | "SELECT c.user_id, u.pubkey, c.remaining_requests::bigint, c.api_key 55 | FROM credits c 56 | JOIN users u ON u.id = c.user_id 57 | WHERE c.api_key = $1 LIMIT 1", 58 | ) 59 | .bind(api_key) 60 | .fetch_optional(pool) 61 | .await 62 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? 63 | .ok_or((StatusCode::UNAUTHORIZED, "Invalid API key".to_string())) 64 | } 65 | 66 | pub async fn send_query_request( 67 | payload: &mut QueryRequest, 68 | api_key: &str, 69 | ) -> Result { 70 | let client = Client::new(); 71 | payload.input_user = payload.input_user.to_lowercase(); 72 | 73 | // Debug print 74 | println!("Sending payload to AI agent: {:?}", payload); 75 | 76 | let response = client 77 | .post(format!("{}/query/generate-query", crate::AGENT_API_URL)) 78 | .header("x-api-key", api_key) 79 | .json(payload) 80 | .send() 81 | .await 82 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; 83 | 84 | // Debug print 85 | println!("Response status: {}", response.status()); 86 | 87 | response 88 | .json() 89 | .await 90 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())) 91 | } 92 | 93 | pub async fn send_query_request_report( 94 | payload: &mut QueryRequestReport, 95 | api_key: &str, 96 | ) -> Result { 97 | let client = Client::new(); 98 | payload.input_user = payload.input_user.to_lowercase(); 99 | let response = client 100 | .post(format!( 101 | "{}/query/generate-visualization", 102 | crate::AGENT_API_URL 103 | )) 104 | .header("x-api-key", api_key) 105 | .json(payload) 106 | .send() 107 | .await 108 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; 109 | 110 | response 111 | .json() 112 | .await 113 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())) 114 | } 115 | 116 | pub async fn generate_query( 117 | State(pool): State, 118 | headers: HeaderMap, 119 | Json(mut payload): Json, 120 | ) -> Result<(StatusCode, Json), (StatusCode, String)> { 121 | println!("Generating query"); 122 | let api_key = headers 123 | .get("x-api-key") 124 | .and_then(|v| v.to_str().ok()) 125 | .ok_or((StatusCode::UNAUTHORIZED, "Missing API key".to_string()))?; 126 | 127 | let credit = fetch_credit_info(&pool, api_key).await?; 128 | 129 | if credit.2 < 1 { 130 | return Err(( 131 | StatusCode::PAYMENT_REQUIRED, 132 | "Insufficient credits".to_string(), 133 | )); 134 | } 135 | 136 | println!("Sending query request"); 137 | let query_response = send_query_request(&mut payload, api_key).await?; 138 | 139 | Ok((StatusCode::OK, Json(query_response))) 140 | } 141 | 142 | pub async fn generate_report( 143 | State(_pool): State, 144 | headers: HeaderMap, 145 | Json(mut payload): Json, 146 | ) -> Result<(StatusCode, Json), (StatusCode, String)> { 147 | let _api_key = headers 148 | .get("x-api-key") 149 | .and_then(|v| v.to_str().ok()) 150 | .ok_or((StatusCode::UNAUTHORIZED, "Missing API key".to_string()))?; 151 | 152 | println!("Getting user info"); 153 | 154 | let query_response = send_query_request_report(&mut payload, _api_key).await?; 155 | 156 | Ok((StatusCode::OK, Json(query_response))) 157 | } 158 | 159 | pub async fn generate_report_service( 160 | _pool: PgPool, 161 | headers: HeaderMap, 162 | Json(mut payload): Json, 163 | ) -> Result<(StatusCode, Json), (StatusCode, String)> { 164 | let _api_key = headers 165 | .get("x-api-key") 166 | .and_then(|v| v.to_str().ok()) 167 | .ok_or((StatusCode::UNAUTHORIZED, "Missing API key".to_string()))?; 168 | 169 | println!("Getting user info"); 170 | 171 | let query_response = send_query_request_report(&mut payload, _api_key).await?; 172 | 173 | Ok((StatusCode::OK, Json(query_response))) 174 | } 175 | -------------------------------------------------------------------------------- /server/src/routes/credits.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::models::{CreditModel, User}, 3 | axum::{extract::State, http::StatusCode, Json}, 4 | serde::{Deserialize, Serialize}, 5 | sqlx::PgPool, 6 | }; 7 | 8 | #[derive(Deserialize)] 9 | pub struct BuyCredits { 10 | pub user_pubkey: String, 11 | pub amount: i64, // Amount in credits 12 | } 13 | 14 | #[derive(Serialize)] 15 | pub struct CreditResponse { 16 | pub user_pubkey: String, 17 | pub new_balance: i64, 18 | pub api_key: Option, 19 | } 20 | 21 | #[derive(Serialize)] 22 | pub struct ValidateCreditsResponse { 23 | pub success: bool, 24 | pub remaining_balance: i64, 25 | } 26 | 27 | // Modify buy_credits to handle API key 28 | pub async fn buy_credits( 29 | State(pool): State, 30 | Json(payload): Json, 31 | ) -> Result<(StatusCode, Json), (StatusCode, String)> { 32 | // let user = sqlx::query_as::<_, User>("SELECT id, pubkey, pump_portal_payload FROM users WHERE pubkey = $1") 33 | let user = sqlx::query_as::<_, User>("SELECT id, pubkey, subscriptions FROM users WHERE pubkey = $1") 34 | .bind(&payload.user_pubkey) 35 | .fetch_optional(&pool) 36 | .await 37 | .map_err(|e| { 38 | eprintln!("Database error: {}", e); 39 | (StatusCode::INTERNAL_SERVER_ERROR, "Database error".to_string()) 40 | })?; 41 | 42 | match user { 43 | Some(user) => { 44 | let api_key = crate::utils::generate_api_key(); 45 | match update_or_insert_credits(&pool, user.id, payload.amount, &api_key).await { 46 | Ok(credit) => Ok(( 47 | StatusCode::CREATED, 48 | Json(CreditResponse { 49 | user_pubkey: payload.user_pubkey, 50 | new_balance: credit.balance, 51 | api_key: Some(api_key), 52 | }), 53 | )), 54 | Err(e) => Err(e), 55 | } 56 | } 57 | None => Ok(( 58 | StatusCode::CREATED, 59 | Json(CreditResponse { 60 | user_pubkey: payload.user_pubkey, 61 | new_balance: 0, 62 | api_key: None, 63 | }), 64 | )), 65 | } 66 | } 67 | 68 | async fn update_or_insert_credits( 69 | pool: &PgPool, 70 | user_id: i32, 71 | amount: i64, 72 | api_key: &str, 73 | ) -> Result { 74 | let credit = sqlx::query_as::<_, CreditModel>( 75 | "INSERT INTO credits (user_id, balance, api_key) 76 | VALUES ($1, $2, $3) 77 | ON CONFLICT (user_id) 78 | DO UPDATE SET balance = credits.balance + EXCLUDED.balance, 79 | api_key = COALESCE(credits.api_key, EXCLUDED.api_key) 80 | RETURNING *", 81 | ) 82 | .bind(user_id) 83 | .bind(amount) 84 | .bind(api_key) 85 | .fetch_one(pool) 86 | .await 87 | .map_err(|e| { 88 | eprintln!("Credits operation error: {}", e); 89 | ( 90 | StatusCode::INTERNAL_SERVER_ERROR, 91 | "Failed to update credits".to_string(), 92 | ) 93 | })?; 94 | 95 | Ok(credit) 96 | } 97 | 98 | #[derive(Deserialize)] 99 | pub struct RefundCredits { 100 | pub user_pubkey: String, 101 | pub amount: i64, // Amount to refund in credits 102 | } 103 | 104 | pub async fn refund_credits( 105 | State(pool): State, 106 | Json(payload): Json, 107 | ) -> Result<(StatusCode, Json), (StatusCode, String)> { 108 | let user = sqlx::query_as::<_, User>("SELECT id, pubkey FROM users WHERE pubkey = $1") 109 | .bind(&payload.user_pubkey) 110 | .fetch_optional(&pool) 111 | .await 112 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; 113 | 114 | let mut balance = 0; 115 | 116 | if let Some(user) = user { 117 | match update_credit_balance(&pool, user.id as i64, payload.amount).await { 118 | Ok(new_balance) => balance = new_balance, 119 | Err(e) => return Err(e), 120 | } 121 | } 122 | 123 | let response = CreditResponse { 124 | user_pubkey: payload.user_pubkey, 125 | new_balance: balance, 126 | api_key: None, 127 | }; 128 | 129 | Ok((StatusCode::CREATED, Json(response))) 130 | } 131 | 132 | async fn update_credit_balance( 133 | pool: &PgPool, 134 | user_id: i64, 135 | amount: i64, 136 | ) -> Result { 137 | let credit = 138 | sqlx::query_as::<_, CreditModel>("SELECT * FROM credits WHERE user_id = $1 FOR UPDATE") 139 | .bind(user_id) 140 | .fetch_optional(pool) 141 | .await 142 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; 143 | 144 | if let Some(credit) = credit { 145 | if credit.balance < amount { 146 | return Err((StatusCode::BAD_REQUEST, "Insufficient balance".to_string())); 147 | } 148 | 149 | let credit = sqlx::query_as::<_, CreditModel>( 150 | "UPDATE credits SET balance = balance - $1 WHERE user_id = $2 RETURNING *", 151 | ) 152 | .bind(amount) 153 | .bind(user_id) 154 | .fetch_one(pool) 155 | .await 156 | .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; 157 | 158 | return Ok(credit.balance); 159 | } 160 | 161 | Ok(0) 162 | } 163 | -------------------------------------------------------------------------------- /server/src/routes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod agent; 2 | pub mod chatbot; 3 | pub mod credits; 4 | pub mod users; 5 | pub mod packages; 6 | pub mod token; -------------------------------------------------------------------------------- /server/src/routes/token.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::Path, http::StatusCode, Json, Router, routing::get}; 2 | use serde_json::Value; 3 | use std::collections::HashMap; 4 | use reqwest; 5 | 6 | pub async fn get_token_info(Path(token_name): Path) -> Result, StatusCode> { 7 | println!("Fetching token info for {}", token_name); 8 | 9 | let token_address = match fetch_token_address(&token_name).await { 10 | Some(address) => address, 11 | None => { 12 | println!("Token {} not found in Solana token list", token_name); 13 | return Err(StatusCode::NOT_FOUND); 14 | } 15 | }; 16 | 17 | match fetch_market_data(&token_address).await { 18 | Some(data) => Ok(Json(data)), 19 | None => { 20 | println!("Failed to fetch market data for token {}", token_name); 21 | Err(StatusCode::BAD_GATEWAY) 22 | } 23 | } 24 | } 25 | 26 | async fn fetch_token_address(token_name: &str) -> Option { 27 | let url = "https://cdn.jsdelivr.net/gh/solana-labs/token-list@main/src/tokens/solana.tokenlist.json"; 28 | let response: Value = reqwest::get(url).await.ok()?.json().await.ok()?; 29 | 30 | let tokens = response.get("tokens")?.as_array()?; 31 | 32 | tokens.iter() 33 | .find(|token| { 34 | token.get("name") 35 | .and_then(|name| name.as_str()) 36 | .map(|name_str| name_str.eq_ignore_ascii_case(token_name)) 37 | .unwrap_or(false) 38 | }) 39 | .and_then(|token| { 40 | let address = token.get("address")?.as_str()?.to_string(); 41 | println!("Found token {} with address {}", token_name, address); 42 | Some(address) 43 | }) 44 | } 45 | 46 | async fn fetch_market_data(contract_address: &str) -> Option { 47 | let url = format!("https://api.coingecko.com/api/v3/coins/solana/contract/{}", contract_address); 48 | let response = reqwest::get(&url).await.ok()?.json::().await.ok(); 49 | 50 | println!("Response from CoinGecko for {}: {:?}", contract_address, &response); 51 | 52 | response 53 | } -------------------------------------------------------------------------------- /server/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // Add function to generate API key 2 | pub fn generate_api_key() -> String { 3 | use rand::{thread_rng, Rng}; 4 | const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 5 | let mut rng = thread_rng(); 6 | let key: String = (0..32) 7 | .map(|_| { 8 | let idx = rng.gen_range(0..CHARSET.len()); 9 | CHARSET[idx] as char 10 | }) 11 | .collect(); 12 | key 13 | } -------------------------------------------------------------------------------- /server/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Load environment variables from .env 4 | set -a 5 | source .env 6 | set +a 7 | 8 | # Debug: Check if variables were loaded 9 | echo "TEST_API_KEY: $TEST_API_KEY" 10 | echo "TEST_HELIUS_API_KEY: $TEST_HELIUS_API_KEY" 11 | echo "TEST_OPENAI_API_KEY: $TEST_OPENAI_API_KEY" 12 | 13 | BASE_URL="http://localhost:5500" 14 | API_KEY="$TEST_API_KEY" 15 | TEST_PUBKEY="9unenHYtwUowNkWdZmSYTwzGxxdzKVJh7npk6W6uqRF3" 16 | 17 | check_response() { 18 | local response="$1" 19 | local status="$2" 20 | local expected="$3" 21 | 22 | echo "Response: $response" 23 | 24 | if [[ "$status" -eq "$expected" ]]; then 25 | echo "✅ Test passed" 26 | else 27 | echo "❌ Test failed (Expected: $expected, Got: $status)" 28 | exit 1 29 | fi 30 | } 31 | 32 | # Generate a unique transaction signature using timestamp and random string 33 | generate_signature() { 34 | local timestamp=$(date +%s) 35 | local random_string=$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32) 36 | echo "${timestamp}${random_string}" 37 | } 38 | 39 | # Test health endpoint 40 | echo "Testing /health..." 41 | curl -X GET "$BASE_URL/health" 42 | echo 43 | 44 | # Test create user 45 | echo "Creating user..." 46 | response=$(curl -s -w "\n%{http_code}" -H "Content-Type: application/json" -X POST -d "{ 47 | \"pubkey\": \"$TEST_PUBKEY\" 48 | }" "$BASE_URL/users") 49 | status=$(echo "$response" | tail -n1) 50 | if [[ "$status" != "201" && "$status" != "200" ]]; then 51 | echo "❌ Test failed (Expected: 201 or 200, Got: $status)" 52 | exit 1 53 | fi 54 | echo "✅ Test passed" 55 | 56 | # Test get users 57 | echo "Fetching users..." 58 | curl -X GET "$BASE_URL/users" 59 | echo 60 | 61 | # Test get packages 62 | echo "Fetching packages..." 63 | response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/packages/$TEST_PUBKEY") 64 | status=$(echo "$response" | tail -n1) 65 | check_response "$response" "$status" 200 66 | 67 | # # Test verify transaction with real signature 68 | # echo "Verifying transaction..." 69 | # response=$(curl -s -w "\n%{http_code}" -H "Content-Type: application/json" -X POST -d '{ 70 | # "package_id": 1, 71 | # "signature": "3dMe8itJ7Rbc3E42aFMDWyrJJPv4dHUpXgoqWFKhHNKB4mbd2veFp8LMEdfzEAoYS9XbXTTQSpQszwSpmY33q9Ky", 72 | # "user_pubkey": "9unenHYtwUowNkWdZmSYTwzGxxdzKVJh7npk6W6uqRF3" 73 | # }' "$BASE_URL/packages/verify") 74 | # status=$(echo "$response" | tail -n1) 75 | # check_response "$response" "$status" 200 76 | 77 | # Test get user usage 78 | echo "Fetching user usage..." 79 | response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/users/9unenHYtwUowNkWdZmSYTwzGxxdzKVJh7npk6W6uqRF3/usage") 80 | status=$(echo "$response" | tail -n1) 81 | check_response "$response" "$status" 200 82 | 83 | # Test chatbot interaction 84 | echo "Chatbot interaction..." 85 | response=$(curl -s -w "\n%{http_code}" -H "Content-Type: application/json" -H "x-api-key: $API_KEY" -X POST -d "{ 86 | \"input_user\": \"What was the trending tokens today?\", 87 | \"address\": \"$TEST_PUBKEY\" 88 | }" "$BASE_URL/chatbot/interact") 89 | status=$(echo "$response" | tail -n1) 90 | check_response "$response" "$status" 200 91 | 92 | # Test user usage after chatbot interaction 93 | echo "Fetching user usage after chatbot interaction..." 94 | response=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/users/9unenHYtwUowNkWdZmSYTwzGxxdzKVJh7npk6W6uqRF3/usage") 95 | status=$(echo "$response" | tail -n1) 96 | check_response "$response" "$status" 200 97 | 98 | echo "All tests passed 🎉" 99 | -------------------------------------------------------------------------------- /swquery/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swquery" 3 | description = "A natural language query for solana wallets" 4 | version = "0.0.2" 5 | edition = "2021" 6 | authors = [ 7 | "Arthur Bretas ", 8 | "Marcelo G Feitoza ", 9 | "Pedro Hagge Baptista ", 10 | "Victor Carvalho ", 11 | ] 12 | repository = "https://github.com/vict0rcarvalh0/swquery" 13 | license = "MIT OR Apache-2.0" 14 | readme = "README.md" 15 | 16 | [lib] 17 | name = "swquery" 18 | 19 | [dependencies] 20 | futures = "0.3.31" 21 | serde = { version = "1.0", features = ["derive"] } 22 | tokio = { version = "1.0", features = ["full"] } 23 | tracing = "0.1" 24 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 25 | axum = { version = "0.7.9" } 26 | serde_json = "1.0" 27 | sqlx = { version = "0.8.2", features = [ 28 | "runtime-tokio-native-tls", 29 | "postgres", 30 | "chrono", 31 | ] } 32 | dotenvy = "0.15" 33 | chrono = { version = "0.4", features = ["serde"] } 34 | tower = { version = "0.5.1", features = ["util"] } 35 | http-body-util = "0.1.0" 36 | hyper-util = { version = "0.1", features = [ 37 | "client", 38 | "http1", 39 | "client-legacy", 40 | ] } 41 | tower-http = { version = "0.6.1", features = ["trace"] } 42 | solana-sdk = "2.0.8" 43 | thiserror = "1.0" 44 | reqwest = { version = "0.11", features = ["json"] } 45 | script = "0.5.0" 46 | wasmtime = "11.0.1" 47 | -------------------------------------------------------------------------------- /swquery/README.md: -------------------------------------------------------------------------------- 1 | # SWQuery SDK 2 | 3 | SWQuery is an SDK designed to streamline interaction with Solana blockchain data using enhanced RPC calls. It leverages Helius RPC and integrates with a custom Agent API to provide powerful querying capabilities for developers building Solana-based applications. 4 | 5 | ## Table of Contents 6 | 7 | - [Overview](#overview) 8 | - [Features](#features) 9 | - [Installation](#installation) 10 | - [Usage](#usage) 11 | - [Examples](#examples) 12 | - [API Reference](#api-reference) 13 | - [Error Handling](#error-handling) 14 | - [Contributing](#contributing) 15 | - [License](#license) 16 | 17 | --- 18 | 19 | ## Overview 20 | 21 | The SWQuery SDK simplifies querying Solana blockchain data by offering high-level methods to interact with Solana's RPC API. It automates fetching and aggregating transaction data, token balances, and account states, making it ideal for developers building analytics, dashboards, and dApps on Solana. 22 | 23 | ## Features 24 | 25 | - **Solana RPC Integration** – Provides easy access to Solana's RPC endpoints via Helius. 26 | - **Agent API** – Translates natural language queries into structured Solana RPC calls. 27 | - **Token and NFT Metadata** – Extracts token metadata for enhanced transaction analysis. 28 | - **Account-Level Token Flow** – Tracks SPL token transfers per account, ensuring accurate balance changes. 29 | - **Error Handling** – Graceful handling of network errors, API failures, and invalid inputs. 30 | 31 | ## Installation 32 | 33 | To integrate the SWQuery SDK into your project: 34 | 35 | ```bash 36 | cargo add swquery 37 | ``` 38 | 39 | Alternatively, add it manually to your `Cargo.toml`: 40 | 41 | ```toml 42 | [dependencies] 43 | swquery = "0.0.2" 44 | ``` 45 | 46 | Then, run: 47 | 48 | ```bash 49 | cargo build 50 | ``` 51 | 52 | ## Usage 53 | 54 | To begin using the SDK, instantiate the `SWqueryClient` with your API keys: 55 | 56 | ```rust 57 | use swquery::SWqueryClient; 58 | use std::time::Duration; 59 | 60 | #[tokio::main] 61 | async fn main() { 62 | let client = SWqueryClient::new( 63 | "YOUR_AGENT_API_KEY".to_string(), 64 | "YOUR_HELIUS_API_KEY".to_string(), 65 | Some(Duration::from_secs(10)), 66 | None, 67 | ); 68 | 69 | let response = client 70 | .query("Get recent transactions for this wallet", "YourWalletAddress") 71 | .await 72 | .unwrap(); 73 | 74 | println!("Response: {:#?}", response); 75 | } 76 | ``` 77 | 78 | --- 79 | 80 | ## API Reference 81 | 82 | ### SWqueryClient 83 | 84 | - `new(api_key: String, helius_api_key: String, timeout: Option, network: Option) -> Self` 85 | Instantiates a new SWqueryClient with optional timeout and network parameters. 86 | 87 | - `query(input: &str, pubkey: &str) -> Result` 88 | Sends a query to the Agent API and fetches data from Solana. 89 | 90 | --- 91 | 92 | ## Error Handling 93 | 94 | SWQuery SDK returns structured errors using the `SdkError` enum, which includes: 95 | 96 | - `InvalidInput(String)` – Raised when input parameters are missing or invalid. 97 | - `NetworkError(String)` – Triggered by network-related issues. 98 | - `ApiRequestFailed(String)` – Indicates a failure from the Agent or Helius RPC. 99 | - `ParseError(String)` – Raised when parsing responses fails. 100 | 101 | Example: 102 | 103 | ```rust 104 | match client.get_recent_transactions("InvalidAddress", 7).await { 105 | Ok(transactions) => println!("Fetched transactions: {:?}", transactions), 106 | Err(e) => eprintln!("Error: {:?}", e), 107 | } 108 | ``` 109 | 110 | --- 111 | 112 | ## License 113 | 114 | This project is licensed under the [MIT License](LICENSE). 115 | -------------------------------------------------------------------------------- /swquery/src/errors.rs: -------------------------------------------------------------------------------- 1 | use {reqwest, thiserror::Error}; 2 | 3 | #[derive(Debug, Error)] 4 | pub enum SdkError { 5 | #[error("Invalid input: {0}")] 6 | InvalidInput(String), 7 | 8 | #[error("IO error: {0}")] 9 | IoError(#[from] std::io::Error), 10 | 11 | #[error("Parse error: {0}")] 12 | ParseError(String), 13 | 14 | #[error("HTTP error: {0}")] 15 | HttpError(#[from] reqwest::Error), 16 | 17 | #[error("Network error occurred: {0}")] 18 | NetworkError(String), 19 | 20 | #[error("Wallet not found")] 21 | WalletNotFound, 22 | 23 | #[error("Failed to parse the query")] 24 | QueryParsingFailed, 25 | 26 | #[error("API request failed: {0}")] 27 | ApiRequestFailed(String), 28 | 29 | #[error("Unknown error")] 30 | Unknown, 31 | 32 | #[error("Unexpected error: {0}")] 33 | Unexpected(String), 34 | 35 | #[error("Failed to send request")] 36 | RequestFailed, 37 | } 38 | -------------------------------------------------------------------------------- /swquery/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod errors; 3 | pub mod llm; 4 | pub mod models; 5 | pub mod utils; 6 | 7 | pub use client::SWqueryClient; 8 | -------------------------------------------------------------------------------- /swquery/src/llm/mod.rs: -------------------------------------------------------------------------------- 1 | // mod transformer; 2 | 3 | // pub use transformer::{TransformerError, TransformerModel}; 4 | -------------------------------------------------------------------------------- /swquery/src/llm/transformer.rs: -------------------------------------------------------------------------------- 1 | // use { 2 | // reqwest, 3 | // serde::Deserialize, 4 | // serde_json::{self, Value}, 5 | // std::collections::HashMap, 6 | // thiserror::Error, 7 | // }; 8 | 9 | // /// Tipo de entrada e saída do modelo. 10 | // type Input = Value; 11 | // type Output = String; 12 | 13 | // /// Erros que podem ocorrer durante o processo de transformação. 14 | // #[derive(Debug, Error)] 15 | // pub enum TransformerError { 16 | // /// Erro de parsing do JSON de entrada. 17 | // #[error("Failed to parse input as JSON: {0}")] 18 | // ParseError(#[from] serde_json::Error), 19 | 20 | // /// Erro se não encontrar o campo "inputUser" no JSON. 21 | // #[error("Inference error: {0}")] 22 | // InferenceError(String), 23 | // } 24 | 25 | // /// `TransformerModel` representa o modelo que extrai o "inputUser" do JSON 26 | // /// recebido. 27 | // pub struct TransformerModel { 28 | // pub name: String, 29 | // pub version: String, 30 | // pub description: String, 31 | // pub input: Vec, 32 | // pub output: Vec, 33 | // pub metadata: HashMap, 34 | // } 35 | 36 | // impl TransformerModel { 37 | // /// Cria uma nova instância do `TransformerModel`. 38 | // pub fn new( 39 | // name: String, 40 | // version: String, 41 | // description: String, 42 | // input: Vec, 43 | // output: Vec, 44 | // metadata: HashMap, 45 | // ) -> Self { 46 | // Self { 47 | // name, 48 | // version, 49 | // description, 50 | // input, 51 | // output, 52 | // metadata, 53 | // } 54 | // } 55 | 56 | // /// Extrai o campo "inputUser" do JSON. Se não existir, retorna um 57 | // /// `InferenceError`. 58 | // fn run(&self, input: Input) -> Result { 59 | // if let Some(user_input) = input.get("inputUser") { 60 | // if let Some(user_str) = user_input.as_str() { 61 | // Ok(user_str.to_string()) 62 | // } else { 63 | // Err(TransformerError::InferenceError( 64 | // "'inputUser' field is not a string.".to_string(), 65 | // )) 66 | // } 67 | // } else { 68 | // Err(TransformerError::InferenceError( 69 | // "No 'inputUser' field found in input JSON.".to_string(), 70 | // )) 71 | // } 72 | // } 73 | 74 | // /// `run_inference` recebe uma string JSON, parseia para `Input`, chama 75 | // /// `run` para extrair o prompt do usuário e retorna esse prompt como 76 | // /// `String`. 77 | // pub fn run_inference(&self, input: &str) -> Result { 78 | // let input_val: Input = serde_json::from_str(input)?; 79 | // let output_val = self.run(input_val)?; 80 | // Ok(output_val) 81 | // } 82 | 83 | // /// Esta função recebe o prompt extraído e envia via POST para a rota do LLM 84 | // /// Espera que o LLM retorne uma string (por exemplo, a query gerada). 85 | // pub async fn send_prompt_to_llm( 86 | // &self, 87 | // prompt: &str, 88 | // ) -> Result> { 89 | // let client = reqwest::Client::new(); 90 | // let resp = client 91 | // .post("http://localhost:5500/agent/generate_query") 92 | // .json(&serde_json::json!({"inputUser": prompt})) 93 | // .send() 94 | // .await?; 95 | 96 | // match resp.status() { 97 | // status if status.is_success() => { 98 | // let response: QueryResponse = resp.json().await?; 99 | // match response.result.status.as_str() { 100 | // "error" => Err(format!("LLM Error: {}", response.result.response).into()), 101 | // "success" => Ok(response), 102 | // _ => Err("Unknown response status".into()), 103 | // } 104 | // } 105 | // status => Err(format!("Server returned status: {}", status).into()), 106 | // } 107 | // } 108 | // } 109 | 110 | // #[derive(Deserialize)] 111 | // pub struct QueryResponse { 112 | // result: QueryResult, 113 | // pub tokens: i64, 114 | // } 115 | 116 | // #[derive(Deserialize, Debug)] 117 | // struct QueryResult { 118 | // pub response: String, 119 | // pub status: String, 120 | // } 121 | -------------------------------------------------------------------------------- /swquery/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod transactions; 2 | pub mod trending; 3 | 4 | pub use transactions::*; 5 | pub use trending::*; 6 | -------------------------------------------------------------------------------- /swquery/src/models/trending.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub struct GetTrendingTokensResponse { 7 | #[serde(rename = "items")] 8 | pub tokens: Vec, 9 | pub sort_by: Option, 10 | pub sort_direction: Option, 11 | } 12 | 13 | #[derive(Serialize, Deserialize, Debug)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct TokenData { 16 | pub chain_id: String, 17 | pub address: String, 18 | pub name: String, 19 | pub symbol: String, 20 | pub logo_url: String, 21 | pub market_cap: f64, 22 | pub volume: f64, 23 | pub volume_change: f64, 24 | pub price: f64, 25 | pub price_change: f64, 26 | } 27 | -------------------------------------------------------------------------------- /swquery/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_URL="http://localhost:5500" 4 | 5 | # Test health endpoint 6 | echo "Testing /health..." 7 | curl -X GET "$BASE_URL/health" 8 | echo 9 | 10 | # Test create user 11 | echo "Creating user..." 12 | curl -X POST -H "Content-Type: application/json" -d '{ 13 | "pubkey": "GtJHNhKQnnJZQTHq2Vh49HpR4yKKJmUonVYbLeS1RPs8" 14 | }' "$BASE_URL/users" 15 | echo 16 | 17 | # Test get users 18 | echo "Fetching users..." 19 | curl -X GET "$BASE_URL/users" 20 | echo 21 | 22 | # Test buy credits 23 | echo "Buying credits..." 24 | curl -X POST -H "Content-Type: application/json" -d '{ 25 | "user_pubkey": "GtJHNhKQnnJZQTHq2Vh49HpR4yKKJmUonVYbLeS1RPs8", 26 | "amount": 5000 27 | }' "$BASE_URL/credits/buy" 28 | echo 29 | 30 | # Test chatbot interaction 31 | echo "Chatbot interaction..." 32 | curl -X POST \ 33 | -H "Content-Type: application/json" \ 34 | -H "x-api-key: WDAO4Z1Z503DWJH7060GIYGR0TWIIPBM" \ 35 | -d '{ 36 | "input_user": "What are the trending tokens on last 24hrs?", 37 | "address": "GtJHNhKQnnJZQTHq2Vh49HpR4yKKJmUonVYbLeS1RPs8", 38 | "helius_key": "bf387173-8363-45e8-a0e0-86985e6d0219", 39 | "openai_key": "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" 40 | }' "$BASE_URL/chatbot/interact" 41 | echo 42 | --------------------------------------------------------------------------------