diff --git a/.gitignore b/.gitignore index 061ef89..8b319ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,179 @@ venv/ __pycache__/ -main.py \ No newline at end of file +main.py + +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6e2f993 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 0xtaodev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a029083..d2eaec0 100644 --- a/README.md +++ b/README.md @@ -1 +1,189 @@ -### JUPITER SDK PYTHON \ No newline at end of file +
+

🐍 JUPITER PYTHON SDK 🪐

+ +
+ +--- + +

+ + +
+ + +
+ + +
+

+ +# 📖 Introduction +**Jupiter Python SDK** is a Python library that allows you to use most of **[Jupiter](https://jup.ag/) features**.
+It enables executing swaps, limit orders, DCA, swap pairs, tokens prices, fetching wallet infos, stats, data and more!
+This library is using packages like: [solana-py](https://github.com/michaelhly/solana-py), [solders](https://github.com/kevinheavey/solders), [anchorpy](https://github.com/kevinheavey/anchorpy).
+There is documentation inside each function, however, you can access to the [official Jupiter API](https://docs.jup.ag/docs) for more information. + +# ⚠️ Disclaimer +**Please note that I'm not responsible for any loss of funds, damages, or other libailities resulting from the use of this software or any associated services.
+This tool is provided for educational purposes only and should not be used as financial advice, it is still in expiremental phase so use it at your own risk.** + +# ✨ Quickstart + +### 🛠️ Installation + +```sh +pip install jupiter-python-sdk +``` + +### 📃 General Usage +**Providing the private key and RPC client is not mandatory if you only intend to execute functions for retrieving data.
+Otherwise, this is required, for instance, to open a DCA account or to close one.** + +**You can set custom URLs for any self-hosted Jupiter APIs. Like the [V6 Swap API](https://station.jup.ag/docs/apis/self-hosted) or [QuickNode's Metis API](https://marketplace.quicknode.com/add-on/metis-jupiter-v6-swap-api).** + +If you encounter ```ImportError: cannot import name 'sync_native' from 'spl.token.instructions``` error when trying to import Jupiter, Jupiter_DCA from jupiter_python_sdk.jupiter, follow these steps: +1. Go to https://github.com/michaelhly/solana-py/tree/master/src/spl/token and download ```instructions.py``` +2. In your packages folder, replace ```spl/token/instructions.py``` with the one you just downloaded. + +### Here is a code snippet on how to use the SDK +```py +import base58 +import base64 +import json + +from solders import message +from solders.pubkey import Pubkey +from solders.keypair import Keypair +from solders.transaction import VersionedTransaction + +from solana.rpc.types import TxOpts +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Processed + +from jupiter_python_sdk.jupiter import Jupiter, Jupiter_DCA + + +private_key = Keypair.from_bytes(base58.b58decode(os.getenv("PRIVATE-KEY"))) # Replace PRIVATE-KEY with your private key as string +async_client = AsyncClient("SOLANA-RPC-ENDPOINT-URL") # Replace SOLANA-RPC-ENDPOINT-URL with your Solana RPC Endpoint URL +jupiter = Jupiter( + async_client=async_client, + keypair=private_key, + quote_api_url="https://quote-api.jup.ag/v6/quote?", + swap_api_url="https://quote-api.jup.ag/v6/swap", + open_order_api_url="https://jup.ag/api/limit/v1/createOrder", + cancel_orders_api_url="https://jup.ag/api/limit/v1/cancelOrders", + query_open_orders_api_url="https://jup.ag/api/limit/v1/openOrders?wallet=", + query_order_history_api_url="https://jup.ag/api/limit/v1/orderHistory", + query_trade_history_api_url="https://jup.ag/api/limit/v1/tradeHistory" +) + + +""" +EXECUTE A SWAP +""" +transaction_data = await jupiter.swap( + input_mint="So11111111111111111111111111111111111111112", + output_mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + amount=5_000_000, + slippage_bps=1, +) +# Returns str: serialized transactions to execute the swap. + +raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data)) +signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message)) +signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature]) +opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) +result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) +transaction_id = json.loads(result.to_json())['result'] +print(f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}") + + +""" +OPEN LIMIT ORDER +""" +transaction_data = await jupiter.open_order( + input_mint=So11111111111111111111111111111111111111112", + output_mint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + in_amount=5_000_000, + out_amount=100_000, +) +# Returns dict: {'transaction_data': serialized transactions to create the limit order, 'signature2': signature of the account that will be opened} + +raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data['transaction_data'])) +signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message)) +signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature, transaction_data['signature2']]) +opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) +result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) +transaction_id = json.loads(result.to_json())['result'] +print(f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}") + + +""" +CREATE DCA ACCOUNT +""" +create_dca_account = await jupiter.dca.create_dca( + input_mint=Pubkey.from_string("So11111111111111111111111111111111111111112"), + output_mint=Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), + total_in_amount=5_000_000, + in_amount_per_cycle=100_000, + cycle_frequency=60, + min_out_amount_per_cycle=0, + max_out_amount_per_cycle=0, + start=0 +) +# Returns DCA Account Pubkey and transaction hash. + + +""" +CLOSE DCA ACCOUNT +""" +close_dca_account = await jupiter.dca.close_dca( + dca_pubkey=Pubkey.from_string("45iYdjmFUHSJCQHnNpWNFF9AjvzRcsQUP9FDBvJCiNS1") +) +# Returns transaction hash. +``` + +### 📜 All available features +```py +- quote +- swap +- open_order +- cancel_orders +- create_dca +- close_dca +- fetch_user_dca_accounts +- fetch_dca_account_fills_history +- get_available_dca_tokens +- fetch_dca_data +- query_open_orders +- query_orders_history +- query_trades_history +- get_jupiter_stats +- get_token_price +- get_indexed_route_map +- get_tokens_list +- get_all_tickers +- get_all_swap_pairs +- get_swap_pairs +- get_token_stats_by_date +- program_id_to_label +``` + +# 📝 TO-DO +- [ ] Bridge 🌉 +- [ ] Perpetual 💸 +- [ ] Price API +- [ ] Wallet Transactions History + +# 🤝 Contributions +If you are interesting in contributing, fork the repository and submit a pull request in order to merge your improvements into the main repository.
+Contact me for any inquiry, I will reach you as soon as possible.
+[![Discord](https://img.shields.io/badge/Discord-%237289DA.svg?logo=discord&logoColor=white)](https://discord.gg/QxwPGcXDp7) +[![Twitter](https://img.shields.io/badge/Twitter-%231DA1F2.svg?logo=Twitter&logoColor=white)](https://twitter.com/_TaoDev_) + +# 👑 Donations +This project doesn't include platform fees. If you find value in it and would like to support its development, your donations are greatly appreciated.
+**SOLANA ADDRESS** +```sh +AyWu89SjZBW1MzkxiREmgtyMKxSkS1zVy8Uo23RyLphX +``` diff --git a/images/jupiter-python-sdk-banner.png b/images/jupiter-python-sdk-banner.png new file mode 100644 index 0000000..0bf8f62 Binary files /dev/null and b/images/jupiter-python-sdk-banner.png differ diff --git a/images/jupiter-python-sdk-logo-transparent.png b/images/jupiter-python-sdk-logo-transparent.png new file mode 100644 index 0000000..ee0ee1d Binary files /dev/null and b/images/jupiter-python-sdk-logo-transparent.png differ diff --git a/images/jupiter-python-sdk-logo.jpg b/images/jupiter-python-sdk-logo.jpg new file mode 100644 index 0000000..dd156e0 Binary files /dev/null and b/images/jupiter-python-sdk-logo.jpg differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9c91e35 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +base58 +solders==0.18.1 +solana==0.30.2 +httpx +anchorpy==0.18.0 +jupiter-python-sdk diff --git a/src/LICENSE.txt b/src/LICENSE.txt new file mode 100644 index 0000000..6e2f993 --- /dev/null +++ b/src/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 0xtaodev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..c29cce5 --- /dev/null +++ b/src/README.md @@ -0,0 +1,187 @@ +
+

🐍 JUPITER PYTHON SDK 🪐

+ +
+ +--- + +

+ + +
+ + +
+ + +
+

+ +# 📖 Introduction +**Jupiter Python SDK** is a Python library that allows you to use most of **[Jupiter](https://jup.ag/) features**.
+It enables executing swaps, limit orders, DCA, swap pairs, tokens prices, fetching wallet infos, stats, data and more!
+This library is using packages like: [solana-py](https://github.com/michaelhly/solana-py), [solders](https://github.com/kevinheavey/solders), [anchorpy](https://github.com/kevinheavey/anchorpy).
+There is documentation inside each function, however, you can access to the [official Jupiter API](https://docs.jup.ag/docs) for more information. + +# ⚠️ Disclaimer +**Please note that I'm not responsible for any loss of funds, damages, or other libailities resulting from the use of this software or any associated services.
+This tool is provided for educational purposes only and should not be used as financial advice, it is still in expiremental phase so use it at your own risk.** + +# ✨ Quickstart + +### 🛠️ Installation + +```sh +pip install jupiter-python-sdk +``` + +### 📃 General Usage +**Providing the private key and RPC client is not mandatory if you only intend to execute functions for retrieving data.
+Otherwise, this is required, for instance, to open a DCA account or to close one.** + +**You can set custom URLs for any self-hosted Jupiter APIs. Like the [V6 Swap API](https://station.jup.ag/docs/apis/self-hosted) or [QuickNode's Metis API](https://marketplace.quicknode.com/add-on/metis-jupiter-v6-swap-api).** + +If you encounter ```ImportError: cannot import name 'sync_native' from 'spl.token.instructions``` error when trying to import Jupiter, Jupiter_DCA from jupiter_python_sdk.jupiter, follow these steps: +1. Go to https://github.com/michaelhly/solana-py/tree/master/src/spl/token and download ```instructions.py``` +2. In your packages folder, replace ```spl/token/instructions.py``` with the one you just downloaded. + +### Here is a code snippet on how to use the SDK +```py +import base58 +import base64 +import json + +from solders import message +from solders.pubkey import Pubkey +from solders.keypair import Keypair +from solders.transaction import VersionedTransaction + +from solana.rpc.types import TxOpts +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Processed + +from jupiter_python_sdk.jupiter import Jupiter, Jupiter_DCA + + +private_key = Keypair.from_bytes(base58.b58decode(os.getenv("PRIVATE-KEY"))) # Replace PRIVATE-KEY with your private key as string +async_client = AsyncClient("SOLANA-RPC-ENDPOINT-URL") # Replace SOLANA-RPC-ENDPOINT-URL with your Solana RPC Endpoint URL +jupiter = Jupiter( + async_client=async_client, + keypair=private_key, + quote_api_url="https://quote-api.jup.ag/v6/quote?", + swap_api_url="https://quote-api.jup.ag/v6/swap", + open_order_api_url="https://jup.ag/api/limit/v1/createOrder", + cancel_orders_api_url="https://jup.ag/api/limit/v1/cancelOrders", + query_open_orders_api_url="https://jup.ag/api/limit/v1/openOrders?wallet=", + query_order_history_api_url="https://jup.ag/api/limit/v1/orderHistory", + query_trade_history_api_url="https://jup.ag/api/limit/v1/tradeHistory" +) + + +""" +EXECUTE A SWAP +""" +transaction_data = await jupiter.swap( + input_mint="So11111111111111111111111111111111111111112", + output_mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + amount=5_000_000, + slippage_bps=1, +) +# Returns str: serialized transactions to execute the swap. + +raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data)) +signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message)) +signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature]) +opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) +result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) +transaction_id = json.loads(result.to_json())['result'] +print(f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}") + + +""" +OPEN LIMIT ORDER +""" +transaction_data = await jupiter.open_order( + input_mint=So11111111111111111111111111111111111111112", + output_mint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + in_amount=5_000_000, + out_amount=100_000, +) +# Returns dict: {'transaction_data': serialized transactions to create the limit order, 'signature2': signature of the account that will be opened} + +raw_transaction = VersionedTransaction.from_bytes(base64.b64decode(transaction_data['transaction_data'])) +signature = private_key.sign_message(message.to_bytes_versioned(raw_transaction.message)) +signed_txn = VersionedTransaction.populate(raw_transaction.message, [signature, transaction_data['signature2']]) +opts = TxOpts(skip_preflight=False, preflight_commitment=Processed) +result = await async_client.send_raw_transaction(txn=bytes(signed_txn), opts=opts) +transaction_id = json.loads(result.to_json())['result'] +print(f"Transaction sent: https://explorer.solana.com/tx/{transaction_id}") + + +""" +CREATE DCA ACCOUNT +""" +create_dca_account = await jupiter.dca.create_dca( + input_mint=Pubkey.from_string("So11111111111111111111111111111111111111112"), + output_mint=Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), + total_in_amount=5_000_000, + in_amount_per_cycle=100_000, + cycle_frequency=60, + min_out_amount_per_cycle=0, + max_out_amount_per_cycle=0, + start=0 +) +# Returns DCA Account Pubkey and transaction hash. + + +""" +CLOSE DCA ACCOUNT +""" +close_dca_account = await jupiter.dca.close_dca( + dca_pubkey=Pubkey.from_string("45iYdjmFUHSJCQHnNpWNFF9AjvzRcsQUP9FDBvJCiNS1") +) +# Returns transaction hash. +``` + +### 📜 All available features +```py +- quote +- swap +- open_order +- cancel_orders +- create_dca +- close_dca +- fetch_user_dca_accounts +- fetch_dca_account_fills_history +- get_available_dca_tokens +- fetch_dca_data +- query_open_orders +- query_orders_history +- query_trades_history +- get_jupiter_stats +- get_token_price +- get_indexed_route_map +- get_tokens_list +- get_all_tickers +- get_all_swap_pairs +- get_swap_pairs +- get_token_stats_by_date +- program_id_to_label +``` + +# 📝 TO-DO +- [ ] Bridge 🌉 +- [ ] Perpetual 💸 + +# 🤝 Contributions +If you are interesting in contributing, fork the repository and submit a pull request in order to merge your improvements into the main repository.
+Contact me for any inquiry, I will reach you as soon as possible.
+[![Discord](https://img.shields.io/badge/Discord-%237289DA.svg?logo=discord&logoColor=white)](https://discord.gg/QxwPGcXDp7) +[![Twitter](https://img.shields.io/badge/Twitter-%231DA1F2.svg?logo=Twitter&logoColor=white)](https://twitter.com/_TaoDev_) + +# 👑 Donations +This project doesn't include platform fees. If you find value in it and would like to support its development, your donations are greatly appreciated.
+**SOLANA ADDRESS** +```sh +AyWu89SjZBW1MzkxiREmgtyMKxSkS1zVy8Uo23RyLphX +``` diff --git a/src/jupiter_python_sdk/__init__.py b/src/jupiter_python_sdk/__init__.py new file mode 100644 index 0000000..f987a05 --- /dev/null +++ b/src/jupiter_python_sdk/__init__.py @@ -0,0 +1 @@ +"""Classes to use Jupiter features.""" diff --git a/jupiter.py b/src/jupiter_python_sdk/jupiter.py similarity index 93% rename from jupiter.py rename to src/jupiter_python_sdk/jupiter.py index 9925804..67fbce6 100644 --- a/jupiter.py +++ b/src/jupiter_python_sdk/jupiter.py @@ -370,7 +370,7 @@ async def create_dca( async def close_dca( self, dca_pubkey: Pubkey, - ): + ) -> str: """Close DCA Account with signing and sending the transaction. Args: @@ -540,30 +540,46 @@ class Jupiter(): def __init__( self, async_client: AsyncClient, - keypair: Keypair + keypair: Keypair, + quote_api_url: str="https://quote-api.jup.ag/v6/quote?", + swap_api_url: str="https://quote-api.jup.ag/v6/swap", + open_order_api_url: str="https://jup.ag/api/limit/v1/createOrder", + cancel_orders_api_url: str="https://jup.ag/api/limit/v1/cancelOrders", + query_open_orders_api_url: str="https://jup.ag/api/limit/v1/openOrders?wallet=", + query_order_history_api_url: str="https://jup.ag/api/limit/v1/orderHistory", + query_trade_history_api_url: str="https://jup.ag/api/limit/v1/tradeHistory", ): self.dca = Jupiter_DCA(async_client, keypair) self.rpc = async_client self.keypair = keypair + + self.ENDPOINT_APIS_URL["QUOTE"] = quote_api_url + self.ENDPOINT_APIS_URL["SWAP"] = swap_api_url + self.ENDPOINT_APIS_URL["OPEN_ORDER"] = open_order_api_url + self.ENDPOINT_APIS_URL["CANCEL_ORDERS"] = cancel_orders_api_url + self.ENDPOINT_APIS_URL["QUERY_OPEN_ORDERS"] = query_open_orders_api_url + self.ENDPOINT_APIS_URL["QUERY_ORDER_HISTORY"] = query_order_history_api_url + self.ENDPOINT_APIS_URL["QUERY_TRADE_HISTORY"] = query_trade_history_api_url async def quote( self, - input_mint: Pubkey, - output_mint: Pubkey, + input_mint: str, + output_mint: str, amount: int, slippage_bps: int=None, swap_mode: str="ExactIn", only_direct_routes: bool=False, as_legacy_transaction: bool=False, exclude_dexes: list=None, - max_accounts: int=None + max_accounts: int=None, + platform_fee_bps: int=None ) -> dict: """Get the best swap route for a token trade pair sorted by largest output token amount from https://quote-api.jup.ag/v6/quote Args: Required: - ``input_mint (Pubkey)``: Input token mint Pubkey\n - ``output_mint (Pubkey)``: Output token mint Pubkey\n + ``input_mint (str)``: Input token mint address\n + ``output_mint (str)``: Output token mint address\n ``amount (int)``: The API takes in amount in integer and you have to factor in the decimals for each token by looking up the decimals for that token. For example, USDC has 6 decimals and 1 USDC is 1000000 in integer when passing it in into the API.\n Optionals: ``slippage_bps (int)``: The slippage % in BPS. If the output token amount exceeds the slippage then the swap transaction will fail.\n @@ -571,7 +587,8 @@ async def quote( ``only_direct_routes (bool)``: Default is False. Direct Routes limits Jupiter routing to single hop routes only.\n ``as_legacy_transaction (bool)``: Default is False. Instead of using versioned transaction, this will use the legacy transaction.\n ``exclude_dexes (list)``: Default is that all DEXes are included. You can pass in the DEXes that you want to exclude in a list. For example, ['Aldrin','Saber'].\n - ``max_accounts (int)``: Find a route given a maximum number of accounts involved, this might dangerously limit routing ending up giving a bad price. The max is an estimation and not the exact count. + ``max_accounts (int)``: Find a route given a maximum number of accounts involved, this might dangerously limit routing ending up giving a bad price. The max is an estimation and not the exact count.\n + ``platform_fee_bps (int)``: If you want to charge the user a fee, you can specify the fee in BPS. Fee % is taken out of the output token. Returns: ``dict``: returns best swap route @@ -582,8 +599,8 @@ async def quote( >>> private_key_string = "tSg8j3pWQyx3TC2fpN9Ud1bS0NoAK0Pa3TC2fpNd1bS0NoASg83TC2fpN9Ud1bS0NoAK0P" >>> private_key = Keypair.from_bytes(base58.b58decode(private_key_string)) >>> jupiter = Jupiter(async_client, private_key) - >>> input_mint = Pubkey.from_string("So11111111111111111111111111111111111111112") - >>> output_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + >>> input_mint = "So11111111111111111111111111111111111111112" + >>> output_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" >>> amount = 5_000_000 >>> quote = await jupiter.quote(input_mint, output_mint, amount) { @@ -601,45 +618,58 @@ async def quote( 'timeTaken': 0.069434356} """ - quote_url = self.ENDPOINT_APIS_URL['QUOTE'] + "inputMint=" + input_mint.__str__() + "&outputMint=" + output_mint.__str__() + "&amount=" + str(amount) + "&swapMode=" + swap_mode + "&onlyDirectRoutes=" + str(only_direct_routes).lower() + "&asLegacyTransaction=" + str(as_legacy_transaction).lower() + quote_url = self.ENDPOINT_APIS_URL['QUOTE'] + "inputMint=" + input_mint + "&outputMint=" + output_mint + "&amount=" + str(amount) + "&swapMode=" + swap_mode + "&onlyDirectRoutes=" + str(only_direct_routes).lower() + "&asLegacyTransaction=" + str(as_legacy_transaction).lower() if slippage_bps: quote_url += "&slippageBps=" + str(slippage_bps) if exclude_dexes: - quote_url += "&excludeDexes=" + ','.join(exclude_dexes).lower() + quote_url += "&excludeDexes=" + ','.join(exclude_dexes) if max_accounts: quote_url += "&maxAccounts=" + str(max_accounts) - + if platform_fee_bps: + quote_url += "&plateformFeeBps=" + platform_fee_bps + quote_response = httpx.get(url=quote_url).json() - return quote_response + try: + quote_response['routePlan'] + return quote_response + except: + raise Exception(quote_response['error']) async def swap( self, - input_mint: Pubkey, - output_mint: Pubkey, + input_mint: str, + output_mint: str, amount: int=0, + quoteResponse: str=None, wrap_unwrap_sol: bool=True, - slippage_bps: int=0, + slippage_bps: int=1, swap_mode: str="ExactIn", + prioritization_fee_lamports: int=None, only_direct_routes: bool=False, as_legacy_transaction: bool=False, exclude_dexes: list=None, - max_accounts: int=None + max_accounts: int=None, + platform_fee_bps: int=None, + dynamic_compute_limit: bool=False ) -> str: """Perform a swap. Args: Required: - ``input_mint (Pubkey)``: Input token mint Pubkey\n - ``output_mint (Pubkey)``: Output token mint Pubkey\n + ``input_mint (str)``: Input token mint str\n + ``output_mint (str)``: Output token mint str\n ``amount (int)``: The API takes in amount in integer and you have to factor in the decimals for each token by looking up the decimals for that token. For example, USDC has 6 decimals and 1 USDC is 1000000 in integer when passing it in into the API.\n Optionals: + ``prioritizationFeeLamports (int)``: If transactions are expiring without confirmation on-chain, this might mean that you have to pay additional fees to prioritize your transaction. To do so, you can set the prioritizationFeeLamports parameter.\n ``wrap_unwrap_sol (bool)``: Auto wrap and unwrap SOL. Default is True.\n ``slippage_bps (int)``: The slippage % in BPS. If the output token amount exceeds the slippage then the swap transaction will fail.\n ``swap_mode (str)``: (ExactIn or ExactOut) Defaults to ExactIn. ExactOut is for supporting use cases where you need an exact token amount, like payments. In this case the slippage is on the input token.\n ``only_direct_routes (bool)``: Default is False. Direct Routes limits Jupiter routing to single hop routes only.\n ``as_legacy_transaction (bool)``: Default is False. Instead of using versioned transaction, this will use the legacy transaction.\n ``exclude_dexes (list)``: Default is that all DEXes are included. You can pass in the DEXes that you want to exclude in a list. For example, ['Aldrin','Saber'].\n - ``max_accounts (int)``: Find a route given a maximum number of accounts involved, this might dangerously limit routing ending up giving a bad price. The max is an estimation and not the exact count. + ``max_accounts (int)``: Find a route given a maximum number of accounts involved, this might dangerously limit routing ending up giving a bad price. The max is an estimation and not the exact count.\n + ``platform_fee_bps (int)``: If you want to charge the user a fee, you can specify the fee in BPS. Fee % is taken out of the output token.\n + ``dynamic_compute_limit (bool)``: Enable dynamic compute limit for the transaction. Default is False. Returns: ``str``: returns serialized transactions to perform the swap from https://quote-api.jup.ag/v6/swap @@ -650,36 +680,41 @@ async def swap( >>> private_key_string = "tSg8j3pWQyx3TC2fpN9Ud1bS0NoAK0Pa3TC2fpNd1bS0NoASg83TC2fpN9Ud1bS0NoAK0P" >>> private_key = Keypair.from_bytes(base58.b58decode(private_key_string)) >>> jupiter = Jupiter(async_client, private_key) - >>> input_mint = Pubkey.from_string("So11111111111111111111111111111111111111112") - >>> output_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + >>> input_mint = "So11111111111111111111111111111111111111112" + >>> output_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" >>> amount = 5_000_000 >>> transaction_data = await jupiter.swap(user_public_key, input_mint, output_mint, amount) AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAJDpQzg6Gwmq0Gtgp4+LWUVz0yQOAuHGNJAGTs0dcqEMVCoh2aSWdVMvcatcojrWtwXATiOw7/o5hE7NFuy3p8vgLfsLhf7Ff9NofcPgIyAbMytm5ggTyKwmR+JqgXUXARVfefILshj4ZhFSjUfRpiSI47mVNFUq9v5NOOCWSEZJZM/GHGfBesEb9blQsf7DnKodziY279S/OPkZf0/OalnPEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAABHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E48Gm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAAQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpT0tsDkEI/SpqJHjq4KzFnbIbtO31EcFiz2AtHgwJAfuMlyWPTiSJ8bs9ECkUjg2DC1oTmdr/EIQEjnvY2+n4WbQ/+if11/ZKdMCbHylYed5LCas238ndUUsyGqezjOXoxvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWHmraeknnR8/memFAZWZHeDMQG7C5ZFLxolWUniPl6SYgcGAAUCwFwVAAYACQNIDQAAAAAAAAsGAAMACAUJAQEFAgADDAIAAAAgTgAAAAAAAAkBAwERBx8JCgADAQIECA0HBwwHGREBAhUOFxMWDxIQFAoYCQcHJMEgmzNB1pyBBwEAAAATZAABIE4AAAAAAACHBQAAAAAAADIAAAkDAwAAAQkB1rO1s+JVEuIRoGsE8f2MlAkFWssCkimIonlHpLV2w4gKBwKRTE0SjIeLSwIICg== """ - quoteResponse = await self.quote( - input_mint=input_mint, - output_mint=output_mint, - amount=amount, - slippage_bps=slippage_bps, - swap_mode=swap_mode, - only_direct_routes=only_direct_routes, - as_legacy_transaction=as_legacy_transaction, - exclude_dexes=exclude_dexes, - max_accounts=max_accounts - ) + if quoteResponse is None: + quoteResponse = await self.quote( + input_mint=input_mint, + output_mint=output_mint, + amount=amount, + slippage_bps=slippage_bps, + swap_mode=swap_mode, + only_direct_routes=only_direct_routes, + as_legacy_transaction=as_legacy_transaction, + exclude_dexes=exclude_dexes, + max_accounts=max_accounts, + platform_fee_bps=platform_fee_bps + ) transaction_parameters = { "quoteResponse": quoteResponse, - "userPublicKey": self.keypair.pubkey(), - "wrapUnwrapSOL": wrap_unwrap_sol + "userPublicKey": self.keypair.pubkey().__str__(), + "wrapAndUnwrapSol": wrap_unwrap_sol, + "dynamicComputeUnitLimit": dynamic_compute_limit } - transaction_data = httpx.post(url=self.ENDPOINT_APIS_URL['SWAP'], json=transaction_parameters).json()['swapTransaction'] - return transaction_data + if prioritization_fee_lamports: + transaction_parameters.update({"prioritizationFeeLamports": prioritization_fee_lamports}) + transaction_data = httpx.post(url=self.ENDPOINT_APIS_URL['SWAP'], json=transaction_parameters).json() + return transaction_data['swapTransaction'] async def open_order( self, - input_mint: Pubkey, - output_mint: Pubkey, + input_mint: str, + output_mint: str, in_amount: int=0, out_amount: int=0, expired_at: int=None @@ -688,8 +723,8 @@ async def open_order( Args: Required: - ``input_mint (Pubkey)``: Input token mint Pubkey\n - ``output_mint (Pubkey)``: Output token mint Pubkey\n + ``input_mint (str)``: Input token mint address\n + ``output_mint (str)``: Output token mint address\n ``in_amount (int)``: The API takes in amount in integer and you have to factor in the decimals for each token by looking up the decimals for that token. For example, USDC has 6 decimals and 1 USDC is 1000000 in integer when passing it in into the API.\n ``out_amount (int)``: The API takes in amount in integer and you have to factor in the decimals for each token by looking up the decimals for that token. For example, USDC has 6 decimals and 1 USDC is 1000000 in integer when passing it in into the API.\n Optionals: @@ -703,11 +738,11 @@ async def open_order( >>> private_key_string = "tSg8j3pWQyx3TC2fpN9Ud1bS0NoAK0Pa3TC2fpNd1bS0NoASg83TC2fpN9Ud1bS0NoAK0P" >>> private_key = Keypair.from_bytes(base58.b58decode(private_key_string)) >>> jupiter = Jupiter(async_client, private_key) - >>> input_mint = Pubkey.from_string("So11111111111111111111111111111111111111112") - >>> output_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + >>> input_mint = "So11111111111111111111111111111111111111112" + >>> output_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" >>> in_amount = 5_000_000 >>> out_amount = 100_000 - >>> transaction_data = await jupiter.swap(user_public_key, input_mint, output_mint, amount) + >>> transaction_data = await jupiter.open_order(user_public_key, input_mint, output_mint, in_amount, out_amount) { 'transaction_data': 'AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgEGC5Qzg6Gwmq0Gtgp4+LWUVz0yQOAuHGNJAGTs0dcqEMVCBvqBKhFi2uRFEKYI4zPatxbdm7DylvnQUby9MexSmeAdsqhWUMQ86Ddz4+7pQFooE6wLglATS/YvzOVUNMOqnyAmC8Ioh9cSvEZniys4XY0OyEvxe39gSdHqlHWJQUPMn4prs0EwIc9JznmgzyMliG5PJTvaFYw75ssASGlB2gMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAImg/TLoYktlelMGKAi4mA0icnTD92092qSZhd3wNABMCv4fVqQvV1OYZ3a3bH43JpI5pIln+UAHnO1fyDJwCfIGm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAAQan1RcZLFxRIYzJTD1K8X9Y2u4Im6H9ROPb2YoAAAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKmr+pT0gdwb1ZeE73qr11921UvCtCB3MMpBcLaiY8+u7QEHDAEABAMCCAIHBgUKCRmFbkqvcJ/1nxAnAAAAAAAAECcAAAAAAAAA', 'signature2': Signature( @@ -928,14 +963,7 @@ async def get_indexed_route_map( ) -> dict: """ Retrieve an indexed route map for all the possible token pairs you can swap between. - - Args: - Optionals: - ``input_mint (str)``: Input token mint Pubkey.\n - ``output_mint (str)``: Output token mint Pubkey.\n - ``cursor (int)``: Pointer to a specific result in the data set.\n - ``skip (int)``: Number of records to skip from the beginning.\n - ``take (int)``: Number of records to retrieve from the current position. + Returns: ``dict``: indexed route map for all the possible token pairs you can swap betwee from https://quote-api.jup.ag/v6/indexed-route-map @@ -1054,7 +1082,7 @@ async def get_token_stats_by_date( @staticmethod async def get_jupiter_stats( unit_of_time: str, - ) -> list: + ) -> dict: """Stats for the unit of time specified. Args: @@ -1075,7 +1103,7 @@ async def get_jupiter_stats( async def get_token_price( input_mint: str, output_mint: str=None, - ) -> list: + ) -> dict: """The Jupiter Price API aims to make getting precise and real-time pricing for all SPL tokens as powerful and simple as possible. Args: diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..9f0540b --- /dev/null +++ b/src/setup.py @@ -0,0 +1,41 @@ +from setuptools import setup, find_packages +import codecs +import os + +here = os.path.abspath(os.path.dirname(__file__)) + +with codecs.open(os.path.join(here, "README.md"), encoding="utf-8") as fh: + long_description = "\n" + fh.read() + +VERSION = '0.0.2.0' +DESCRIPTION = 'Jupiter Python SDK' +LONG_DESCRIPTION = 'This package allows the use of Jupiter decentralized exchange features on Solana using Python.' + +# Setting up +setup( + name="jupiter-python-sdk", + version=VERSION, + author="TaoDev", + author_email="taodev3@proton.me", + description=DESCRIPTION, + long_description_content_type="text/markdown", + long_description=long_description, + packages=find_packages(), + license=('LICENSE.txt'), + install_requires=[ + 'base58', + 'solders', + 'solana', + 'httpx', + 'anchorpy', + ], + keywords=['python', 'solana', 'jupiter', 'dex', 'trading', 'sdk'], + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Operating System :: Unix", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + ] +) \ No newline at end of file