├── .bumpversion.cfg ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── release-drafter.yml └── workflows │ ├── pr-title.yml │ ├── python-package.yml │ ├── python-publish.yml │ └── release-drafter.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── fireblocks_sdk ├── __init__.py ├── api_types.py ├── ncw_sdk.py ├── sdk.py ├── sdk_token_provider.py └── tokenization_api_types.py ├── setup.cfg └── setup.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 2.16.1 3 | commit = false 4 | tag = false 5 | 6 | [bumpversion:file:setup.py] 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: tomervil, yarinvak 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions (please complete the following information):** 27 | - Python Version: [e.g 2.0] 28 | - fireblocks-sdk version: [e.g. 1.7.0] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: michael-fireblocks, tomervil 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '🧰 Maintenance' 14 | label: 'chore' 15 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 16 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 17 | version-resolver: 18 | major: 19 | labels: 20 | - 'major' 21 | - 'breaking' 22 | minor: 23 | labels: 24 | - 'minor' 25 | - 'enhancement' 26 | patch: 27 | labels: 28 | - 'patch' 29 | - 'bug' 30 | default: patch 31 | template: | 32 | ## Changes 33 | 34 | $CHANGES 35 | autolabeler: 36 | - label: 'chore' 37 | files: 38 | - '*.md' 39 | branch: 40 | - '/docs{0,1}\/.+/' 41 | - label: 'bug' 42 | branch: 43 | - '/fix\/.+/' 44 | title: 45 | - '/fix/i' 46 | - '/bugfix/i' 47 | - label: 'enhancement' 48 | title: 49 | - '/added/i' 50 | - '/add/i' 51 | - '/feature/i' 52 | - '/feat/i' 53 | - '/support/i' 54 | - '/enable/i' 55 | branch: 56 | - '/feature\/.+/' 57 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: PR Title Validation 2 | on: 3 | pull_request: 4 | types: [opened, edited, synchronize, reopened] 5 | jobs: 6 | validate: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: deepakputhraya/action-pr-title@master 10 | with: 11 | disallowed_prefixes: 'COR-' 12 | prefix_case_sensitive: false -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | python-version: [3.9, "3.10", 3.11, 3.12] 10 | os: [macos-13, macos-14, windows-latest, ubuntu-22.04, ubuntu-24.04] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set up Python ${{ matrix.python-version }} 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install flake8 pytest 22 | - name: Lint with flake8 23 | run: | 24 | # stop the build if there are Python syntax errors or undefined names 25 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 26 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 27 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 28 | - name: Build package 29 | run: pip install -e . 30 | - name: Import package to test compatbility 31 | run: python -c "import fireblocks_sdk" 32 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | jobs: 16 | deploy: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | token: ${{ secrets.RELEASE_TOKEN }} 24 | - name: Set up Python 25 | uses: actions/setup-python@v2 26 | with: 27 | python-version: '3.x' 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install build 32 | pip install bumpversion 33 | - run: | 34 | initialTag=${{ github.event.release.tag_name }} 35 | tag="${initialTag//[v]/}" 36 | echo $tag 37 | git remote update 38 | git fetch 39 | echo "finished fetching" 40 | git checkout --track origin/master 41 | echo "finished checkout" 42 | git config --global user.email "github-actions@github.com" 43 | git config --global user.name "Github Actions" 44 | echo "finished configuration" 45 | bumpversion patch --new-version $tag 46 | echo "bumpversion finished" 47 | git add . 48 | git commit -m "release $tag" 49 | git push 50 | - name: Build package 51 | run: python -m build 52 | - name: Publish package 53 | uses: pypa/gh-action-pypi-publish@release/v1 54 | with: 55 | user: __token__ 56 | password: ${{ secrets.PYPI_API_TOKEN }} 57 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | 10 | jobs: 11 | update_release_draft: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: release-drafter/release-drafter@v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.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 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .idea 106 | .vscode/ 107 | run/ 108 | 109 | .DS_Store 110 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fireblocks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Official Python SDK for Fireblocks API 2 | [![PyPI version](https://badge.fury.io/py/fireblocks-sdk.svg)](https://badge.fury.io/py/fireblocks-sdk) 3 | 4 | ## About 5 | This repository contains the official Python SDK for Fireblocks API. 6 | For the complete API reference, go to the [API reference](https://developers.fireblocks.com/). 7 | 8 | ## Usage 9 | ### Before You Begin 10 | Make sure you have the credentials for Fireblocks API Services. Otherwise, please contact Fireblocks support for further instructions on how to obtain your API credentials. 11 | 12 | ### Requirements 13 | An officially supported version of Python, as per the official lifecycle: 14 | https://devguide.python.org/versions 15 | 16 | ### Installation 17 | `pip3 install fireblocks-sdk` 18 | 19 | #### Importing Fireblocks SDK 20 | ```python 21 | from fireblocks_sdk import FireblocksSDK 22 | 23 | fireblocks = FireblocksSDK(private_key, api_key) 24 | ``` 25 | 26 | You can also pass additional arguments: 27 | ```python 28 | fireblocks = FireblocksSDK(private_key, api_key, api_base_url="https://api.fireblocks.io", timeout=2.0, anonymous_platform=True) 29 | ``` 30 | 31 | #### Using Fireblocks Tokenization endpoints 32 | ```python 33 | from fireblocks_sdk import FireblocksSDK, FireblocksTokenization, \ 34 | ContractUploadRequest 35 | 36 | fireblocks = FireblocksSDK(private_key, api_key) 37 | 38 | # Get linked tokens 39 | tokens=fireblocks.get_linked_tokens() 40 | 41 | # Upload a private contract 42 | contractTemplateRequest=ContractUploadRequest( 43 | name='New Contract Template', 44 | description='description', 45 | longDescription='long description', 46 | bytecode='0x12345', 47 | sourcecode= 'sourcecode', 48 | initializationPhase='ON_DEPLOYMENT', 49 | abi=[] 50 | ) 51 | template=fireblocks.upload_contract_template(contractTemplateRequest) 52 | print(template['id']) 53 | ``` 54 | -------------------------------------------------------------------------------- /fireblocks_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | from fireblocks_sdk.sdk import FireblocksSDK 2 | from fireblocks_sdk.ncw_sdk import FireblocksNCW 3 | from fireblocks_sdk.sdk_token_provider import SdkTokenProvider 4 | from fireblocks_sdk.api_types import * 5 | from fireblocks_sdk.tokenization_api_types import * 6 | -------------------------------------------------------------------------------- /fireblocks_sdk/api_types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional, List, Union 3 | 4 | 5 | def snake_to_camel(snake_case: str): 6 | words = snake_case.split('_') 7 | return words[0] + ''.join(word.capitalize() for word in words[1:]) 8 | 9 | 10 | def convert_class_to_dict(class_dict: dict): 11 | output_dict = {} 12 | for key, value in class_dict.items(): 13 | if isinstance(value, list): 14 | output_dict[snake_to_camel(key)] = [item.to_dict() if hasattr(item, 'to_dict') else item for item 15 | in value] 16 | elif hasattr(value, 'to_dict') and callable(getattr(value, 'to_dict')): 17 | output_dict[snake_to_camel(key)] = value.to_dict() 18 | elif value is not None: 19 | output_dict[snake_to_camel(key)] = value 20 | return output_dict 21 | 22 | 23 | class TransferPeerPath: 24 | def __init__(self, peer_type, peer_id): 25 | """Defines a source or a destination for a transfer 26 | 27 | Args: 28 | peer_type (str): either VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, FIAT_ACCOUNT, NETWORK_CONNECTION, ONE_TIME_ADDRESS or UNKNOWN_PEER 29 | peer_id (str): the account/wallet id 30 | """ 31 | 32 | if peer_type not in PEER_TYPES: 33 | raise Exception("Got invalid transfer peer type: " + peer_type) 34 | self.type = peer_type 35 | if peer_id is not None: 36 | self.id = str(peer_id) 37 | 38 | 39 | class DestinationTransferPeerPath(TransferPeerPath): 40 | def __init__(self, peer_type, peer_id=None, one_time_address=None): 41 | """Defines a destination for a transfer 42 | 43 | Args: 44 | peer_type (str): either VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, FIAT_ACCOUNT, NETWORK_CONNECTION, ONE_TIME_ADDRESS or UNKNOWN_PEER 45 | peer_id (str): the account/wallet id 46 | one_time_address (JSON object): The destination address (and tag) for a non whitelisted address. 47 | """ 48 | TransferPeerPath.__init__(self, peer_type, peer_id) 49 | 50 | if one_time_address is not None: 51 | self.oneTimeAddress = one_time_address 52 | 53 | 54 | TRANSACTION_TRANSFER = "TRANSFER" 55 | TRANSACTION_MINT = "MINT" 56 | TRANSACTION_BURN = "BURN" 57 | TRANSACTION_SUPPLY_TO_COMPOUND = "SUPPLY_TO_COMPOUND" 58 | TRANSACTION_REDEEM_FROM_COMPOUND = "REDEEM_FROM_COMPOUND" 59 | RAW = "RAW" 60 | CONTRACT_CALL = "CONTRACT_CALL" 61 | ONE_TIME_ADDRESS = "ONE_TIME_ADDRESS" 62 | TYPED_MESSAGE = "TYPED_MESSAGE" 63 | 64 | TRANSACTION_TYPES = ( 65 | TRANSACTION_TRANSFER, 66 | TRANSACTION_MINT, 67 | TRANSACTION_BURN, 68 | TRANSACTION_SUPPLY_TO_COMPOUND, 69 | TRANSACTION_REDEEM_FROM_COMPOUND, 70 | RAW, 71 | CONTRACT_CALL, 72 | ONE_TIME_ADDRESS, 73 | TYPED_MESSAGE 74 | ) 75 | 76 | TRANSACTION_STATUS_SUBMITTED = "SUBMITTED" 77 | TRANSACTION_STATUS_QUEUED = "QUEUED" 78 | TRANSACTION_STATUS_PENDING_SIGNATURE = "PENDING_SIGNATURE" 79 | TRANSACTION_STATUS_PENDING_AUTHORIZATION = "PENDING_AUTHORIZATION" 80 | TRANSACTION_STATUS_PENDING_3RD_PARTY_MANUAL_APPROVAL = "PENDING_3RD_PARTY_MANUAL_APPROVAL" 81 | TRANSACTION_STATUS_PENDING_3RD_PARTY = "PENDING_3RD_PARTY" 82 | TRANSACTION_STATUS_PENDING = "PENDING" # Deprecated 83 | TRANSACTION_STATUS_BROADCASTING = "BROADCASTING" 84 | TRANSACTION_STATUS_CONFIRMING = "CONFIRMING" 85 | TRANSACTION_STATUS_CONFIRMED = "CONFIRMED" # Deprecated 86 | TRANSACTION_STATUS_COMPLETED = "COMPLETED" 87 | TRANSACTION_STATUS_PENDING_AML_SCREENING = "PENDING_AML_SCREENING" 88 | TRANSACTION_STATUS_PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED" 89 | TRANSACTION_STATUS_CANCELLING = "CANCELLING" 90 | TRANSACTION_STATUS_CANCELLED = "CANCELLED" 91 | TRANSACTION_STATUS_REJECTED = "REJECTED" 92 | TRANSACTION_STATUS_FAILED = "FAILED" 93 | TRANSACTION_STATUS_TIMEOUT = "TIMEOUT" 94 | TRANSACTION_STATUS_BLOCKED = "BLOCKED" 95 | 96 | TRANSACTION_STATUS_TYPES = ( 97 | TRANSACTION_STATUS_SUBMITTED, 98 | TRANSACTION_STATUS_QUEUED, 99 | TRANSACTION_STATUS_PENDING_SIGNATURE, 100 | TRANSACTION_STATUS_PENDING_AUTHORIZATION, 101 | TRANSACTION_STATUS_PENDING_3RD_PARTY_MANUAL_APPROVAL, 102 | TRANSACTION_STATUS_PENDING_3RD_PARTY, 103 | TRANSACTION_STATUS_PENDING, 104 | TRANSACTION_STATUS_BROADCASTING, 105 | TRANSACTION_STATUS_CONFIRMING, 106 | TRANSACTION_STATUS_CONFIRMED, 107 | TRANSACTION_STATUS_COMPLETED, 108 | TRANSACTION_STATUS_PENDING_AML_SCREENING, 109 | TRANSACTION_STATUS_PARTIALLY_COMPLETED, 110 | TRANSACTION_STATUS_CANCELLING, 111 | TRANSACTION_STATUS_CANCELLED, 112 | TRANSACTION_STATUS_REJECTED, 113 | TRANSACTION_STATUS_FAILED, 114 | TRANSACTION_STATUS_TIMEOUT, 115 | TRANSACTION_STATUS_BLOCKED 116 | ) 117 | 118 | VAULT_ACCOUNT = "VAULT_ACCOUNT" 119 | EXCHANGE_ACCOUNT = "EXCHANGE_ACCOUNT" 120 | INTERNAL_WALLET = "INTERNAL_WALLET" 121 | EXTERNAL_WALLET = "EXTERNAL_WALLET" 122 | UNKNOWN_PEER = "UNKNOWN" 123 | FIAT_ACCOUNT = "FIAT_ACCOUNT" 124 | NETWORK_CONNECTION = "NETWORK_CONNECTION" 125 | COMPOUND = "COMPOUND" 126 | 127 | PEER_TYPES = ( 128 | VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, NETWORK_CONNECTION, 129 | COMPOUND, ONE_TIME_ADDRESS) 130 | 131 | MPC_ECDSA_SECP256K1 = "MPC_ECDSA_SECP256K1" 132 | MPC_EDDSA_ED25519 = "MPC_EDDSA_ED25519" 133 | 134 | SIGNING_ALGORITHM = (MPC_ECDSA_SECP256K1, MPC_EDDSA_ED25519) 135 | 136 | HIGH = "HIGH" 137 | MEDIUM = "MEDIUM" 138 | LOW = "LOW" 139 | 140 | FEE_LEVEL = (HIGH, MEDIUM, LOW) 141 | 142 | 143 | class TransferTicketTerm: 144 | def __init__(self, network_connection_id, outgoing, asset, amount, note=None, operation=TRANSACTION_TRANSFER): 145 | """Defines a transfer ticket's term 146 | 147 | Args: 148 | network_connection_id (str): The Fireblocks network connection on which this term should be fulfilled 149 | outgoing (bool): True means that the term is from the initiator of the ticket 150 | asset (str): The asset of term that was agreed on 151 | amount (str): The amount of the asset that should be transferred 152 | note (str, optional): Custom note that can be added to the term 153 | 154 | """ 155 | 156 | self.networkConnectionId = str(network_connection_id) 157 | self.outgoing = bool(outgoing) 158 | self.asset = str(asset) 159 | self.amount = str(amount) 160 | if note: 161 | self.note = str(note) 162 | self.operation = operation 163 | 164 | 165 | class UnsignedMessage: 166 | def __init__(self, content, bip44addressIndex=None, bip44change=None, derivationPath=None): 167 | """Defines message to be signed by raw transaction 168 | 169 | Args: 170 | content (str): The message to be signed in hex format encoding 171 | bip44addressIndex (number, optional): BIP44 address_index path level 172 | bip44change (number, optional): BIP44 change path level 173 | derivationPath (list of numbers, optional): Should be passed only if asset and source were not specified 174 | """ 175 | 176 | self.content = content 177 | 178 | if bip44addressIndex: 179 | self.bip44addressIndex = bip44addressIndex 180 | 181 | if bip44change: 182 | self.bip44change = bip44change 183 | 184 | if derivationPath: 185 | self.derivationPath = derivationPath 186 | 187 | 188 | class RawMessage: 189 | def __init__(self, messages, algorithm=None): 190 | """Defines raw message 191 | 192 | Args: 193 | messages (list of UnsignedMessage): 194 | algorithm (str, optional): 195 | """ 196 | 197 | self.messages = messages 198 | if algorithm: 199 | self.algorithm = algorithm 200 | 201 | 202 | class TransactionDestination: 203 | def __init__(self, amount, destination): 204 | """Defines destinations for multiple outputs transaction 205 | 206 | Args: 207 | amount (double): The amount to transfer 208 | destination (DestinationTransferPeerPath): The transfer destination 209 | """ 210 | 211 | self.amount = str(amount) 212 | self.destination = destination.__dict__ 213 | 214 | 215 | class FireblocksApiException(Exception): 216 | """Exception raised for Fireblocks sdk errors 217 | 218 | Attributes: 219 | message: explanation of the error 220 | error_code: error code of the error 221 | """ 222 | 223 | def __init__(self, message="Fireblocks SDK error", error_code=None): 224 | self.message = message 225 | self.error_code = error_code 226 | super().__init__(self.message) 227 | 228 | class RescanTx: 229 | """ 230 | Args 231 | asset_id (string): The asset symbol 232 | tx_hash (string): The hash of the transaction 233 | """ 234 | def __init__(self, asset_id, tx_hash): 235 | self.asset_id = asset_id 236 | self.tx_hash = tx_hash 237 | 238 | def to_dict(self): 239 | return convert_class_to_dict(self.__dict__) 240 | 241 | class PagedVaultAccountsRequestFilters: 242 | """ Optional filters to apply for request 243 | 244 | Args 245 | name_prefix (string, optional): Vault account name prefix 246 | name_suffix (string, optional): Vault account name suffix 247 | min_amount_threshold (number, optional): The minimum amount for asset to have in order to be included in the results 248 | asset_id (string, optional): The asset symbol 249 | order_by (ASC/DESC, optional): Order of results by vault creation time (default: DESC) 250 | limit (number, optional): Results page size 251 | before (string, optional): cursor string received from previous request 252 | after (string, optional): cursor string received from previous request 253 | 254 | Constraints 255 | - You should only insert 'name_prefix' or 'name_suffix' (or none of them), but not both 256 | - You should only insert 'before' or 'after' (or none of them), but not both 257 | - For default and max 'limit' values please see: https://docs.fireblocks.com/api/swagger-ui/#/ 258 | """ 259 | 260 | def __init__(self, name_prefix=None, name_suffix=None, min_amount_threshold=None, asset_id=None, order_by=None, 261 | limit=None, before=None, 262 | after=None): 263 | self.name_prefix = name_prefix 264 | self.name_suffix = name_suffix 265 | self.min_amount_threshold = min_amount_threshold 266 | self.asset_id = asset_id 267 | self.order_by = order_by 268 | self.limit = limit 269 | self.before = before 270 | self.after = after 271 | 272 | 273 | class PagedExchangeAccountRequestFilters: 274 | """ Optional filters to apply for request 275 | 276 | Args 277 | 278 | limit (number, optional): Results page size 279 | before (string, optional): cursor string received from previous request 280 | after (string, optional): cursor string received from previous request 281 | 282 | Constraints 283 | - You should only insert 'before' or 'after' (or none of them), but not both 284 | - For default and max 'limit' values please see: https://docs.fireblocks.com/api/swagger-ui/#/ 285 | """ 286 | 287 | def __init__(self, limit=None, before=None, after=None): 288 | self.limit = limit 289 | self.before = before 290 | self.after = after 291 | 292 | 293 | class GetAssetWalletsFilters: 294 | """ Optional filters to apply for request 295 | 296 | Args 297 | total_amount_larger_than (number, optional): The minimum amount for asset to have in order to be included in the results 298 | asset_id (string, optional): The asset symbol 299 | order_by (ASC/DESC, optional): Order of results by vault creation time (default: DESC) 300 | limit (number, optional): Results page size 301 | before (string, optional): cursor string received from previous request 302 | after (string, optional): cursor string received from previous request 303 | 304 | Constraints 305 | - You should only insert 'before' or 'after' (or none of them), but not both 306 | """ 307 | 308 | def __init__(self, total_amount_larger_than=None, asset_id=None, order_by=None, limit=None, before=None, 309 | after=None): 310 | self.total_amount_larger_than = total_amount_larger_than 311 | self.asset_id = asset_id 312 | self.order_by = order_by 313 | self.limit = limit 314 | self.before = before 315 | self.after = after 316 | 317 | 318 | class GetSmartTransferFilters: 319 | """ Optional filters to apply for request 320 | Args 321 | query (string, optional): Search query string - either ticketId, asset or network name 322 | statuses (DRAFT/PENDING_APPROVAL/OPEN/IN_SETTLEMENT/FULFILLED/EXPIRED/CANCELED, optional): array of ticket statuses 323 | network_id (string, optional): networkId used in ticket 324 | created_by_me (bool, optional): created by me flag 325 | expires_after (string, optional): Lower bound of search range 326 | expires_before (string, optional): Upper bound of search range 327 | ticket_type (ASYNC/ATOMIC, optional): type of ticket 328 | external_ref_id (string, optional): external ref id 329 | after (string, optional): cursor string received from previous request 330 | limit (number, optional): Results page size 331 | 332 | Constraints 333 | - You should only insert 'before' or 'after' (or none of them), but not both 334 | """ 335 | 336 | def __init__(self, query: Optional[str] = None, statuses: Optional[str] = None, network_id: Optional[str] = None, 337 | created_by_me: Optional[bool] = None, expires_after: Optional[str] = None, 338 | expires_before: Optional[str] = None, ticket_type: Optional[str] = None, 339 | external_ref_id: Optional[str] = None, after: Optional[str] = None, limit: Optional[str] = None): 340 | self.query = query 341 | self.statuses = statuses 342 | self.network_id = network_id 343 | self.created_by_me = created_by_me 344 | self.expires_after = expires_after 345 | self.expires_before = expires_before 346 | self.ticket_type = ticket_type 347 | self.external_ref_id = external_ref_id 348 | self.limit = limit 349 | self.after = after 350 | 351 | 352 | class GetOwnedNftsSortValues(str, Enum): 353 | OWNERSHIP_LAST_UPDATE_TIME = "ownershipLastUpdateTime" 354 | TOKEN_NAME = "name" 355 | COLLECTION_NAME = "collection.name" 356 | BLOCKCHAIN_DESCRIPTOR = "blockchainDescriptor" 357 | 358 | 359 | class GetNftsSortValues(str, Enum): 360 | TOKEN_NAME = "name" 361 | COLLECTION_NAME = "collection.name" 362 | BLOCKCHAIN_DESCRIPTOR = "blockchainDescriptor" 363 | 364 | 365 | class NFTOwnershipStatusValues(str, Enum): 366 | LISTED = "LISTED" 367 | ARCHIVED = "ARCHIVED" 368 | 369 | 370 | class NFTOwnershipStatusUpdatedPayload: 371 | def __init__(self, asset_id: str, status: NFTOwnershipStatusValues): 372 | self.asset_id = asset_id 373 | self.status = status 374 | 375 | def serialize(self) -> dict: 376 | return { 377 | 'assetId': self.asset_id, 378 | 'status': self.status, 379 | } 380 | 381 | 382 | class GetOwnedCollectionsSortValue(str, Enum): 383 | COLLECTION_NAME = "name" 384 | 385 | 386 | class GetOwnedAssetsSortValues(str, Enum): 387 | ASSET_NAME = "name" 388 | 389 | 390 | class NFTsWalletTypeValues(str, Enum): 391 | VAULT_ACCOUNT = "VAULT_ACCOUNT" 392 | END_USER_WALLET = "END_USER_WALLET" 393 | 394 | class SpamTokenOwnershipValues(str, Enum): 395 | TRUE = "true" 396 | FALSE = "false" 397 | ALL = "all" 398 | 399 | class TokenOwnershipSpamUpdatePayload: 400 | def __init__(self, asset_id: str, spam: bool): 401 | self.asset_id = asset_id 402 | self.spam = spam 403 | 404 | def serialize(self) -> dict: 405 | return { 406 | 'assetId': self.asset_id, 407 | 'spam': self.spam, 408 | } 409 | 410 | 411 | class AssetClassValues(str, Enum): 412 | NATIVE = "NATIVE" 413 | FT = "FT" 414 | FIAT = "FIAT" 415 | NFT = "NFT" 416 | SFT = "SFT" 417 | 418 | 419 | class AssetScopeValues(str, Enum): 420 | GLOBAL = "GLOBAL" 421 | LOCAL = "LOCAL" 422 | 423 | 424 | class OrderValues(str, Enum): 425 | ASC = "ASC" 426 | DESC = "DESC" 427 | 428 | 429 | class TimePeriod(str, Enum): 430 | DAY = "DAY" 431 | WEEK = "WEEK" 432 | 433 | class PolicyTransactionType(str, Enum): 434 | ANY = "*" 435 | CONTRACT_CALL = "CONTRACT_CALL" 436 | RAW = "RAW" 437 | TRANSFER = "TRANSFER" 438 | APPROVE = "APPROVE" 439 | MINT = "MINT" 440 | BURN = "BURN" 441 | SUPPLY = "SUPPLY" 442 | REDEEM = "REDEEM" 443 | STAKE = "STAKE" 444 | TYPED_MESSAGE = "TYPED_MESSAGE" 445 | 446 | 447 | class PolicySrcOrDestType(str, Enum): 448 | EXCHANGE = "EXCHANGE" 449 | UNMANAGED = "UNMANAGED" 450 | VAULT = "VAULT" 451 | NETWORK_CONNECTION = "NETWORK_CONNECTION" 452 | COMPOUND = "COMPOUND" 453 | FIAT_ACCOUNT = "FIAT_ACCOUNT" 454 | ONE_TIME_ADDRESS = "ONE_TIME_ADDRESS" 455 | ANY = "*" 456 | 457 | 458 | class PolicyType(str, Enum): 459 | TRANSFER = "TRANSFER" 460 | 461 | 462 | class PolicyAction(str, Enum): 463 | ALLOW = "ALLOW" 464 | BLOCK = "BLOCK" 465 | TWO_TIER = "2-TIER" 466 | 467 | 468 | class PolicyDestAddressType(str, Enum): 469 | ANY = "*" 470 | WHITELISTED = "WHITELISTED" 471 | ONE_TIME = "ONE_TIME" 472 | 473 | 474 | class PolicyAmountScope(str, Enum): 475 | SINGLE_TX = "SINGLE_TX" 476 | TIMEFRAME = "TIMEFRAME" 477 | 478 | 479 | class PolicySrcOrDestSubType(str, Enum): 480 | ANY = "*" 481 | EXTERNAL = "EXTERNAL" 482 | INTERNAL = "INTERNAL" 483 | CONTRACT = "CONTRACT" 484 | EXCHANGETEST = "EXCHANGETEST" 485 | 486 | 487 | class Wildcard(str, Enum): 488 | WILDCARD = "*" 489 | 490 | 491 | class AuthorizationLogic(str, Enum): 492 | AND = "AND" 493 | OR = "OR" 494 | 495 | class Role(str, Enum): 496 | ADMIN = "ADMIN" 497 | SIGNER = "SIGNER" 498 | EDITOR = "EDITOR" 499 | APPROVER = "APPROVER" 500 | VIEWER = "VIEWER" 501 | NON_SIGNING_ADMIN = "NON_SIGNING_ADMIN" 502 | AUDITOR = "AUDITOR" 503 | NCW_ADMIN = "NCW_ADMIN" 504 | NCW_SIGNER = "NCW_SIGNER" 505 | 506 | 507 | class AuthorizationGroup: 508 | def __init__(self, users: Optional[List[str]] = None, users_groups: Optional[List[str]] = None, th: int = 0): 509 | if users: 510 | self.users = users 511 | if users_groups: 512 | self.users_groups = users_groups 513 | self.th = th 514 | 515 | def to_dict(self): 516 | return convert_class_to_dict(self.__dict__) 517 | 518 | 519 | class PolicyAuthorizationGroups: 520 | def __init__(self, logic: AuthorizationLogic, allow_operator_as_authorizer: Optional[bool] = None, 521 | groups: List[AuthorizationGroup] = []): 522 | self.logic = logic 523 | if allow_operator_as_authorizer: 524 | self.allow_operator_as_authorizer = allow_operator_as_authorizer 525 | self.groups = groups 526 | 527 | def to_dict(self): 528 | return convert_class_to_dict(self.__dict__) 529 | 530 | 531 | class Operators: 532 | def __init__(self, wildcard: Optional[Wildcard] = None, users: Optional[List[str]] = None, 533 | users_groups: Optional[List[str]] = None, services: Optional[List[str]] = None): 534 | if wildcard: 535 | self.wildcard = wildcard 536 | if users: 537 | self.users = users 538 | if users_groups: 539 | self.users_groups = users_groups 540 | if services: 541 | self.services = services 542 | 543 | def to_dict(self): 544 | return convert_class_to_dict(self.__dict__) 545 | 546 | 547 | class DesignatedSigners: 548 | def __init__(self, users: Optional[List[str]] = None, users_groups: Optional[List[str]] = None): 549 | if users: 550 | self.users = users 551 | if users_groups: 552 | self.users_groups = users_groups 553 | 554 | def to_dict(self): 555 | return convert_class_to_dict(self.__dict__) 556 | 557 | 558 | class SrcDst: 559 | def __init__(self, ids: Optional[List[List[Union[str, PolicySrcOrDestType, PolicySrcOrDestSubType]]]] = None): 560 | if ids: 561 | self.ids = ids 562 | 563 | def to_dict(self): 564 | return convert_class_to_dict(self.__dict__) 565 | 566 | 567 | class AmountAggregation: 568 | def __init__(self, operators: str, src_transfer_peers: str, dst_transfer_peers: str): 569 | self.operators = operators 570 | self.src_transfer_peers = src_transfer_peers 571 | self.dst_transfer_peers = dst_transfer_peers 572 | 573 | def to_dict(self): 574 | return convert_class_to_dict(self.__dict__) 575 | 576 | 577 | class DerivationPath: 578 | def __init__(self, path: List[int]): 579 | self.path = path 580 | 581 | def to_dict(self): 582 | return convert_class_to_dict(self.__dict__) 583 | 584 | 585 | class RawMessageSigning: 586 | def __init__(self, derivation_path: DerivationPath, algorithm: str): 587 | self.derivation_path = derivation_path 588 | self.algorithm = algorithm 589 | 590 | def to_dict(self): 591 | return convert_class_to_dict(self.__dict__) 592 | 593 | 594 | class PolicyRule: 595 | def __init__(self, 596 | type: PolicyType, 597 | action: PolicyAction, 598 | asset: str, 599 | amount_currency: str, 600 | amount_scope: PolicyAmountScope, 601 | amount: Union[int, str], 602 | period_sec: int, 603 | external_descriptor: Optional[str] = None, 604 | operator: Optional[str] = None, 605 | operators: Optional[Operators] = None, 606 | transaction_type: Optional[PolicyTransactionType] = None, 607 | operator_services: Optional[List[str]] = None, 608 | designated_signer: Optional[str] = None, 609 | designated_signers: Optional[DesignatedSigners] = None, 610 | src_type: Optional[PolicySrcOrDestType] = None, 611 | src_sub_type: Optional[PolicySrcOrDestSubType] = None, 612 | src_id: Optional[str] = None, 613 | src: Optional[SrcDst] = None, 614 | dst_type: Optional[PolicySrcOrDestType] = None, 615 | dst_sub_type: Optional[PolicySrcOrDestSubType] = None, 616 | dst_id: Optional[str] = None, 617 | dst: Optional[SrcDst] = None, 618 | dst_address_type: Optional[PolicyDestAddressType] = None, 619 | authorizers: Optional[List[str]] = None, 620 | authorizers_count: Optional[int] = None, 621 | authorization_groups: Optional[PolicyAuthorizationGroups] = None, 622 | amount_aggregation: Optional[AmountAggregation] = None, 623 | raw_message_signing: Optional[RawMessageSigning] = None, 624 | apply_for_approve: Optional[bool] = None, 625 | apply_for_typed_message: Optional[bool] = None): 626 | self.type = type 627 | self.action = action 628 | self.asset = asset 629 | self.amount_currency = amount_currency 630 | self.amount_scope = amount_scope 631 | self.amount = amount 632 | self.period_sec = period_sec 633 | if external_descriptor: 634 | self.external_descriptor = external_descriptor 635 | if operator: 636 | self.operator = operator 637 | if operators: 638 | self.operators = operators 639 | if transaction_type: 640 | self.transaction_type = transaction_type 641 | if operator_services: 642 | self.operator_services = operator_services 643 | if designated_signer: 644 | self.designated_signer = designated_signer 645 | if designated_signers: 646 | self.designated_signers = designated_signers 647 | if src_type: 648 | self.src_type = src_type 649 | if src_sub_type: 650 | self.src_sub_type = src_sub_type 651 | if src_id: 652 | self.src_id = src_id 653 | if src: 654 | self.src = src 655 | if dst_type: 656 | self.dst_type = dst_type 657 | if dst_sub_type: 658 | self.dst_sub_type = dst_sub_type 659 | if dst_id: 660 | self.dst_id = dst_id 661 | if dst: 662 | self.dst = dst 663 | if dst_address_type: 664 | self.dst_address_type = dst_address_type 665 | if authorizers: 666 | self.authorizers = authorizers 667 | if authorizers_count: 668 | self.authorizers_count = authorizers_count 669 | if authorization_groups: 670 | self.authorization_groups = authorization_groups 671 | if amount_aggregation: 672 | self.amount_aggregation = amount_aggregation 673 | if raw_message_signing: 674 | self.raw_message_signing = raw_message_signing 675 | if apply_for_approve: 676 | self.apply_for_approve = apply_for_approve 677 | if apply_for_typed_message: 678 | self.apply_for_typed_message = apply_for_typed_message 679 | 680 | def to_dict(self): 681 | return convert_class_to_dict(self.__dict__) 682 | 683 | 684 | class StakeRequestDto: 685 | def __init__(self, 686 | vault_account_id: str, 687 | provider_id: str, 688 | stake_amount: str, 689 | tx_note: str = None, 690 | fee: str = None, 691 | fee_level: str = None): 692 | self.vault_account_id = vault_account_id 693 | self.provider_id = provider_id 694 | self.stake_amount = stake_amount 695 | self.tx_note = tx_note 696 | self.fee = fee 697 | self.fee_level = fee_level 698 | 699 | def to_dict(self): 700 | return convert_class_to_dict(self.__dict__) 701 | 702 | 703 | class UnstakeRequestDto: 704 | def __init__(self, id: str, amount: str = None, fee: str = None, fee_level: str = None, tx_note: str = None): 705 | self.id = id 706 | self.amount = amount 707 | self.fee = fee 708 | self.fee_level = fee_level 709 | self.tx_note = tx_note 710 | 711 | def to_dict(self): 712 | return convert_class_to_dict(self.__dict__) 713 | 714 | 715 | class WithdrawRequestDto: 716 | def __init__(self, id: str, fee: str = None, fee_level: str = None, tx_note: str = None): 717 | self.id = id 718 | self.fee = fee 719 | self.fee_level = fee_level 720 | self.tx_note = tx_note 721 | 722 | def to_dict(self): 723 | return convert_class_to_dict(self.__dict__) 724 | 725 | class ClaimRewardsRequestDto: 726 | def __init__(self, id: str, fee: str = None, fee_level: str = None, tx_note: str = None): 727 | self.id = id 728 | self.fee = fee 729 | self.fee_level = fee_level 730 | self.tx_note = tx_note 731 | 732 | def to_dict(self): 733 | return convert_class_to_dict(self.__dict__) 734 | 735 | class SplitRequestDto: 736 | def __init__(self, id: str, amount: str, fee: str = None, fee_level: str = None, tx_note: str = None): 737 | self.id = id 738 | self.amount = amount 739 | self.fee = fee 740 | self.fee_level = fee_level 741 | self.tx_note = tx_note 742 | 743 | def to_dict(self): 744 | return convert_class_to_dict(self.__dict__) -------------------------------------------------------------------------------- /fireblocks_sdk/ncw_sdk.py: -------------------------------------------------------------------------------- 1 | from .sdk import FireblocksSDK 2 | import urllib 3 | 4 | class FireblocksNCW: 5 | def __init__(self, sdk: FireblocksSDK): 6 | self.sdk = sdk 7 | self._wallet_url = "/v1/wallets" 8 | 9 | def create_wallet(self): 10 | url = "/v1/wallets" 11 | return self.sdk._post_request(url) 12 | 13 | def get_wallets(self): 14 | return self.sdk._get_request(self._wallet_url) 15 | 16 | def get_wallet(self, wallet_id: str): 17 | url = f"{self._wallet_url}/{wallet_id}" 18 | return self.sdk._get_request(url) 19 | 20 | def enable_wallet(self, wallet_id: str, enabled: bool): 21 | url = f"{self._wallet_url}/{wallet_id}/enable" 22 | body = {"enabled": enabled} 23 | return self.sdk._put_request(url, body) 24 | 25 | def create_wallet_account(self, wallet_id: str): 26 | url = f"{self._wallet_url}/{wallet_id}/accounts" 27 | return self.sdk._post_request(url) 28 | 29 | def get_wallet_accounts( 30 | self, 31 | wallet_id: str, 32 | page_cursor: str = None, 33 | page_size: int = None, 34 | sort: str = None, 35 | order: str = None, 36 | enabled: bool = None, 37 | ): 38 | url = f"{self._wallet_url}/{wallet_id}/accounts" 39 | query_params = {} 40 | 41 | if page_cursor: 42 | query_params["pageCursor"] = page_cursor 43 | 44 | if page_size: 45 | query_params["pageSize"] = page_size 46 | 47 | if sort: 48 | query_params["sort"] = sort 49 | 50 | if order: 51 | query_params["order"] = order 52 | 53 | if enabled: 54 | query_params["enabled"] = enabled 55 | 56 | return self.sdk._get_request(url, query_params=query_params) 57 | 58 | def get_wallet_account(self, wallet_id: str, account_id: str): 59 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}" 60 | return self.sdk._get_request(url) 61 | 62 | def get_wallet_assets( 63 | self, 64 | wallet_id: str, 65 | account_id: str, 66 | page_cursor: str = None, 67 | page_size: int = None, 68 | sort: str = None, 69 | order: str = None, 70 | enabled: bool = None, 71 | ): 72 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}/assets" 73 | query_params = {} 74 | 75 | if page_cursor: 76 | query_params["pageCursor"] = page_cursor 77 | 78 | if page_size: 79 | query_params["pageSize"] = page_size 80 | 81 | if sort: 82 | query_params["sort"] = sort 83 | 84 | if order: 85 | query_params["order"] = order 86 | 87 | if enabled: 88 | query_params["enabled"] = enabled 89 | 90 | return self.sdk._get_request(url, query_params=query_params) 91 | 92 | def get_wallet_asset(self, wallet_id: str, account_id: str, asset_id: str): 93 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}/assets/{asset_id}" 94 | return self.sdk._get_request(url) 95 | 96 | def activate_wallet_asset(self, wallet_id: str, account_id: str, asset_id: str): 97 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}/assets/{asset_id}" 98 | return self.sdk._post_request(url) 99 | 100 | def refresh_wallet_asset_balance( 101 | self, wallet_id: str, account_id: str, asset_id: str 102 | ): 103 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}/assets/{asset_id}/balance" 104 | return self.sdk._put_request(url) 105 | 106 | def get_wallet_asset_balance( 107 | self, wallet_id: str, account_id: str, asset_id: str 108 | ): 109 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}/assets/{asset_id}/balance" 110 | return self.sdk._get_request(url) 111 | 112 | def get_wallet_asset_addresses( 113 | self, 114 | wallet_id: str, 115 | account_id: str, 116 | asset_id: str, 117 | page_cursor: str = None, 118 | page_size: int = None, 119 | sort: str = None, 120 | order: str = None, 121 | enabled: bool = None, 122 | ): 123 | url = f"{self._wallet_url}/{wallet_id}/accounts/{account_id}/assets/{asset_id}/addresses" 124 | query_params = {} 125 | 126 | if page_cursor: 127 | query_params["pageCursor"] = page_cursor 128 | 129 | if page_size: 130 | query_params["pageSize"] = page_size 131 | 132 | if sort: 133 | query_params["sort"] = sort 134 | 135 | if order: 136 | query_params["order"] = order 137 | 138 | if enabled: 139 | query_params["enabled"] = enabled 140 | 141 | return self.sdk._get_request(url, query_params=query_params) 142 | 143 | def get_devices(self, wallet_id: str): 144 | url = f"{self._wallet_url}/{wallet_id}/devices" 145 | return self.sdk._get_request(url) 146 | 147 | def enable_device(self, wallet_id: str, device_id: str, enabled: bool): 148 | url = f"{self._wallet_url}/{wallet_id}/devices/{device_id}" 149 | body = {"enabled": enabled} 150 | 151 | return self.sdk._put_request(url, body) 152 | 153 | def invoke_wallet_rpc(self, wallet_id: str, device_id: str, payload: str): 154 | """ 155 | payload: stringified JSON, message originated in the NCW SDK 156 | """ 157 | url = f"{self._wallet_url}/{wallet_id}/devices/{device_id}/rpc" 158 | body = {"payload": payload} 159 | 160 | return self.sdk._post_request(url, body) 161 | 162 | def get_public_key_info(self, wallet_id: str, algorithm: str, derivation_path: str, compressed=None): 163 | """Get the public key information 164 | 165 | Args: 166 | wallet_id (str) 167 | algorithm (str) 168 | derivation_path (str) 169 | compressed (boolean, optional) 170 | """ 171 | 172 | url = f"/v1/ncw/wallet/${wallet_id}/public_key_info" 173 | if algorithm: 174 | url += f"?algorithm={algorithm}" 175 | if derivation_path: 176 | url += f"&derivationPath={urllib.parse.quote(derivation_path)}" 177 | if compressed: 178 | url += f"&compressed={compressed}" 179 | return self.sdk._get_request(url, None, None, wallet_id) 180 | 181 | def get_public_key_info_by_account_asset( 182 | self, wallet_id: str, asset_id: str, account_id: int, change: int, address_index: int, compressed=None 183 | ): 184 | """Get the public key information for an NCW account 185 | 186 | Args: 187 | wallet_id (str) 188 | asset_id (str) 189 | account_id (number) 190 | change (number) 191 | address_index (number) 192 | compressed (boolean, optional) 193 | """ 194 | 195 | url = f"/v1/ncw/wallet/${wallet_id}/accounts/{account_id}/{asset_id}/{change}/{address_index}/public_key_info" 196 | if compressed: 197 | url += f"?compressed={compressed}" 198 | 199 | return self.sdk._get_request(url, None, None, wallet_id) 200 | 201 | -------------------------------------------------------------------------------- /fireblocks_sdk/sdk.py: -------------------------------------------------------------------------------- 1 | import json 2 | import platform 3 | import urllib 4 | from importlib.metadata import version 5 | from operator import attrgetter 6 | from typing import Any, Dict, Optional, List, Union 7 | import requests 8 | 9 | from .api_types import ( 10 | FireblocksApiException, 11 | TRANSACTION_TYPES, 12 | TRANSACTION_STATUS_TYPES, 13 | TransferPeerPath, 14 | DestinationTransferPeerPath, 15 | TransferTicketTerm, 16 | TRANSACTION_TRANSFER, 17 | SIGNING_ALGORITHM, 18 | UnsignedMessage, 19 | FEE_LEVEL, 20 | PagedVaultAccountsRequestFilters, 21 | TransactionDestination, 22 | GetAssetWalletsFilters, 23 | TimePeriod, 24 | GetOwnedCollectionsSortValue, 25 | OrderValues, 26 | GetOwnedAssetsSortValues, 27 | PolicyRule, 28 | GetSmartTransferFilters, 29 | NFTOwnershipStatusValues, 30 | GetOwnedNftsSortValues, 31 | GetNftsSortValues, 32 | NFTsWalletTypeValues, 33 | NFTOwnershipStatusUpdatedPayload, 34 | PagedExchangeAccountRequestFilters, 35 | StakeRequestDto, 36 | UnstakeRequestDto, 37 | WithdrawRequestDto, 38 | ClaimRewardsRequestDto, 39 | SplitRequestDto, 40 | Role, 41 | SpamTokenOwnershipValues, 42 | TokenOwnershipSpamUpdatePayload, 43 | TokenOwnershipSpamUpdatePayload, 44 | RescanTx, 45 | AssetClassValues, 46 | AssetScopeValues, 47 | ) 48 | from .tokenization_api_types import \ 49 | CreateTokenRequest, \ 50 | ContractUploadRequest, \ 51 | ContractDeployRequest, \ 52 | ContractInitializationPhase, \ 53 | ContractTemplateType, \ 54 | TokenLinkStatus, \ 55 | TokenLinkType, \ 56 | ReadCallFunction, \ 57 | WriteCallFunction, \ 58 | CreateCollectionRequest, \ 59 | MintCollectionTokenRequest, \ 60 | BurnCollectionTokenRequest, \ 61 | AbiFunction 62 | from .sdk_token_provider import SdkTokenProvider 63 | 64 | 65 | def handle_response(response, page_mode=False): 66 | try: 67 | response_data = response.json() 68 | except: 69 | response_data = None 70 | if response.status_code >= 300: 71 | if type(response_data) is dict: 72 | error_code = response_data.get("code") 73 | raise FireblocksApiException( 74 | "Got an error from fireblocks server: " + response.text, error_code 75 | ) 76 | else: 77 | raise FireblocksApiException( 78 | "Got an error from fireblocks server: " + response.text 79 | ) 80 | else: 81 | if page_mode: 82 | return { 83 | "transactions": response_data, 84 | "pageDetails": { 85 | "prevPage": response.headers.get("prev-page", ""), 86 | "nextPage": response.headers.get("next-page", ""), 87 | }, 88 | } 89 | return response_data 90 | 91 | 92 | class FireblocksSDK: 93 | def __init__( 94 | self, 95 | private_key, 96 | api_key, 97 | api_base_url="https://api.fireblocks.io", 98 | timeout=None, 99 | anonymous_platform=False, 100 | seconds_jwt_exp=55, 101 | ): 102 | """Creates a new Fireblocks API Client. 103 | 104 | Args: 105 | private_key (str): A string representation of your private key (in PEM format) 106 | api_key (str): Your api key. This is a uuid you received from Fireblocks 107 | api_base_url (str): The fireblocks server URL. Leave empty to use the default server 108 | timeout (number): Timeout for http requests in seconds 109 | """ 110 | self.private_key = private_key 111 | self.api_key = api_key 112 | self.base_url = api_base_url 113 | self.token_provider = SdkTokenProvider(private_key, api_key, seconds_jwt_exp) 114 | self.timeout = timeout 115 | self.http_session = requests.Session() 116 | self.http_session.headers.update( 117 | { 118 | "X-API-Key": self.api_key, 119 | "User-Agent": self._get_user_agent(anonymous_platform), 120 | } 121 | ) 122 | 123 | def get_staking_chains(self): 124 | """Get all staking chains.""" 125 | return self._get_request("/v1/staking/chains") 126 | 127 | def get_staking_chain_info(self, chain_descriptor: str): 128 | """Get chain info.""" 129 | return self._get_request(f"/v1/staking/chains/{chain_descriptor}/chainInfo") 130 | 131 | def get_staking_positions_summary(self): 132 | """Get staking positions summary.""" 133 | return self._get_request(f"/v1/staking/positions/summary") 134 | 135 | def get_staking_positions_summary_by_vault(self): 136 | """Get staking positions summary by vault.""" 137 | return self._get_request("/v1/staking/positions/summary/vaults") 138 | 139 | def execute_staking_stake(self, chain_descriptor: str, request_body: StakeRequestDto): 140 | """Initiate staking stake on a chain. 141 | """ 142 | return self._post_request(f"/v1/staking/chains/{chain_descriptor}/stake", request_body.to_dict()) 143 | 144 | def execute_staking_unstake(self, chain_descriptor: str, request_body: UnstakeRequestDto): 145 | """Execute staking unstake on a chain. 146 | """ 147 | return self._post_request(f"/v1/staking/chains/{chain_descriptor}/unstake", request_body.to_dict()) 148 | 149 | def execute_staking_withdraw(self, chain_descriptor: str, request_body: WithdrawRequestDto): 150 | """Execute staking withdraw on a chain. 151 | """ 152 | return self._post_request(f"/v1/staking/chains/{chain_descriptor}/withdraw", request_body.to_dict()) 153 | 154 | def execute_staking_claim_rewards(self, chain_descriptor: str, request_body: ClaimRewardsRequestDto): 155 | """Execute staking claim rewards on a chain. 156 | """ 157 | return self._post_request(f"/v1/staking/chains/{chain_descriptor}/claimRewards", request_body.to_dict()) 158 | 159 | def execute_staking_split(self, chain_descriptor: str, request_body: SplitRequestDto): 160 | """Execute staking split on a chain. 161 | """ 162 | return self._post_request(f"/v1/staking/chains/{chain_descriptor}/split", request_body.to_dict()) 163 | 164 | def get_staking_positions(self, chain_descriptor: str = None): 165 | """Get all staking positions, optionally filtered by chain.""" 166 | return self._get_request("/v1/staking/positions", 167 | query_params={"chainDescriptor": chain_descriptor} if chain_descriptor else None) 168 | 169 | def get_staking_position(self, position_id: str): 170 | """Get a staking position by id.""" 171 | return self._get_request(f"/v1/staking/positions/{position_id}") 172 | 173 | def get_staking_providers(self): 174 | """Get all staking providers.""" 175 | return self._get_request(f"/v1/staking/providers") 176 | 177 | def approve_staking_provider_terms_of_service(self, provider_id: str): 178 | """Approve staking provider terms of service.""" 179 | return self._post_request(f"/v1/staking/providers/{provider_id}/approveTermsOfService") 180 | 181 | def get_nft(self, id: str): 182 | url = "/v1/nfts/tokens/" + id 183 | 184 | return self._get_request(url) 185 | 186 | def get_nfts( 187 | self, 188 | ids: List[str], 189 | page_cursor: str = "", 190 | page_size: int = 100, 191 | sort: List[GetNftsSortValues] = None, 192 | order: OrderValues = None, 193 | ): 194 | """ 195 | Example list: "[1,2,3,4]" 196 | 197 | """ 198 | url = f"/v1/nfts/tokens" 199 | 200 | if len(ids) <= 0: 201 | raise FireblocksApiException( 202 | "Invalid token_ids. Should contain at least 1 token id" 203 | ) 204 | 205 | params = { 206 | "ids": ",".join(ids), 207 | } 208 | 209 | if page_cursor: 210 | params["pageCursor"] = page_cursor 211 | 212 | if page_size: 213 | params["pageSize"] = page_size 214 | 215 | if sort: 216 | params["sort"] = ",".join(sort) 217 | 218 | if order: 219 | params["order"] = order.value 220 | 221 | return self._get_request(url, query_params=params) 222 | 223 | def refresh_nft_metadata(self, id: str): 224 | """ 225 | 226 | :param id: 227 | :return: 228 | """ 229 | url = "/v1/nfts/tokens/" + id 230 | return self._put_request(path=url) 231 | 232 | def refresh_nft_ownership_by_vault( 233 | self, blockchain_descriptor: str, vault_account_id: str 234 | ): 235 | """ 236 | 237 | :param blockchain_descriptor: 238 | :param vault_account_id: 239 | :return: 240 | """ 241 | url = "/v1/nfts/ownership/tokens" 242 | 243 | params = {} 244 | if blockchain_descriptor: 245 | params["blockchainDescriptor"] = blockchain_descriptor 246 | 247 | if vault_account_id: 248 | params["vaultAccountId"] = vault_account_id 249 | 250 | return self._put_request(url, query_params=params) 251 | 252 | def get_owned_nfts(self, blockchain_descriptor: str, vault_account_ids: List[str] = None, ids: List[str] = None, 253 | collection_ids: List[str] = None, page_cursor: str = '', page_size: int = 100, 254 | sort: List[GetOwnedNftsSortValues] = None, 255 | order: OrderValues = None, status: NFTOwnershipStatusValues = None, search: str = None, 256 | ncw_account_ids: List[str] = None, ncw_id: str = None, wallet_type: NFTsWalletTypeValues = None, spam: SpamTokenOwnershipValues = None): 257 | """ 258 | 259 | """ 260 | url = f"/v1/nfts/ownership/tokens" 261 | 262 | params = {} 263 | 264 | if blockchain_descriptor: 265 | params["blockchainDescriptor"] = blockchain_descriptor 266 | 267 | if vault_account_ids: 268 | params["vaultAccountIds"] = ",".join(vault_account_ids) 269 | 270 | if ids: 271 | params["ids"] = ",".join(ids) 272 | 273 | if collection_ids: 274 | params["collectionIds"] = ",".join(collection_ids) 275 | 276 | if ncw_account_ids: 277 | params['ncwAccountIds'] = ",".join(ncw_account_ids) 278 | 279 | if ncw_id: 280 | params['ncwId'] = ncw_id.value 281 | 282 | if wallet_type: 283 | params['walletType'] = wallet_type.value 284 | 285 | if page_cursor: 286 | params["pageCursor"] = page_cursor 287 | 288 | if page_size: 289 | params["pageSize"] = page_size 290 | 291 | if sort: 292 | params["sort"] = ",".join(sort) 293 | 294 | if order: 295 | params["order"] = order.value 296 | 297 | if status: 298 | params["status"] = status.value 299 | 300 | if search: 301 | params["search"] = search 302 | 303 | if spam: 304 | params["spam"] = spam.value 305 | 306 | return self._get_request(url, query_params=params) 307 | 308 | def list_owned_collections(self, search: str = None, status: NFTOwnershipStatusValues = None, 309 | ncw_id: str = None, wallet_type: NFTsWalletTypeValues = None, 310 | sort: List[GetOwnedCollectionsSortValue] = None, 311 | order: OrderValues = None, page_cursor: str = '', page_size: int = 100): 312 | """ 313 | 314 | """ 315 | url = f"/v1/nfts/ownership/collections" 316 | 317 | params = {} 318 | 319 | if search: 320 | params['search'] = search 321 | 322 | if status: 323 | params['status'] = status.value 324 | 325 | if ncw_id: 326 | params['ncwId'] = ncw_id.value 327 | 328 | if wallet_type: 329 | params['walletType'] = wallet_type.value 330 | 331 | if page_cursor: 332 | params['pageCursor'] = page_cursor 333 | 334 | if page_size: 335 | params['pageSize'] = page_size 336 | 337 | if sort: 338 | params['sort'] = ",".join(sort) 339 | 340 | if order: 341 | params['order'] = order.value 342 | 343 | return self._get_request(url, query_params=params) 344 | 345 | def list_owned_assets(self, search: str = None, status: NFTOwnershipStatusValues = None, 346 | ncw_id: str = None, wallet_type: NFTsWalletTypeValues = None, 347 | sort: List[GetOwnedAssetsSortValues] = None, 348 | order: OrderValues = None, page_cursor: str = '', page_size: int = 100, spam: SpamTokenOwnershipValues = None): 349 | """ 350 | """ 351 | url = f"/v1/nfts/ownership/assets" 352 | 353 | params = {} 354 | 355 | if search: 356 | params['search'] = search 357 | 358 | if status: 359 | params['status'] = status.value 360 | 361 | if ncw_id: 362 | params['ncwId'] = ncw_id.value 363 | 364 | if wallet_type: 365 | params['walletType'] = wallet_type.value 366 | 367 | if page_cursor: 368 | params["pageCursor"] = page_cursor 369 | 370 | if page_size: 371 | params["pageSize"] = page_size 372 | 373 | if sort: 374 | params["sort"] = ",".join(sort) 375 | 376 | if order: 377 | params['order'] = order 378 | 379 | if spam: 380 | params["spam"] = spam.value 381 | 382 | return self._get_request(url, query_params=params) 383 | 384 | def update_nft_ownership_status(self, id: str, status: NFTOwnershipStatusValues): 385 | """Update NFT ownership status for specific token 386 | 387 | Args: 388 | id (string): NFT asset id 389 | status (string): Status for update 390 | """ 391 | url = "/v1/nfts/ownership/tokens/" + id + "/status" 392 | 393 | return self._put_request(url, {"status": status.value}) 394 | 395 | def update_nft_ownerships_status(self, payload: List[NFTOwnershipStatusUpdatedPayload]): 396 | """Updates tokens status for a tenant, in all tenant vaults. 397 | 398 | Args: 399 | payload (NFTOwnershipStatusUpdatedPayload[]): List of assets with status for update 400 | """ 401 | url = "/v1/nfts/ownership/tokens/status" 402 | 403 | return self._put_request(url, list(map((lambda payload_item: payload_item.serialize()), payload))) 404 | 405 | def update_nft_token_ownerships_spam_status(self, payload: List[TokenOwnershipSpamUpdatePayload]): 406 | """Updates tokens spam status for a tenant, in all tenant vaults. 407 | 408 | Args: 409 | payload (TokenOwnershipSpamUpdatePayload[]): List of assets with status for update 410 | """ 411 | url = "/v1/nfts/ownership/tokens/spam" 412 | 413 | return self._put_request(url, list(map((lambda payload_item: payload_item.serialize()), payload))) 414 | 415 | def get_supported_assets(self): 416 | """Gets all assets that are currently supported by Fireblocks""" 417 | 418 | return self._get_request("/v1/supported_assets") 419 | 420 | def set_asset_price(self, id: str, currency: str, price: float): 421 | """Set asset price 422 | 423 | Args: 424 | id (str): The asset ID 425 | currency (str): The currency (according to ISO 4217 currency codes) 426 | price (str): The price in currency 427 | """ 428 | 429 | body = { 430 | "currency": currency, 431 | "price": price, 432 | } 433 | 434 | return self._post_request(f"/v1/assets/prices/${id}", body) 435 | 436 | def get_vault_accounts_with_page_info( 437 | self, paged_vault_accounts_request_filters: PagedVaultAccountsRequestFilters 438 | ): 439 | """Gets a page of vault accounts for your tenant according to filters given 440 | 441 | Args: 442 | paged_vault_accounts_request_filters (object, optional): Possible filters to apply for request 443 | """ 444 | 445 | url = f"/v1/vault/accounts_paged" 446 | ( 447 | name_prefix, 448 | name_suffix, 449 | min_amount_threshold, 450 | asset_id, 451 | order_by, 452 | limit, 453 | before, 454 | after, 455 | ) = attrgetter( 456 | "name_prefix", 457 | "name_suffix", 458 | "min_amount_threshold", 459 | "asset_id", 460 | "order_by", 461 | "limit", 462 | "before", 463 | "after", 464 | )( 465 | paged_vault_accounts_request_filters 466 | ) 467 | 468 | params = {} 469 | 470 | if name_prefix: 471 | params["namePrefix"] = name_prefix 472 | 473 | if name_suffix: 474 | params["nameSuffix"] = name_suffix 475 | 476 | if min_amount_threshold is not None: 477 | params["minAmountThreshold"] = min_amount_threshold 478 | 479 | if asset_id is not None: 480 | params["assetId"] = asset_id 481 | 482 | if order_by is not None: 483 | params["orderBy"] = order_by 484 | 485 | if limit is not None: 486 | params["limit"] = limit 487 | 488 | if before is not None: 489 | params["before"] = before 490 | 491 | if after is not None: 492 | params["after"] = after 493 | 494 | if params: 495 | url = url + "?" + urllib.parse.urlencode(params) 496 | 497 | return self._get_request(url) 498 | 499 | def get_asset_wallets(self, get_vault_wallets_filters: GetAssetWalletsFilters): 500 | """Optional filters to apply for request 501 | 502 | Args 503 | total_amount_larger_than (number, optional): The minimum amount for asset to have in order to be included in the results 504 | asset_id (string, optional): The asset symbol 505 | order_by (ASC/DESC, optional): Order of results by vault creation time (default: DESC) 506 | limit (number, optional): Results page size 507 | before (string, optional): cursor string received from previous request 508 | after (string, optional): cursor string received from previous request 509 | 510 | Constraints 511 | - You should only insert 'before' or 'after' (or none of them), but not both 512 | """ 513 | url = f"/v1/vault/asset_wallets" 514 | 515 | total_amount_larger_than, asset_id, order_by, limit, before, after = attrgetter( 516 | "total_amount_larger_than", 517 | "asset_id", 518 | "order_by", 519 | "limit", 520 | "before", 521 | "after", 522 | )(get_vault_wallets_filters) 523 | 524 | params = {} 525 | 526 | if total_amount_larger_than is not None: 527 | params["totalAmountLargerThan"] = total_amount_larger_than 528 | 529 | if asset_id is not None: 530 | params["assetId"] = asset_id 531 | 532 | if order_by is not None: 533 | params["orderBy"] = order_by 534 | 535 | if limit is not None: 536 | params["limit"] = limit 537 | 538 | if before is not None: 539 | params["before"] = before 540 | 541 | if after is not None: 542 | params["after"] = after 543 | 544 | if params: 545 | url = url + "?" + urllib.parse.urlencode(params) 546 | 547 | return self._get_request(url) 548 | 549 | def get_vault_account(self, vault_account_id): 550 | """Deprecated - Replaced by get_vault_account_by_id 551 | Args: 552 | vault_account_id (string): The id of the requested account 553 | """ 554 | 555 | return self._get_request(f"/v1/vault/accounts/{vault_account_id}") 556 | 557 | def get_vault_account_by_id(self, vault_account_id): 558 | """Gets a single vault account 559 | Args: 560 | vault_account_id (string): The id of the requested account 561 | """ 562 | 563 | return self._get_request(f"/v1/vault/accounts/{vault_account_id}") 564 | 565 | def get_vault_account_asset(self, vault_account_id, asset_id): 566 | """Gets a single vault account asset 567 | Args: 568 | vault_account_id (string): The id of the requested account 569 | asset_id (string): The symbol of the requested asset (e.g BTC, ETH) 570 | """ 571 | 572 | return self._get_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}") 573 | 574 | def refresh_vault_asset_balance( 575 | self, vault_account_id, asset_id, idempotency_key=None 576 | ): 577 | """Gets a single vault account asset after forcing refresh from the blockchain 578 | Args: 579 | vault_account_id (string): The id of the requested account 580 | asset_id (string): The symbol of the requested asset (e.g BTC, ETH) 581 | """ 582 | 583 | return self._post_request( 584 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/balance", 585 | {}, 586 | idempotency_key, 587 | ) 588 | 589 | def get_deposit_addresses(self, vault_account_id, asset_id): 590 | """Gets deposit addresses for an asset in a vault account 591 | Args: 592 | vault_account_id (string): The id of the requested account 593 | asset_id (string): The symbol of the requested asset (e.g BTC, ETH) 594 | """ 595 | 596 | return self._get_request( 597 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses" 598 | ) 599 | 600 | def get_unspent_inputs(self, vault_account_id, asset_id): 601 | """Gets utxo list for an asset in a vault account 602 | Args: 603 | vault_account_id (string): The id of the requested account 604 | asset_id (string): The symbol of the requested asset (like BTC, DASH and utxo based assets) 605 | """ 606 | 607 | return self._get_request( 608 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/unspent_inputs" 609 | ) 610 | 611 | def generate_new_address( 612 | self, 613 | vault_account_id, 614 | asset_id, 615 | description=None, 616 | customer_ref_id=None, 617 | idempotency_key=None, 618 | ): 619 | """Generates a new address for an asset in a vault account 620 | 621 | Args: 622 | vault_account_id (string): The vault account ID 623 | asset_id (string): The ID of the asset for which to generate the deposit address 624 | description (string, optional): A description for the new address 625 | customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions 626 | idempotency_key (str, optional) 627 | """ 628 | 629 | return self._post_request( 630 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses", 631 | {"description": description or "", "customerRefId": customer_ref_id or ""}, 632 | idempotency_key, 633 | ) 634 | 635 | def set_address_description( 636 | self, vault_account_id, asset_id, address, tag=None, description=None 637 | ): 638 | """Sets the description of an existing address 639 | 640 | Args: 641 | vault_account_id (string): The vault account ID 642 | asset_id (string): The ID of the asset 643 | address (string): The address for which to set the set_address_description 644 | tag (string, optional): The XRP tag, or EOS memo, for which to set the description 645 | description (string, optional): The description to set, or none for no description 646 | """ 647 | if tag: 648 | return self._put_request( 649 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}:{tag}", 650 | {"description": description or ""}, 651 | ) 652 | else: 653 | return self._put_request( 654 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}", 655 | {"description": description or ""}, 656 | ) 657 | 658 | def get_network_connections(self): 659 | """Gets all network connections for your tenant""" 660 | 661 | return self._get_request("/v1/network_connections") 662 | 663 | def create_network_connection( 664 | self, 665 | local_network_id: str, 666 | remote_network_id: str, 667 | routing_policy=None, 668 | idempotency_key=None, 669 | ): 670 | """Creates a network connection 671 | Args: 672 | localNetworkId (str): The local netowrk profile's id 673 | remoteNetworkId (str): The remote network profile's id 674 | routingPolicy (RoutingPolicy): The desired routing policy for the connection 675 | """ 676 | 677 | body = { 678 | "localNetworkId": local_network_id, 679 | "remoteNetworkId": remote_network_id, 680 | "routingPolicy": routing_policy or {}, 681 | } 682 | 683 | return self._post_request(f"/v1/network_connections", body, idempotency_key) 684 | 685 | def get_network_connection_by_id(self, connection_id: str): 686 | """Gets a single network connection 687 | Args: 688 | connection_id (string): The network connection's id 689 | """ 690 | 691 | return self._get_request(f"/v1/network_connections/{connection_id}") 692 | 693 | def remove_network_connection(self, connection_id: str): 694 | """Removes a network connection 695 | Args: 696 | connection_id (string): The network connection's id 697 | """ 698 | 699 | return self._delete_request(f"/v1/network_connections/{connection_id}") 700 | 701 | def set_network_connection_routing_policy( 702 | self, connection_id: str, routing_policy=None 703 | ): 704 | """Sets routing policy for a network connection 705 | Args: 706 | connection_id (string): The network connection's id 707 | routing_policy (routingPolicy): The desired routing policy 708 | """ 709 | 710 | body = {"routingPolicy": routing_policy or {}} 711 | 712 | return self._patch_request( 713 | f"/v1/network_connections/{connection_id}/set_routing_policy", body 714 | ) 715 | 716 | def get_discoverable_network_ids(self): 717 | """Gets all discoverable network profiles""" 718 | 719 | return self._get_request(f"/v1/network_ids") 720 | 721 | def create_network_id(self, name: str, routing_policy=None): 722 | """Creates a new network profile 723 | Args: 724 | name (str): A name for the new network profile 725 | routing_policy (routingPolicy): The desired routing policy for the network 726 | """ 727 | 728 | body = {"name": name, "routingPolicy": routing_policy or {}} 729 | 730 | return self._post_request(f"/v1/network_ids", body) 731 | 732 | def get_network_id(self, network_id: str): 733 | """Gets a single network profile 734 | Args: 735 | network_id (str): The network profile's id 736 | """ 737 | 738 | return self._get_request(f"/v1/network_ids/{network_id}") 739 | 740 | def delete_network_id(self, network_id: str): 741 | """Deletes a single network profile 742 | Args: 743 | network_id (str): The network profile's id 744 | """ 745 | 746 | return self._delete_request(f"/v1/network_ids/{network_id}") 747 | 748 | def set_network_id_discoverability(self, network_id: str, is_discoverable: bool): 749 | """Sets discoverability for network profile 750 | Args: 751 | network_id (str): The network profile's id 752 | is_discoverable: (bool) The desired discoverability to set 753 | """ 754 | 755 | body = {"isDiscoverable": is_discoverable} 756 | 757 | return self._patch_request( 758 | f"/v1/network_ids/{network_id}/set_discoverability", body 759 | ) 760 | 761 | def set_network_id_routing_policy(self, network_id: str, routing_policy): 762 | """Sets routing policy for network profile 763 | Args: 764 | network_id (str): The network profile's id 765 | routing_policy: (routingPolicy) The desired routing policy 766 | """ 767 | 768 | body = {"routingPolicy": routing_policy} 769 | 770 | return self._patch_request( 771 | f"/v1/network_ids/{network_id}/set_routing_policy", body 772 | ) 773 | 774 | def set_network_id_name(self, network_id: str, name: str): 775 | """Sets network profile name 776 | Args: 777 | network_id (str): The network profile's id 778 | name: (str) The desired network profile's name 779 | """ 780 | 781 | body = {"name": name} 782 | 783 | return self._patch_request(f"/v1/network_ids/{network_id}/set_name", body) 784 | 785 | def get_exchange_accounts(self): 786 | """Gets all exchange accounts for your tenant""" 787 | 788 | return self._get_request("/v1/exchange_accounts") 789 | 790 | def get_exchange_accounts_paged(self, paged_exchange_accounts_request_filters: PagedExchangeAccountRequestFilters): 791 | """Gets a page of vault accounts for your tenant according to filters given 792 | 793 | Args: 794 | paged_exchange_accounts_request_filters (object, optional): Possible filters to apply for request 795 | """ 796 | 797 | url = f"/v1/exchange_accounts/paged" 798 | limit, before, after = \ 799 | attrgetter('limit', 'before', 'after')( 800 | paged_exchange_accounts_request_filters) 801 | 802 | params = {} 803 | 804 | if limit is not None: 805 | params['limit'] = limit 806 | 807 | if before is not None: 808 | params['before'] = before 809 | 810 | if after is not None: 811 | params['after'] = after 812 | 813 | if params: 814 | url = url + "?" + urllib.parse.urlencode(params) 815 | 816 | return self._get_request(url) 817 | 818 | def get_exchange_account(self, exchange_account_id): 819 | """Gets an exchange account for your tenant 820 | Args: 821 | exchange_account_id (string): The exchange ID in Fireblocks 822 | """ 823 | 824 | return self._get_request(f"/v1/exchange_accounts/{exchange_account_id}") 825 | 826 | def get_exchange_account_asset(self, exchange_account_id, asset_id): 827 | """Get a specific asset from an exchange account 828 | 829 | Args: 830 | exchange_account_id (string): The exchange ID in Fireblocks 831 | asset_id (string): The asset to transfer 832 | """ 833 | 834 | return self._get_request( 835 | f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}" 836 | ) 837 | 838 | def transfer_to_subaccount( 839 | self, exchange_account_id, subaccount_id, asset_id, amount, idempotency_key=None 840 | ): 841 | """Transfer to a subaccount from a main exchange account 842 | 843 | Args: 844 | exchange_account_id (string): The exchange ID in Fireblocks 845 | subaccount_id (string): The ID of the subaccount in the exchange 846 | asset_id (string): The asset to transfer 847 | amount (double): The amount to transfer 848 | idempotency_key (str, optional) 849 | """ 850 | body = {"subaccountId": subaccount_id, "amount": amount} 851 | 852 | return self._post_request( 853 | f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}/transfer_to_subaccount", 854 | body, 855 | idempotency_key, 856 | ) 857 | 858 | def transfer_from_subaccount( 859 | self, exchange_account_id, subaccount_id, asset_id, amount, idempotency_key=None 860 | ): 861 | """Transfer from a subaccount to a main exchange account 862 | 863 | Args: 864 | exchange_account_id (string): The exchange ID in Fireblocks 865 | subaccount_id (string): The ID of the subaccount in the exchange 866 | asset_id (string): The asset to transfer 867 | amount (double): The amount to transfer 868 | idempotency_key (str, optional) 869 | """ 870 | body = {"subaccountId": subaccount_id, "amount": amount} 871 | 872 | return self._post_request( 873 | f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}/transfer_from_subaccount", 874 | body, 875 | idempotency_key, 876 | ) 877 | 878 | def get_fiat_accounts(self): 879 | """Gets all fiat accounts for your tenant""" 880 | 881 | return self._get_request("/v1/fiat_accounts") 882 | 883 | def get_fiat_account_by_id(self, account_id): 884 | """Gets a single fiat account by ID 885 | 886 | Args: 887 | account_id (string): The fiat account ID 888 | """ 889 | 890 | return self._get_request(f"/v1/fiat_accounts/{account_id}") 891 | 892 | def redeem_to_linked_dda(self, account_id, amount, idempotency_key=None): 893 | """Redeem from a fiat account to a linked DDA 894 | 895 | Args: 896 | account_id (string): The fiat account ID in Fireblocks 897 | amount (double): The amount to transfer 898 | idempotency_key (str, optional) 899 | """ 900 | body = { 901 | "amount": amount, 902 | } 903 | 904 | return self._post_request( 905 | f"/v1/fiat_accounts/{account_id}/redeem_to_linked_dda", 906 | body, 907 | idempotency_key, 908 | ) 909 | 910 | def deposit_from_linked_dda(self, account_id, amount, idempotency_key=None): 911 | """Deposit to a fiat account from a linked DDA 912 | 913 | Args: 914 | account_id (string): The fiat account ID in Fireblocks 915 | amount (double): The amount to transfer 916 | idempotency_key (str, optional) 917 | """ 918 | body = { 919 | "amount": amount, 920 | } 921 | 922 | return self._post_request( 923 | f"/v1/fiat_accounts/{account_id}/deposit_from_linked_dda", 924 | body, 925 | idempotency_key, 926 | ) 927 | 928 | def get_transactions_with_page_info( 929 | self, 930 | before=0, 931 | after=None, 932 | status=None, 933 | limit=None, 934 | txhash=None, 935 | assets=None, 936 | source_type=None, 937 | source_id=None, 938 | dest_type=None, 939 | dest_id=None, 940 | next_or_previous_path=None, 941 | ): 942 | """Gets a list of transactions matching the given filters or path. 943 | Note that "next_or_previous_path" is mutually exclusive with other parameters. 944 | If you wish to iterate over the nextPage/prevPage pages, please provide only the "next_or_previous_path" parameter from `pageDetails` response 945 | example: 946 | get_transactions_with_page_info(next_or_previous_path=response[pageDetails][nextPage]) 947 | 948 | Args: 949 | before (int, optional): Only gets transactions created before given timestamp (in milliseconds) 950 | after (int, optional): Only gets transactions created after given timestamp (in milliseconds) 951 | status (str, optional): Only gets transactions with the specified status, which should be one of the following: 952 | SUBMITTED, QUEUED, PENDING_SIGNATURE, PENDING_AUTHORIZATION, PENDING_3RD_PARTY_MANUAL_APPROVAL, 953 | PENDING_3RD_PARTY, BROADCASTING, CONFIRMING, COMPLETED, PENDING_AML_CHECKUP, PARTIALLY_COMPLETED, 954 | CANCELLING, CANCELLED, REJECTED, FAILED, TIMEOUT, BLOCKED 955 | limit (int, optional): Limit the amount of returned results. If not specified, a limit of 200 results will be used 956 | txhash (str, optional): Only gets transactions with the specified txHash 957 | assets (str, optional): Filter results for specified assets 958 | source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: 959 | VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN, UNKNOWN_PEER, FIAT_ACCOUNT, 960 | NETWORK_CONNECTION, COMPOUND 961 | source_id (str, optional): Only gets transactions with given source_id 962 | dest_type (str, optional): Only gets transactions with given dest_type, which should be one of the following: 963 | VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, 964 | NETWORK_CONNECTION, COMPOUND 965 | dest_id (str, optional): Only gets transactions with given dest_id 966 | next_or_previous_path (str, optional): get transactions matching the path, provided from pageDetails 967 | """ 968 | if next_or_previous_path is not None: 969 | if not next_or_previous_path: 970 | return { 971 | "transactions": [], 972 | "pageDetails": {"prevPage": "", "nextPage": ""}, 973 | } 974 | index = next_or_previous_path.index("/v1/") 975 | length = len(next_or_previous_path) - 1 976 | suffix_path = next_or_previous_path[index:length] 977 | return self._get_request(suffix_path, True) 978 | else: 979 | return self._get_transactions( 980 | before, 981 | after, 982 | status, 983 | limit, 984 | None, 985 | txhash, 986 | assets, 987 | source_type, 988 | source_id, 989 | dest_type, 990 | dest_id, 991 | True, 992 | ) 993 | 994 | def get_transactions( 995 | self, 996 | before=0, 997 | after=0, 998 | status=None, 999 | limit=None, 1000 | order_by=None, 1001 | txhash=None, 1002 | assets=None, 1003 | source_type=None, 1004 | source_id=None, 1005 | dest_type=None, 1006 | dest_id=None, 1007 | ): 1008 | """Gets a list of transactions matching the given filters 1009 | 1010 | Args: 1011 | before (int, optional): Only gets transactions created before given timestamp (in milliseconds) 1012 | after (int, optional): Only gets transactions created after given timestamp (in milliseconds) 1013 | status (str, optional): Only gets transactions with the specified status, which should be one of the following: 1014 | SUBMITTED, QUEUED, PENDING_SIGNATURE, PENDING_AUTHORIZATION, PENDING_3RD_PARTY_MANUAL_APPROVAL, 1015 | PENDING_3RD_PARTY, BROADCASTING, CONFIRMING, COMPLETED, PENDING_AML_CHECKUP, PARTIALLY_COMPLETED, 1016 | CANCELLING, CANCELLED, REJECTED, FAILED, TIMEOUT, BLOCKED 1017 | limit (int, optional): Limit the amount of returned results. If not specified, a limit of 200 results will be used 1018 | order_by (str, optional): Determines the order of the returned results. Possible values are 'createdAt' or 'lastUpdated' 1019 | txhash (str, optional): Only gets transactions with the specified txHash 1020 | assets (str, optional): Filter results for specified assets 1021 | source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: 1022 | VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN, UNKNOWN_PEER, FIAT_ACCOUNT, 1023 | NETWORK_CONNECTION, COMPOUND 1024 | source_id (str, optional): Only gets transactions with given source_id 1025 | dest_type (str, optional): Only gets transactions with given dest_type, which should be one of the following: 1026 | VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, 1027 | NETWORK_CONNECTION, COMPOUND 1028 | dest_id (str, optional): Only gets transactions with given dest_id 1029 | """ 1030 | return self._get_transactions( 1031 | before, 1032 | after, 1033 | status, 1034 | limit, 1035 | order_by, 1036 | txhash, 1037 | assets, 1038 | source_type, 1039 | source_id, 1040 | dest_type, 1041 | dest_id, 1042 | ) 1043 | 1044 | def _get_transactions( 1045 | self, 1046 | before, 1047 | after, 1048 | status, 1049 | limit, 1050 | order_by, 1051 | txhash, 1052 | assets, 1053 | source_type, 1054 | source_id, 1055 | dest_type, 1056 | dest_id, 1057 | page_mode=False, 1058 | ): 1059 | path = "/v1/transactions" 1060 | params = {} 1061 | 1062 | if status and status not in TRANSACTION_STATUS_TYPES: 1063 | raise FireblocksApiException("Got invalid transaction type: " + status) 1064 | 1065 | if before: 1066 | params["before"] = before 1067 | if after: 1068 | params["after"] = after 1069 | if status: 1070 | params["status"] = status 1071 | if limit: 1072 | params["limit"] = limit 1073 | if order_by: 1074 | params["orderBy"] = order_by 1075 | if txhash: 1076 | params["txHash"] = txhash 1077 | if assets: 1078 | params["assets"] = assets 1079 | if source_type: 1080 | params["sourceType"] = source_type 1081 | if source_id: 1082 | params["sourceId"] = source_id 1083 | if dest_type: 1084 | params["destType"] = dest_type 1085 | if dest_id: 1086 | params["destId"] = dest_id 1087 | if params: 1088 | path = path + "?" + urllib.parse.urlencode(params) 1089 | 1090 | return self._get_request(path, page_mode) 1091 | 1092 | def get_internal_wallets(self): 1093 | """Gets all internal wallets for your tenant""" 1094 | 1095 | return self._get_request("/v1/internal_wallets") 1096 | 1097 | def get_internal_wallet(self, wallet_id): 1098 | """Gets an internal wallet from your tenant 1099 | Args: 1100 | wallet_id (str): The wallet id to query 1101 | """ 1102 | 1103 | return self._get_request(f"/v1/internal_wallets/{wallet_id}") 1104 | 1105 | def get_internal_wallet_assets( 1106 | self, 1107 | wallet_id, 1108 | page_size: Optional[int] = None, 1109 | page_cursor: Optional[str] = None, 1110 | ): 1111 | """Gets a paginated response of assets for an internal wallet from your tenant 1112 | Args: 1113 | wallet_id (str): The wallet id to query 1114 | page_size (int): Number of assets to return per page (default=50, max=200) 1115 | page_cursor (str): Cursor for pagination 1116 | """ 1117 | 1118 | params = {} 1119 | 1120 | if page_size: 1121 | params["pageSize"] = page_size 1122 | if page_cursor: 1123 | params["pageCursor"] = page_cursor 1124 | 1125 | return self._get_request( 1126 | f"/v1/internal_wallets/{wallet_id}/assets", query_params=params 1127 | ) 1128 | 1129 | def get_internal_wallet_asset(self, wallet_id, asset_id): 1130 | """Gets an asset from an internal wallet from your tenant 1131 | Args: 1132 | wallet_id (str): The wallet id to query 1133 | asset_id (str): The asset id to query 1134 | """ 1135 | return self._get_request(f"/v1/internal_wallets/{wallet_id}/{asset_id}") 1136 | 1137 | def get_external_wallets(self): 1138 | """Gets all external wallets for your tenant""" 1139 | 1140 | return self._get_request("/v1/external_wallets") 1141 | 1142 | def get_external_wallet(self, wallet_id): 1143 | """Gets an external wallet from your tenant 1144 | Args: 1145 | wallet_id (str): The wallet id to query 1146 | """ 1147 | 1148 | return self._get_request(f"/v1/external_wallets/{wallet_id}") 1149 | 1150 | def get_external_wallet_asset(self, wallet_id, asset_id): 1151 | """Gets an asset from an external wallet from your tenant 1152 | Args: 1153 | wallet_id (str): The wallet id to query 1154 | asset_id (str): The asset id to query 1155 | """ 1156 | return self._get_request(f"/v1/external_wallets/{wallet_id}/{asset_id}") 1157 | 1158 | def get_contract_wallets(self): 1159 | """Gets all contract wallets for your tenant""" 1160 | return self._get_request(f"/v1/contracts") 1161 | 1162 | def get_contract_wallet(self, wallet_id): 1163 | """Gets a single contract wallet 1164 | 1165 | Args: 1166 | wallet_id (str): The contract wallet ID 1167 | """ 1168 | return self._get_request(f"/v1/contracts/{wallet_id}") 1169 | 1170 | def get_contract_wallet_asset(self, wallet_id, asset_id): 1171 | """Gets a single contract wallet asset 1172 | 1173 | Args: 1174 | wallet_id (str): The contract wallet ID 1175 | asset_id (str): The asset ID 1176 | """ 1177 | return self._get_request(f"/v1/contracts/{wallet_id}/{asset_id}") 1178 | 1179 | def get_transaction_by_id(self, txid): 1180 | """Gets detailed information for a single transaction 1181 | 1182 | Args: 1183 | txid (str): The transaction id to query 1184 | """ 1185 | 1186 | return self._get_request(f"/v1/transactions/{txid}") 1187 | 1188 | def get_transaction_by_external_id(self, external_tx_id): 1189 | """Gets detailed information for a single transaction 1190 | 1191 | Args: 1192 | external_tx_id (str): The external id of the transaction 1193 | """ 1194 | 1195 | return self._get_request(f"/v1/transactions/external_tx_id/{urllib.parse.quote(external_tx_id, safe='')}") 1196 | 1197 | def get_fee_for_asset(self, asset_id): 1198 | """Gets the estimated fees for an asset 1199 | 1200 | Args: 1201 | asset_id (str): The asset symbol (e.g BTC, ETH) 1202 | """ 1203 | 1204 | return self._get_request(f"/v1/estimate_network_fee?assetId={asset_id}") 1205 | 1206 | def estimate_fee_for_transaction( 1207 | self, 1208 | asset_id, 1209 | amount, 1210 | source, 1211 | destination=None, 1212 | tx_type=TRANSACTION_TRANSFER, 1213 | idempotency_key=None, 1214 | destinations=None, 1215 | ): 1216 | """Estimates transaction fee 1217 | 1218 | Args: 1219 | asset_id (str): The asset symbol (e.g BTC, ETH) 1220 | source (TransferPeerPath): The transaction source 1221 | destination (DestinationTransferPeerPath, optional): The transfer destination. 1222 | amount (str): The amount 1223 | tx_type (str, optional): Transaction type: either TRANSFER, MINT, BURN, TRANSACTION_SUPPLY_TO_COMPOUND or TRANSACTION_REDEEM_FROM_COMPOUND. Default is TRANSFER. 1224 | idempotency_key (str, optional) 1225 | destinations (list of TransactionDestination objects, optional): For UTXO based assets, send to multiple destinations which should be specified using this field. 1226 | """ 1227 | 1228 | if tx_type not in TRANSACTION_TYPES: 1229 | raise FireblocksApiException("Got invalid transaction type: " + tx_type) 1230 | 1231 | if not isinstance(source, TransferPeerPath): 1232 | raise FireblocksApiException( 1233 | "Expected transaction source of type TransferPeerPath, but got type: " 1234 | + type(source) 1235 | ) 1236 | 1237 | body = { 1238 | "assetId": asset_id, 1239 | "amount": amount, 1240 | "source": source.__dict__, 1241 | "operation": tx_type, 1242 | } 1243 | 1244 | if destination: 1245 | if not isinstance( 1246 | destination, (TransferPeerPath, DestinationTransferPeerPath) 1247 | ): 1248 | raise FireblocksApiException( 1249 | "Expected transaction fee estimation destination of type DestinationTransferPeerPath or TransferPeerPath, but got type: " 1250 | + type(destination) 1251 | ) 1252 | body["destination"] = destination.__dict__ 1253 | 1254 | if destinations: 1255 | if any([not isinstance(x, TransactionDestination) for x in destinations]): 1256 | raise FireblocksApiException( 1257 | "Expected destinations of type TransactionDestination" 1258 | ) 1259 | body["destinations"] = [dest.__dict__ for dest in destinations] 1260 | 1261 | return self._post_request( 1262 | "/v1/transactions/estimate_fee", body, idempotency_key 1263 | ) 1264 | 1265 | def cancel_transaction_by_id(self, txid, idempotency_key=None): 1266 | """Cancels the selected transaction 1267 | 1268 | Args: 1269 | txid (str): The transaction id to cancel 1270 | idempotency_key (str, optional) 1271 | """ 1272 | 1273 | return self._post_request( 1274 | f"/v1/transactions/{txid}/cancel", idempotency_key=idempotency_key 1275 | ) 1276 | 1277 | def drop_transaction( 1278 | self, txid, fee_level=None, requested_fee=None, idempotency_key=None 1279 | ): 1280 | """Drops the selected transaction from the blockchain by replacing it with a 0 ETH transaction to itself 1281 | 1282 | Args: 1283 | txid (str): The transaction id to drop 1284 | fee_level (str): The fee level of the dropping transaction 1285 | requested_fee (str, optional): Requested fee for transaction 1286 | idempotency_key (str, optional) 1287 | """ 1288 | body = {} 1289 | 1290 | if fee_level: 1291 | body["feeLevel"] = fee_level 1292 | 1293 | if requested_fee: 1294 | body["requestedFee"] = requested_fee 1295 | 1296 | return self._post_request( 1297 | f"/v1/transactions/{txid}/drop", body, idempotency_key 1298 | ) 1299 | 1300 | def create_vault_account( 1301 | self, 1302 | name, 1303 | hiddenOnUI=False, 1304 | customer_ref_id=None, 1305 | autoFuel=False, 1306 | idempotency_key=None, 1307 | ): 1308 | """Creates a new vault account. 1309 | 1310 | Args: 1311 | name (str): A name for the new vault account 1312 | hiddenOnUI (boolean): Specifies whether the vault account is hidden from the web console, false by default 1313 | customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions 1314 | idempotency_key (str, optional) 1315 | """ 1316 | body = {"name": name, "hiddenOnUI": hiddenOnUI, "autoFuel": autoFuel} 1317 | 1318 | if customer_ref_id: 1319 | body["customerRefId"] = customer_ref_id 1320 | 1321 | return self._post_request("/v1/vault/accounts", body, idempotency_key) 1322 | 1323 | def hide_vault_account(self, vault_account_id, idempotency_key=None): 1324 | """Hides the vault account from being visible in the web console 1325 | 1326 | Args: 1327 | vault_account_id (str): The vault account Id 1328 | idempotency_key (str, optional) 1329 | """ 1330 | return self._post_request( 1331 | f"/v1/vault/accounts/{vault_account_id}/hide", 1332 | idempotency_key=idempotency_key, 1333 | ) 1334 | 1335 | def unhide_vault_account(self, vault_account_id, idempotency_key=None): 1336 | """Returns the vault account to being visible in the web console 1337 | 1338 | Args: 1339 | vault_account_id (str): The vault account Id 1340 | idempotency_key (str, optional) 1341 | """ 1342 | return self._post_request( 1343 | f"/v1/vault/accounts/{vault_account_id}/unhide", 1344 | idempotency_key=idempotency_key, 1345 | ) 1346 | 1347 | def freeze_transaction_by_id(self, txId, idempotency_key=None): 1348 | """Freezes the selected transaction 1349 | 1350 | Args: 1351 | txId (str): The transaction ID to freeze 1352 | idempotency_key (str, optional) 1353 | """ 1354 | return self._post_request( 1355 | f"/v1/transactions/{txId}/freeze", idempotency_key=idempotency_key 1356 | ) 1357 | 1358 | def unfreeze_transaction_by_id(self, txId, idempotency_key=None): 1359 | """Unfreezes the selected transaction 1360 | 1361 | Args: 1362 | txId (str): The transaction ID to unfreeze 1363 | idempotency_key (str, optional) 1364 | """ 1365 | return self._post_request( 1366 | f"/v1/transactions/{txId}/unfreeze", idempotency_key=idempotency_key 1367 | ) 1368 | 1369 | def update_vault_account(self, vault_account_id, name): 1370 | """Updates a vault account. 1371 | 1372 | Args: 1373 | vault_account_id (str): The vault account Id 1374 | name (str): A new name for the vault account 1375 | """ 1376 | body = { 1377 | "name": name, 1378 | } 1379 | 1380 | return self._put_request(f"/v1/vault/accounts/{vault_account_id}", body) 1381 | 1382 | def register_new_asset(self, blockchainId, address, symbol=None, idempotency_key=None): 1383 | """Registers new asset 1384 | 1385 | Args: 1386 | blockchainId (str): Native asset of blockchain 1387 | address (str): Asset contract address 1388 | symbol (str) optional: Asset symbol 1389 | idempotency_key (str, optional) 1390 | """ 1391 | body = { 1392 | "blockchainId": blockchainId, 1393 | "address": address, 1394 | "symbol": symbol, 1395 | } 1396 | 1397 | return self._post_request("/v1/assets", body, idempotency_key) 1398 | 1399 | def list_assets( 1400 | self, 1401 | blockchain_id: str = None, 1402 | asset_class: AssetClassValues = None, 1403 | symbol: str = None, 1404 | scope: AssetScopeValues = None, 1405 | deprecated: bool = None, 1406 | page_cursor: str = None, 1407 | page_size: int = None, 1408 | ids: List[str] = None, 1409 | ): 1410 | """ 1411 | List assets 1412 | 1413 | Args: 1414 | blockchain_id (str): Blockchain id of the assets 1415 | asset_class (AssetClassValues): Assets class 1416 | symbol (str): Assets onchain symbol 1417 | scope (AssetScopeValues): Scope of the assets 1418 | deprecated (bool): Are assets deprecated 1419 | page_cursor (str): Next page cursor to fetch 1420 | page_size (int): Items per page 1421 | ids (List[str]): Asset ids (max 100) 1422 | """ 1423 | 1424 | url = "/v1/assets" 1425 | 1426 | params = {} 1427 | 1428 | if blockchain_id: 1429 | params["blockchainId"] = blockchain_id 1430 | if asset_class: 1431 | params["assetClass"] = asset_class.value 1432 | if symbol: 1433 | params["symbol"] = symbol 1434 | if scope: 1435 | params["scope"] = scope.value 1436 | if deprecated is not None: 1437 | params["deprecated"] = "true" if deprecated is True else "false" 1438 | if ids is not None and len(ids) > 0: 1439 | params["ids"] = ids 1440 | if page_cursor: 1441 | params["pageCursor"] = page_cursor 1442 | if page_size: 1443 | params["pageSize"] = page_size 1444 | 1445 | if params: 1446 | url = url + "?" + urllib.parse.urlencode(params, doseq=True) 1447 | 1448 | return self._get_request(url) 1449 | 1450 | def get_asset_by_id(self, asset_id: str): 1451 | """ 1452 | Get an asset 1453 | 1454 | Args: 1455 | asset_id (str): The ID or legacyId of the asset 1456 | """ 1457 | 1458 | return self._get_request(f"/v1/assets/{asset_id}") 1459 | 1460 | def list_blockchains( 1461 | self, 1462 | protocol: str = None, 1463 | deprecated: bool = None, 1464 | test: bool = None, 1465 | page_cursor: str = None, 1466 | page_size: int = None, 1467 | ids: List[str] = None, 1468 | ): 1469 | """ 1470 | List blockchains 1471 | 1472 | Args: 1473 | protocol (str): Blockchain protocol 1474 | deprecated (bool): Is blockchain deprecated 1475 | test (bool): Is test blockchain 1476 | page_cursor (str): Page cursor to fetch 1477 | page_size (int): Items per page (max 500) 1478 | ids (List[str]): Blockchain ids (max 100) 1479 | """ 1480 | 1481 | url = "/v1/blockchains" 1482 | 1483 | params = {} 1484 | 1485 | if protocol: 1486 | params["protocol"] = protocol 1487 | if deprecated is not None: 1488 | params["deprecated"] = "true" if deprecated is True else "false" 1489 | if test is not None: 1490 | params["test"] = "true" if test is True else "false" 1491 | if ids is not None and len(ids) > 0: 1492 | params["ids"] = ids 1493 | if page_cursor: 1494 | params["pageCursor"] = page_cursor 1495 | if page_size: 1496 | params["pageSize"] = page_size 1497 | 1498 | if params: 1499 | url = url + "?" + urllib.parse.urlencode(params, doseq=True) 1500 | 1501 | return self._get_request(url) 1502 | 1503 | def get_blockchain_by_id(self, blockchain_id: str): 1504 | """ 1505 | Get an blockchain 1506 | 1507 | Args: 1508 | blockchain_id (str): The ID or legacyId of the blockchain 1509 | """ 1510 | 1511 | return self._get_request(f"/v1/blockchains/{blockchain_id}") 1512 | 1513 | def create_vault_asset(self, vault_account_id, asset_id, idempotency_key=None): 1514 | """Creates a new asset within an existing vault account 1515 | 1516 | Args: 1517 | vault_account_id (str): The vault account Id 1518 | asset_id (str): The symbol of the asset to add (e.g BTC, ETH) 1519 | idempotency_key (str, optional) 1520 | """ 1521 | 1522 | return self._post_request( 1523 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}", 1524 | idempotency_key=idempotency_key, 1525 | ) 1526 | 1527 | def activate_vault_asset(self, vault_account_id, asset_id, idempotency_key=None): 1528 | """Retry to create a vault asset for a vault asset that failed 1529 | 1530 | Args: 1531 | vault_account_id (str): The vault account Id 1532 | asset_id (str): The symbol of the asset to add (e.g BTC, ETH) 1533 | idempotency_key (str, optional) 1534 | """ 1535 | 1536 | return self._post_request( 1537 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/activate", 1538 | idempotency_key=idempotency_key, 1539 | ) 1540 | 1541 | def set_vault_account_customer_ref_id( 1542 | self, vault_account_id, customer_ref_id, idempotency_key=None 1543 | ): 1544 | """Sets an AML/KYT customer reference ID for the vault account 1545 | 1546 | Args: 1547 | vault_account_id (str): The vault account Id 1548 | customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions 1549 | idempotency_key (str, optional) 1550 | """ 1551 | 1552 | return self._post_request( 1553 | f"/v1/vault/accounts/{vault_account_id}/set_customer_ref_id", 1554 | {"customerRefId": customer_ref_id or ""}, 1555 | idempotency_key, 1556 | ) 1557 | 1558 | def set_vault_account_customer_ref_id_for_address( 1559 | self, 1560 | vault_account_id, 1561 | asset_id, 1562 | address, 1563 | customer_ref_id=None, 1564 | idempotency_key=None, 1565 | ): 1566 | """Sets an AML/KYT customer reference ID for the given address 1567 | 1568 | Args: 1569 | vault_account_id (str): The vault account Id 1570 | asset_id (str): The symbol of the asset to add (e.g BTC, ETH) 1571 | address (string): The address for which to set the customer reference id 1572 | customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions 1573 | idempotency_key (str, optional) 1574 | """ 1575 | 1576 | return self._post_request( 1577 | f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}/set_customer_ref_id", 1578 | {"customerRefId": customer_ref_id or ""}, 1579 | idempotency_key, 1580 | ) 1581 | 1582 | def create_contract_wallet(self, name, idempotency_key=None): 1583 | """Creates a new contract wallet 1584 | 1585 | Args: 1586 | name (str): A name for the new contract wallet 1587 | """ 1588 | return self._post_request("/v1/contracts", {"name": name}, idempotency_key) 1589 | 1590 | def create_contract_wallet_asset( 1591 | self, wallet_id, assetId, address, tag=None, idempotency_key=None 1592 | ): 1593 | """Creates a new contract wallet asset 1594 | 1595 | Args: 1596 | wallet_id (str): The wallet id 1597 | assetId (str): The asset to add 1598 | address (str): The wallet address 1599 | tag (str): (for ripple only) The ripple account tag 1600 | """ 1601 | return self._post_request( 1602 | f"/v1/contracts/{wallet_id}/{assetId}", 1603 | {"address": address, "tag": tag}, 1604 | idempotency_key, 1605 | ) 1606 | 1607 | def create_external_wallet(self, name, customer_ref_id=None, idempotency_key=None): 1608 | """Creates a new external wallet 1609 | 1610 | Args: 1611 | name (str): A name for the new external wallet 1612 | customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions 1613 | idempotency_key (str, optional) 1614 | """ 1615 | 1616 | return self._post_request( 1617 | "/v1/external_wallets", 1618 | {"name": name, "customerRefId": customer_ref_id or ""}, 1619 | idempotency_key, 1620 | ) 1621 | 1622 | def create_internal_wallet(self, name, customer_ref_id=None, idempotency_key=None): 1623 | """Creates a new internal wallet 1624 | 1625 | Args: 1626 | name (str): A name for the new internal wallet 1627 | customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions 1628 | idempotency_key (str, optional) 1629 | """ 1630 | 1631 | return self._post_request( 1632 | "/v1/internal_wallets", 1633 | {"name": name, "customerRefId": customer_ref_id or ""}, 1634 | idempotency_key, 1635 | ) 1636 | 1637 | def create_external_wallet_asset( 1638 | self, wallet_id, asset_id, address, tag=None, idempotency_key=None 1639 | ): 1640 | """Creates a new asset within an exiting external wallet 1641 | 1642 | Args: 1643 | wallet_id (str): The wallet id 1644 | asset_id (str): The symbol of the asset to add (e.g BTC, ETH) 1645 | address (str): The wallet address 1646 | tag (str, optional): (for ripple only) The ripple account tag 1647 | idempotency_key (str, optional) 1648 | """ 1649 | 1650 | body = {"address": address} 1651 | if tag: 1652 | body["tag"] = tag 1653 | 1654 | return self._post_request( 1655 | f"/v1/external_wallets/{wallet_id}/{asset_id}", body, idempotency_key 1656 | ) 1657 | 1658 | def create_internal_wallet_asset( 1659 | self, wallet_id, asset_id, address, tag=None, idempotency_key=None 1660 | ): 1661 | """Creates a new asset within an exiting internal wallet 1662 | 1663 | Args: 1664 | wallet_id (str): The wallet id 1665 | asset_id (str): The symbol of the asset to add (e.g BTC, ETH) 1666 | address (str): The wallet address 1667 | tag (str, optional): (for ripple only) The ripple account tag 1668 | idempotency_key (str, optional) 1669 | """ 1670 | 1671 | body = {"address": address} 1672 | if tag: 1673 | body["tag"] = tag 1674 | 1675 | return self._post_request( 1676 | f"/v1/internal_wallets/{wallet_id}/{asset_id}", body, idempotency_key 1677 | ) 1678 | 1679 | def create_transaction( 1680 | self, 1681 | asset_id=None, 1682 | amount=None, 1683 | source=None, 1684 | destination=None, 1685 | fee=None, 1686 | gas_price=None, 1687 | wait_for_status=False, 1688 | tx_type=TRANSACTION_TRANSFER, 1689 | note=None, 1690 | network_fee=None, 1691 | customer_ref_id=None, 1692 | replace_tx_by_hash=None, 1693 | extra_parameters=None, 1694 | destinations=None, 1695 | fee_level=None, 1696 | fail_on_low_fee=None, 1697 | max_fee=None, 1698 | max_total_fee=None, 1699 | gas_limit=None, 1700 | idempotency_key=None, 1701 | external_tx_id=None, 1702 | treat_as_gross_amount=None, 1703 | force_sweep=None, 1704 | priority_fee=None, 1705 | ): 1706 | """Creates a new transaction 1707 | 1708 | Args: 1709 | asset_id (str, optional): The asset symbol (e.g BTC, ETH) 1710 | source (TransferPeerPath, optional): The transfer source 1711 | destination (DestinationTransferPeerPath, optional): The transfer destination. Leave empty (None) if the transaction has no destination 1712 | amount (double): The amount 1713 | fee (double, optional): Sathoshi/Latoshi per byte. 1714 | gas_price (number, optional): gasPrice for ETH and ERC-20 transactions. 1715 | wait_for_status (bool, optional): If true, waits for transaction status. Default is false. 1716 | tx_type (str, optional): Transaction type: either TRANSFER, MINT, BURN, TRANSACTION_SUPPLY_TO_COMPOUND or TRANSACTION_REDEEM_FROM_COMPOUND. Default is TRANSFER. 1717 | note (str, optional): A customer note that can be associated with the transaction. 1718 | network_fee (str, optional): Transaction blockchain fee (For Ethereum, you can't pass gasPrice, gasLimit and networkFee all together) 1719 | customer_ref_id (string, optional): The ID for AML providers to associate the owner of funds with transactions 1720 | extra_parameters (object, optional) 1721 | destinations (list of TransactionDestination objects, optional): For UTXO based assets, send to multiple destinations which should be specified using this field. 1722 | fee_level (FeeLevel, optional): Transaction fee level: either HIGH, MEDIUM, LOW. 1723 | fail_on_low_fee (bool, optional): False by default, if set to true and MEDIUM fee level is higher than the one specified in the transaction, the transction will fail. 1724 | max_fee (str, optional): The maximum fee (gas price or fee per byte) that should be payed for the transaction. 1725 | gas_limit (number, optional): For ETH-based assets only. 1726 | idempotency_key (str, optional) 1727 | external_tx_id (str, optional): A unique key for transaction provided externally 1728 | treat_as_gross_amount (bool, optional): Determine if amount should be treated as gross or net 1729 | force_sweep (bool, optional): Determine if transaction should be treated as a forced sweep 1730 | priority_fee (number, optional): The priority fee of Ethereum transaction according to EIP-1559 1731 | """ 1732 | 1733 | if tx_type not in TRANSACTION_TYPES: 1734 | raise FireblocksApiException("Got invalid transaction type: " + tx_type) 1735 | 1736 | if source: 1737 | if not isinstance(source, TransferPeerPath): 1738 | raise FireblocksApiException( 1739 | "Expected transaction source of type TransferPeerPath, but got type: " 1740 | + type(source) 1741 | ) 1742 | 1743 | body = { 1744 | "waitForStatus": wait_for_status, 1745 | "operation": tx_type, 1746 | } 1747 | 1748 | if asset_id: 1749 | body["assetId"] = asset_id 1750 | 1751 | if source: 1752 | body["source"] = source.__dict__ 1753 | 1754 | if amount is not None: 1755 | body["amount"] = amount 1756 | 1757 | if fee: 1758 | body["fee"] = fee 1759 | 1760 | if fee_level: 1761 | if fee_level not in FEE_LEVEL: 1762 | raise FireblocksApiException("Got invalid fee level: " + fee_level) 1763 | body["feeLevel"] = fee_level 1764 | 1765 | if max_fee: 1766 | body["maxFee"] = max_fee 1767 | 1768 | if max_total_fee: 1769 | body["maxTotalFee"] = max_total_fee 1770 | 1771 | if fail_on_low_fee: 1772 | body["failOnLowFee"] = fail_on_low_fee 1773 | 1774 | if gas_price: 1775 | body["gasPrice"] = str(gas_price) 1776 | 1777 | if gas_limit: 1778 | body["gasLimit"] = str(gas_limit) 1779 | 1780 | if note: 1781 | body["note"] = note 1782 | 1783 | if destination: 1784 | if not isinstance( 1785 | destination, (TransferPeerPath, DestinationTransferPeerPath) 1786 | ): 1787 | raise FireblocksApiException( 1788 | "Expected transaction destination of type DestinationTransferPeerPath or TransferPeerPath, but got type: " 1789 | + type(destination) 1790 | ) 1791 | body["destination"] = destination.__dict__ 1792 | 1793 | if network_fee: 1794 | body["networkFee"] = network_fee 1795 | 1796 | if customer_ref_id: 1797 | body["customerRefId"] = customer_ref_id 1798 | 1799 | if replace_tx_by_hash: 1800 | body["replaceTxByHash"] = replace_tx_by_hash 1801 | 1802 | if treat_as_gross_amount: 1803 | body["treatAsGrossAmount"] = treat_as_gross_amount 1804 | 1805 | if destinations: 1806 | if any([not isinstance(x, TransactionDestination) for x in destinations]): 1807 | raise FireblocksApiException( 1808 | "Expected destinations of type TransactionDestination" 1809 | ) 1810 | 1811 | body["destinations"] = [dest.__dict__ for dest in destinations] 1812 | 1813 | if extra_parameters: 1814 | body["extraParameters"] = extra_parameters 1815 | 1816 | if external_tx_id: 1817 | body["externalTxId"] = external_tx_id 1818 | 1819 | if force_sweep: 1820 | body["forceSweep"] = force_sweep 1821 | 1822 | if priority_fee: 1823 | body["priorityFee"] = priority_fee 1824 | 1825 | return self._post_request("/v1/transactions", body, idempotency_key) 1826 | 1827 | def delete_contract_wallet(self, wallet_id): 1828 | """Deletes a single contract wallet 1829 | 1830 | Args: 1831 | wallet_id (string): The contract wallet ID 1832 | """ 1833 | return self._delete_request(f"/v1/contracts/{wallet_id}") 1834 | 1835 | def delete_contract_wallet_asset(self, wallet_id, asset_id): 1836 | """Deletes a single contract wallet 1837 | 1838 | Args: 1839 | wallet_id (string): The contract wallet ID 1840 | asset_id (string): The asset ID 1841 | """ 1842 | 1843 | return self._delete_request(f"/v1/contracts/{wallet_id}/{asset_id}") 1844 | 1845 | def delete_internal_wallet(self, wallet_id): 1846 | """Deletes a single internal wallet 1847 | 1848 | Args: 1849 | wallet_id (string): The internal wallet ID 1850 | """ 1851 | 1852 | return self._delete_request(f"/v1/internal_wallets/{wallet_id}") 1853 | 1854 | def delete_external_wallet(self, wallet_id): 1855 | """Deletes a single external wallet 1856 | 1857 | Args: 1858 | wallet_id (string): The external wallet ID 1859 | """ 1860 | 1861 | return self._delete_request(f"/v1/external_wallets/{wallet_id}") 1862 | 1863 | def delete_internal_wallet_asset(self, wallet_id, asset_id): 1864 | """Deletes a single asset from an internal wallet 1865 | 1866 | Args: 1867 | wallet_id (string): The internal wallet ID 1868 | asset_id (string): The asset ID 1869 | """ 1870 | 1871 | return self._delete_request(f"/v1/internal_wallets/{wallet_id}/{asset_id}") 1872 | 1873 | def delete_external_wallet_asset(self, wallet_id, asset_id): 1874 | """Deletes a single asset from an external wallet 1875 | 1876 | Args: 1877 | wallet_id (string): The external wallet ID 1878 | asset_id (string): The asset ID 1879 | """ 1880 | 1881 | return self._delete_request(f"/v1/external_wallets/{wallet_id}/{asset_id}") 1882 | 1883 | def set_customer_ref_id_for_internal_wallet( 1884 | self, wallet_id, customer_ref_id=None, idempotency_key=None 1885 | ): 1886 | """Sets an AML/KYT customer reference ID for the specific internal wallet 1887 | 1888 | Args: 1889 | wallet_id (string): The external wallet ID 1890 | customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions 1891 | idempotency_key (str, optional) 1892 | """ 1893 | 1894 | return self._post_request( 1895 | f"/v1/internal_wallets/{wallet_id}/set_customer_ref_id", 1896 | {"customerRefId": customer_ref_id or ""}, 1897 | idempotency_key, 1898 | ) 1899 | 1900 | def set_customer_ref_id_for_external_wallet( 1901 | self, wallet_id, customer_ref_id=None, idempotency_key=None 1902 | ): 1903 | """Sets an AML/KYT customer reference ID for the specific external wallet 1904 | 1905 | Args: 1906 | wallet_id (string): The external wallet ID 1907 | customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions 1908 | idempotency_key (str, optional) 1909 | """ 1910 | 1911 | return self._post_request( 1912 | f"/v1/external_wallets/{wallet_id}/set_customer_ref_id", 1913 | {"customerRefId": customer_ref_id or ""}, 1914 | idempotency_key, 1915 | ) 1916 | 1917 | def get_transfer_tickets(self): 1918 | """Gets all transfer tickets of your tenant""" 1919 | 1920 | return self._get_request("/v1/transfer_tickets") 1921 | 1922 | def create_transfer_ticket( 1923 | self, terms, external_ticket_id=None, description=None, idempotency_key=None 1924 | ): 1925 | """Creates a new transfer ticket 1926 | 1927 | Args: 1928 | terms (list of TransferTicketTerm objects): The list of TransferTicketTerm 1929 | external_ticket_id (str, optional): The ID for of the transfer ticket on customer's platform 1930 | description (str, optional): A description for the new ticket 1931 | idempotency_key (str, optional) 1932 | """ 1933 | 1934 | body = {} 1935 | 1936 | if external_ticket_id: 1937 | body["externalTicketId"] = external_ticket_id 1938 | 1939 | if description: 1940 | body["description"] = description 1941 | 1942 | if any([not isinstance(x, TransferTicketTerm) for x in terms]): 1943 | raise FireblocksApiException( 1944 | "Expected Tranfer Assist ticket's term of type TranferTicketTerm" 1945 | ) 1946 | 1947 | body["terms"] = [term.__dict__ for term in terms] 1948 | 1949 | return self._post_request(f"/v1/transfer_tickets", body, idempotency_key) 1950 | 1951 | def get_transfer_ticket_by_id(self, ticket_id): 1952 | """Retrieve a transfer ticket 1953 | 1954 | Args: 1955 | ticket_id (str): The ID of the transfer ticket. 1956 | """ 1957 | 1958 | return self._get_request(f"/v1/transfer_tickets/{ticket_id}") 1959 | 1960 | def get_transfer_ticket_term(self, ticket_id, term_id): 1961 | """Retrieve a transfer ticket 1962 | 1963 | Args: 1964 | ticket_id (str): The ID of the transfer ticket 1965 | term_id (str): The ID of the term within the transfer ticket 1966 | """ 1967 | 1968 | return self._get_request(f"/v1/transfer_tickets/{ticket_id}/{term_id}") 1969 | 1970 | def cancel_transfer_ticket(self, ticket_id, idempotency_key=None): 1971 | """Cancel a transfer ticket 1972 | 1973 | Args: 1974 | ticket_id (str): The ID of the transfer ticket to cancel 1975 | idempotency_key (str, optional) 1976 | """ 1977 | 1978 | return self._post_request( 1979 | f"/v1/transfer_tickets/{ticket_id}/cancel", idempotency_key=idempotency_key 1980 | ) 1981 | 1982 | def execute_ticket_term( 1983 | self, ticket_id, term_id, source=None, idempotency_key=None 1984 | ): 1985 | """Initiate a transfer ticket transaction 1986 | 1987 | Args: 1988 | ticket_id (str): The ID of the transfer ticket 1989 | term_id (str): The ID of the term within the transfer ticket 1990 | source (TransferPeerPath): JSON object of the source of the transaction. The network connection's vault account by default 1991 | """ 1992 | 1993 | body = {} 1994 | 1995 | if source: 1996 | if not isinstance(source, TransferPeerPath): 1997 | raise FireblocksApiException( 1998 | "Expected ticket term source Of type TransferPeerPath, but got type: " 1999 | + type(source) 2000 | ) 2001 | body["source"] = source.__dict__ 2002 | 2003 | return self._post_request( 2004 | f"/v1/transfer_tickets/{ticket_id}/{term_id}/transfer", 2005 | body, 2006 | idempotency_key, 2007 | ) 2008 | 2009 | def set_confirmation_threshold_for_txid( 2010 | self, txid, required_confirmations_number, idempotency_key=None 2011 | ): 2012 | """Set the required number of confirmations for transaction 2013 | 2014 | Args: 2015 | txid (str): The transaction id 2016 | required_confirmations_Number (number): Required confirmation threshold fot the txid 2017 | idempotency_key (str, optional) 2018 | """ 2019 | 2020 | body = {"numOfConfirmations": required_confirmations_number} 2021 | 2022 | return self._post_request( 2023 | f"/v1/transactions/{txid}/set_confirmation_threshold", body, idempotency_key 2024 | ) 2025 | 2026 | def set_confirmation_threshold_for_txhash( 2027 | self, txhash, required_confirmations_number, idempotency_key=None 2028 | ): 2029 | """Set the required number of confirmations for transaction by txhash 2030 | 2031 | Args: 2032 | txhash (str): The transaction hash 2033 | required_confirmations_Number (number): Required confirmation threshold fot the txhash 2034 | idempotency_key (str, optional) 2035 | """ 2036 | 2037 | body = {"numOfConfirmations": required_confirmations_number} 2038 | 2039 | return self._post_request( 2040 | f"/v1/txHash/{txhash}/set_confirmation_threshold", body, idempotency_key 2041 | ) 2042 | 2043 | def get_public_key_info(self, algorithm, derivation_path, compressed=None): 2044 | """Get the public key information 2045 | 2046 | Args: 2047 | algorithm (str, optional) 2048 | derivation_path (str) 2049 | compressed (boolean, optional) 2050 | """ 2051 | 2052 | url = "/v1/vault/public_key_info" 2053 | if algorithm: 2054 | url += f"?algorithm={algorithm}" 2055 | if derivation_path: 2056 | url += f"&derivationPath={urllib.parse.quote(derivation_path)}" 2057 | if compressed: 2058 | url += f"&compressed={compressed}" 2059 | return self._get_request(url) 2060 | 2061 | def get_public_key_info_for_vault_account( 2062 | self, asset_id, vault_account_id, change, address_index, compressed=None 2063 | ): 2064 | """Get the public key information for a vault account 2065 | 2066 | Args: 2067 | assetId (str) 2068 | vaultAccountId (number) 2069 | change (number) 2070 | addressIndex (number) 2071 | compressed (boolean, optional) 2072 | """ 2073 | 2074 | url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/{change}/{address_index}/public_key_info" 2075 | if compressed: 2076 | url += f"?compressed={compressed}" 2077 | 2078 | return self._get_request(url) 2079 | 2080 | def allocate_funds_to_private_ledger( 2081 | self, 2082 | vault_account_id, 2083 | asset, 2084 | allocation_id, 2085 | amount, 2086 | treat_as_gross_amount=None, 2087 | idempotency_key=None, 2088 | ): 2089 | """Allocate funds from your default balance to a private ledger 2090 | 2091 | Args: 2092 | vault_account_id (string) 2093 | asset (string) 2094 | allocation_id (string) 2095 | amount (string) 2096 | treat_as_gross_amount (bool, optional) 2097 | idempotency_key (string, optional) 2098 | """ 2099 | 2100 | url = f"/v1/vault/accounts/{vault_account_id}/{asset}/lock_allocation" 2101 | 2102 | return self._post_request( 2103 | url, 2104 | { 2105 | "allocationId": allocation_id, 2106 | "amount": amount, 2107 | "treatAsGrossAmount": treat_as_gross_amount or False, 2108 | }, 2109 | idempotency_key, 2110 | ) 2111 | 2112 | def deallocate_funds_from_private_ledger( 2113 | self, vault_account_id, asset, allocation_id, amount, idempotency_key=None 2114 | ): 2115 | """deallocate funds from a private ledger to your default balance 2116 | 2117 | Args: 2118 | vault_account_id (string) 2119 | asset (string) 2120 | allocation_id (string) 2121 | amount (string) 2122 | idempotency_key (string, optional) 2123 | """ 2124 | 2125 | url = f"/v1/vault/accounts/{vault_account_id}/{asset}/release_allocation" 2126 | 2127 | return self._post_request( 2128 | url, {"allocationId": allocation_id, "amount": amount}, idempotency_key 2129 | ) 2130 | 2131 | def get_gas_station_info(self, asset_id=None): 2132 | """Get configuration and status of the Gas Station account" 2133 | 2134 | Args: 2135 | asset_id (string, optional) 2136 | """ 2137 | 2138 | url = f"/v1/gas_station" 2139 | 2140 | if asset_id: 2141 | url = url + f"/{asset_id}" 2142 | 2143 | return self._get_request(url) 2144 | 2145 | def set_gas_station_configuration( 2146 | self, gas_threshold, gas_cap, max_gas_price=None, asset_id=None 2147 | ): 2148 | """Set configuration of the Gas Station account 2149 | 2150 | Args: 2151 | gasThreshold (str) 2152 | gasCap (str) 2153 | maxGasPrice (str, optional) 2154 | asset_id (str, optional) 2155 | """ 2156 | 2157 | url = f"/v1/gas_station/configuration" 2158 | 2159 | if asset_id: 2160 | url = url + f"/{asset_id}" 2161 | 2162 | body = { 2163 | "gasThreshold": gas_threshold, 2164 | "gasCap": gas_cap, 2165 | "maxGasPrice": max_gas_price, 2166 | } 2167 | 2168 | return self._put_request(url, body) 2169 | 2170 | def get_vault_assets_balance( 2171 | self, account_name_prefix=None, account_name_suffix=None 2172 | ): 2173 | """Gets vault assets accumulated balance 2174 | 2175 | Args: 2176 | account_name_prefix (string, optional): Vault account name prefix 2177 | account_name_suffix (string, optional): Vault account name suffix 2178 | """ 2179 | url = f"/v1/vault/assets" 2180 | 2181 | params = {} 2182 | 2183 | if account_name_prefix: 2184 | params["accountNamePrefix"] = account_name_prefix 2185 | 2186 | if account_name_suffix: 2187 | params["accountNameSuffix"] = account_name_suffix 2188 | 2189 | if params: 2190 | url = url + "?" + urllib.parse.urlencode(params) 2191 | 2192 | return self._get_request(url) 2193 | 2194 | def get_vault_balance_by_asset(self, asset_id=None): 2195 | """Gets vault accumulated balance by asset 2196 | 2197 | Args: 2198 | asset_id (str, optional): The asset symbol (e.g BTC, ETH) 2199 | """ 2200 | url = f"/v1/vault/assets" 2201 | 2202 | if asset_id: 2203 | url += f"/{asset_id}" 2204 | 2205 | return self._get_request(url) 2206 | 2207 | def create_raw_transaction( 2208 | self, raw_message, source=None, asset_id=None, note=None 2209 | ): 2210 | """Creates a new raw transaction with the specified parameters 2211 | 2212 | Args: 2213 | raw_message (RawMessage): The messages that should be signed 2214 | source (TransferPeerPath, optional): The transaction source 2215 | asset_id (str, optional): Transaction asset id 2216 | note (str, optional): A custome note that can be associated with the transaction 2217 | """ 2218 | 2219 | if asset_id is None: 2220 | if raw_message.algorithm not in SIGNING_ALGORITHM: 2221 | raise Exception( 2222 | "Got invalid signing algorithm type: " + raw_message.algorithm 2223 | ) 2224 | 2225 | if not all([isinstance(x, UnsignedMessage) for x in raw_message.messages]): 2226 | raise FireblocksApiException("Expected messages of type UnsignedMessage") 2227 | 2228 | raw_message.messages = [message.__dict__ for message in raw_message.messages] 2229 | 2230 | return self.create_transaction( 2231 | asset_id, 2232 | source=source, 2233 | tx_type="RAW", 2234 | extra_parameters={"rawMessageData": raw_message.__dict__}, 2235 | note=note, 2236 | ) 2237 | 2238 | def get_max_spendable_amount( 2239 | self, vault_account_id, asset_id, manual_signing=False 2240 | ): 2241 | """Get max spendable amount per asset and vault. 2242 | 2243 | Args: 2244 | vault_account_id (str): The vault account Id. 2245 | asset_id (str): Asset id. 2246 | manual_signing (boolean, optional): False by default. 2247 | """ 2248 | url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/max_spendable_amount?manual_signing={manual_signing}" 2249 | 2250 | return self._get_request(url) 2251 | 2252 | def get_max_bip44_index_used(self, vault_account_id, asset_id): 2253 | """Get maximum BIP44 index used in deriving addresses or in change addresses. 2254 | 2255 | Args: 2256 | vault_account_id (str): The vault account Id. 2257 | asset_id (str): Asset id. 2258 | """ 2259 | url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/max_bip44_index_used" 2260 | 2261 | return self._get_request(url) 2262 | 2263 | def rescan_transactions_beta(self, rescan_txs: List[RescanTx]) -> List[Dict[str, Any]]: 2264 | """initiate rescan for given transactions 2265 | Args: 2266 | rescan_txs: (Array of RescanTx): the transaction asset_id and hash for rescan 2267 | Each RescanTx should have the following keys: 2268 | - 'asset_id': string 2269 | - 'tx_hash': String 2270 | """ 2271 | path = f"/v1/transactions/rescan" 2272 | request_data = [tx.to_dict() for tx in rescan_txs] 2273 | return self._post_request(path, request_data) 2274 | 2275 | def get_paginated_addresses(self, vault_account_id, asset_id, limit=500, before=None, after=None): 2276 | """Gets a paginated response of the addresses for a given vault account and asset 2277 | Args: 2278 | vault_account_id (str): The vault account Id 2279 | asset_id (str): the asset Id 2280 | limit(number, optional): limit of addresses per paging request 2281 | before (str, optional): curser for the previous paging 2282 | after (str, optional): curser for the next paging 2283 | """ 2284 | path = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses_paginated" 2285 | params = {} 2286 | if limit: 2287 | params["limit"] = limit 2288 | if before: 2289 | params["before"] = before 2290 | if after: 2291 | params["after"] = after 2292 | if params: 2293 | path = path + "?" + urllib.parse.urlencode(params) 2294 | return self._get_request(path) 2295 | 2296 | def set_auto_fuel(self, vault_account_id, auto_fuel, idempotency_key=None): 2297 | """Sets autoFuel to true/false for a vault account 2298 | 2299 | Args: 2300 | vault_account_id (str): The vault account Id 2301 | auto_fuel (boolean): The new value for the autoFuel flag 2302 | idempotency_key (str, optional) 2303 | """ 2304 | body = {"autoFuel": auto_fuel} 2305 | 2306 | return self._post_request( 2307 | f"/v1/vault/accounts/{vault_account_id}/set_auto_fuel", 2308 | body, 2309 | idempotency_key, 2310 | ) 2311 | 2312 | def validate_address(self, asset_id, address): 2313 | """Gets vault accumulated balance by asset 2314 | 2315 | Args: 2316 | asset_id (str): The asset symbol (e.g XRP, EOS) 2317 | address (str): The address to be verified 2318 | """ 2319 | url = f"/v1/transactions/validate_address/{asset_id}/{address}" 2320 | 2321 | return self._get_request(url) 2322 | 2323 | def resend_webhooks(self): 2324 | """Resend failed webhooks of your tenant""" 2325 | 2326 | return self._post_request("/v1/webhooks/resend") 2327 | 2328 | def resend_transaction_webhooks_by_id( 2329 | self, tx_id, resend_created, resend_status_updated 2330 | ): 2331 | """Resend webhooks of transaction 2332 | 2333 | Args: 2334 | tx_id (str): The transaction for which the message is sent. 2335 | resend_created (boolean): If true, a webhook will be sent for the creation of the transaction. 2336 | resend_status_updated (boolean): If true, a webhook will be sent for the status of the transaction. 2337 | """ 2338 | body = { 2339 | "resendCreated": resend_created, 2340 | "resendStatusUpdated": resend_status_updated, 2341 | } 2342 | 2343 | return self._post_request(f"/v1/webhooks/resend/{tx_id}", body) 2344 | 2345 | def get_users(self) -> List[Dict[str, Any]]: 2346 | """ 2347 | Gets all Users for your tenant 2348 | """ 2349 | 2350 | url = "/v1/users" 2351 | 2352 | return self._get_request(url) 2353 | 2354 | def get_ota_configuration(self) -> Dict[str, Any]: 2355 | """ 2356 | Get the tenant's OTA (One-Time-Address) configuration 2357 | """ 2358 | 2359 | url = "/v1/management/ota" 2360 | 2361 | return self._get_request(url) 2362 | 2363 | def update_ota_configuration(self, enable: bool) -> None: 2364 | """ 2365 | Update the tenant's OTA (One-Time-Address) configuration 2366 | @param enable 2367 | """ 2368 | 2369 | url = "/v1/management/ota" 2370 | 2371 | body = { 2372 | "enabled": enable 2373 | } 2374 | 2375 | return self._put_request(url, body) 2376 | 2377 | def get_user_groups(self) -> List[Dict[str, Any]]: 2378 | """ 2379 | Gets all User Groups for your tenant 2380 | """ 2381 | 2382 | url = "/v1/management/user_groups" 2383 | 2384 | return self._get_request(url) 2385 | 2386 | def get_user_group(self, id: str) -> Dict[str, Any]: 2387 | """ 2388 | Gets a User Group by ID 2389 | @param id: The ID of the User 2390 | """ 2391 | 2392 | url = f"/v1/management/user_groups/{id}" 2393 | 2394 | return self._get_request(url) 2395 | 2396 | def create_user_group(self, group_name: str, member_ids: Optional[List[str]] = None) -> Dict[str, Any]: 2397 | """ 2398 | Creates a new User Group 2399 | @param group_name: The name of the User Group 2400 | @param member_ids: The ids of the User Group members 2401 | """ 2402 | 2403 | url = "/v1/management/user_groups" 2404 | 2405 | body = { 2406 | "groupName": group_name, 2407 | "memberIds": member_ids 2408 | } 2409 | 2410 | return self._post_request(url, body) 2411 | 2412 | def update_user_group(self, id: str, group_name: Optional[str] = None, member_ids: Optional[List[str]] = None) -> \ 2413 | Dict[str, Any]: 2414 | """ 2415 | Updates a User Group 2416 | @param id: The ID of the User Group 2417 | @param group_name: The name of the User Group 2418 | @param member_ids: The ids of the User Group members 2419 | """ 2420 | 2421 | url = f"/v1/management/user_groups/{id}" 2422 | 2423 | body = { 2424 | "groupName": group_name, 2425 | "memberIds": member_ids 2426 | } 2427 | 2428 | return self._put_request(url, body) 2429 | 2430 | def delete_user_group(self, id: str) -> None: 2431 | """ 2432 | Deletes a User Group 2433 | @param id: The ID of the User Group 2434 | """ 2435 | 2436 | url = f"/v1/management/user_groups/{id}" 2437 | 2438 | return self._delete_request(url) 2439 | 2440 | def get_console_users(self) -> List[Dict[str, Any]]: 2441 | """ 2442 | Gets all Console Users for your tenant 2443 | """ 2444 | 2445 | url = "/v1/management/users" 2446 | 2447 | return self._get_request(url) 2448 | 2449 | def get_api_users(self) -> List[Dict[str, Any]]: 2450 | """ 2451 | Gets all Api Users for your tenant 2452 | """ 2453 | 2454 | url = "/v1/management/api_users" 2455 | 2456 | return self._get_request(url) 2457 | 2458 | def create_console_user(self, first_name: str, last_name: str, email: str, role: Role) -> None: 2459 | """ 2460 | Create Console User for your tenant 2461 | @param first_name: firstName of the user, example: "Johnny". Maximum length: 30 chars. 2462 | @param last_name: lastName of the user. Maximum length: 30 chars. 2463 | @param email: email of the user, example: "email@example.com" 2464 | @param role: role of the user, for example: "ADMIN" 2465 | """ 2466 | 2467 | url = "/v1/management/users" 2468 | 2469 | body = { 2470 | "firstName": first_name, 2471 | "lastName": last_name, 2472 | "email": email, 2473 | "role": role 2474 | } 2475 | 2476 | return self._post_request(url, body) 2477 | 2478 | def create_api_user(self, name: str, role: Role, csr_pem: str, co_signer_setup: Optional[str] = None, co_signer_setup_is_first_user: Optional[bool] = False) -> None: 2479 | """ 2480 | Create Api User for your tenant 2481 | @param role: role of the user, for example: "ADMIN" 2482 | @param name: name of the api user, example: "Johnny The Api". Maximum length: 30 chars. 2483 | @param csr_pem: generate .csr file and provide its string content here, example: "-----BEGIN CERTIFICATE REQUEST-----aaa-----END CERTIFICATE REQUEST-----" 2484 | You can find more info about csrPem and how to create it here: https://developers.fireblocks.com/docs/quickstart 2485 | @param co_signer_setup: your cosigner, for example: "SGX_MACHINE", read more: https://developers.fireblocks.com/docs/quickstart 2486 | @param co_signer_setup_is_first_user: [SGX server enabled only] If you are the first user to be configured on this SGX-enabled Co-Signer server, this has to be true 2487 | """ 2488 | 2489 | url = "/v1/management/api_users" 2490 | 2491 | body = { 2492 | "role": role, 2493 | "name": name, 2494 | "csrPem": csr_pem, 2495 | "coSignerSetup": co_signer_setup, 2496 | "coSignerSetupIsFirstUser": co_signer_setup_is_first_user 2497 | } 2498 | 2499 | return self._post_request(url, body) 2500 | 2501 | def reset_device_request(self, id: str) -> None: 2502 | """ 2503 | Re-enroll Mobile Device of a user in your tenant 2504 | @param id: userId of the user to reset device 2505 | """ 2506 | 2507 | url = f"/v1/management/users/{id}/reset_device" 2508 | 2509 | return self._post_request(url) 2510 | 2511 | def get_whitelisted_ip_addresses(self, id: str) -> Dict[str, Any]: 2512 | """ 2513 | Get whitelisted addresses of api user in your tenant 2514 | @param id: userId of the user 2515 | """ 2516 | 2517 | url = f"/v1/management/api_users/{id}/whitelist_ip_addresses" 2518 | 2519 | return self._get_request(url) 2520 | 2521 | def get_off_exchanges(self): 2522 | """ 2523 | Get your connected off exchanges virtual accounts 2524 | """ 2525 | url = f"/v1/off_exchange_accounts" 2526 | 2527 | return self._get_request(url) 2528 | 2529 | def get_audit_logs(self, time_period: TimePeriod = TimePeriod.DAY): 2530 | """ 2531 | Get audit logs 2532 | :param time_period: The last time period to fetch audit logs 2533 | """ 2534 | 2535 | url = "/v1/audits" 2536 | 2537 | return self._get_request(url, query_params={"timePeriod": time_period.value}) 2538 | 2539 | def get_paginated_audit_logs(self, time_period: TimePeriod = TimePeriod.DAY, cursor = None): 2540 | """ 2541 | Get paginated audit logs 2542 | :param time_period: The last time period to fetch audit logs 2543 | :param cursor: The next id to fetch audit logs from 2544 | """ 2545 | url = "/v1/management/audit_logs" 2546 | params = {} 2547 | 2548 | if cursor: 2549 | params["cursor"] = cursor 2550 | 2551 | if time_period: 2552 | params["timePeriod"] = time_period.value 2553 | 2554 | return self._get_request(url, query_params=params) 2555 | 2556 | def get_off_exchange_by_id(self, off_exchange_id): 2557 | """ 2558 | Get your connected off exchange by it's ID 2559 | :param off_exchange_id: ID of the off exchange virtual account 2560 | :return: off exchange entity 2561 | """ 2562 | 2563 | url = f"/v1/off_exchange_accounts/{off_exchange_id}" 2564 | 2565 | return self._get_request(url) 2566 | 2567 | def settle_off_exchange_by_id(self, off_exchange_id, idempotency_key=None): 2568 | """ 2569 | Create a settle request to your off exchange by it's ID 2570 | :param off_exchange_id: ID of the off exchange virtual account 2571 | :param idempotency_key 2572 | """ 2573 | 2574 | url = f"/v1/off_exchanges/{off_exchange_id}/settle" 2575 | 2576 | return self._post_request(url, {}, idempotency_key) 2577 | 2578 | def set_fee_payer_configuration( 2579 | self, base_asset, fee_payer_account_id, idempotency_key=None 2580 | ): 2581 | """ 2582 | Setting fee payer configuration for base asset 2583 | :param base_asset: ID of the base asset you want to configure fee payer for (for example: SOL) 2584 | :param fee_payer_account_id: ID of the vault account you want your fee to be paid from 2585 | :param idempotency_key 2586 | """ 2587 | 2588 | url = f"/v1/fee_payer/{base_asset}" 2589 | 2590 | body = {"feePayerAccountId": fee_payer_account_id} 2591 | 2592 | return self._post_request(url, body, idempotency_key) 2593 | 2594 | def get_fee_payer_configuration(self, base_asset): 2595 | """ 2596 | Get fee payer configuration for base asset 2597 | :param base_asset: ID of the base asset 2598 | :return: the fee payer configuration 2599 | """ 2600 | 2601 | url = f"/v1/fee_payer/{base_asset}" 2602 | 2603 | return self._get_request(url) 2604 | 2605 | def remove_fee_payer_configuration(self, base_asset): 2606 | """ 2607 | Delete fee payer configuration for base asset 2608 | :param base_asset: ID of the base asset 2609 | """ 2610 | url = f"/v1/fee_payer/{base_asset}" 2611 | 2612 | return self._delete_request(url) 2613 | 2614 | def get_web3_connections( 2615 | self, pageCursor=None, pageSize=None, sort=None, filter=None, order=None 2616 | ): 2617 | """ 2618 | Get all signer connections of the current user 2619 | :return: Array of sessions 2620 | """ 2621 | 2622 | method_param = locals() 2623 | url = "/v1/connections" 2624 | optional_params = ["pageCursor", "pageSize", "sort", "filter", "order"] 2625 | 2626 | query_params = { 2627 | param: method_param.get(param) 2628 | for param in optional_params 2629 | if method_param.get(param) 2630 | } 2631 | 2632 | if query_params: 2633 | url = url + "?" + urllib.parse.urlencode(query_params) 2634 | 2635 | return self._get_request(url) 2636 | 2637 | def create_web3_connection( 2638 | self, 2639 | vault_account_id: str, 2640 | uri: str, 2641 | chain_ids: List[str], 2642 | fee_level: str = "MEDIUM", 2643 | idempotency_key: str = None, 2644 | ): 2645 | """ 2646 | Initiate a new signer connection 2647 | :param vault_account_id: The id of the requested account 2648 | :param uri: Wallet Connect uri provided by the dApp 2649 | :param chain_ids: A list of chain ids to be used by the connection 2650 | :param fee_level: The fee level of the dropping transaction (HIGH, MEDIUM, LOW) 2651 | :param idempotency_key: Idempotency key 2652 | :return: The created session's ID and its metadata 2653 | """ 2654 | 2655 | url = "/v1/connections/wc" 2656 | 2657 | payload = { 2658 | "vaultAccountId": int(vault_account_id), 2659 | "feeLevel": fee_level, 2660 | "uri": uri, 2661 | "chainIds": chain_ids, 2662 | } 2663 | 2664 | return self._post_request(url, payload, idempotency_key) 2665 | 2666 | def submit_web3_connection(self, session_id: str, approve: bool): 2667 | """ 2668 | Approve or Reject the initiated connection 2669 | :param session_id: The ID of the session 2670 | :param approve: Whether you approve the connection or not 2671 | """ 2672 | 2673 | url = f"/v1/connections/wc/{session_id}" 2674 | 2675 | body = {"approve": approve} 2676 | 2677 | return self._put_request(url, body) 2678 | 2679 | def remove_web3_connection(self, session_id: str): 2680 | """ 2681 | Remove an existing connection 2682 | :param session_id: The ID of the session 2683 | """ 2684 | 2685 | url = f"/v1/connections/wc/{session_id}" 2686 | 2687 | return self._delete_request(url) 2688 | 2689 | def get_active_policy(self): 2690 | """ 2691 | Get active policy (TAP) [BETA] 2692 | """ 2693 | 2694 | url = "/v1/tap/active_policy" 2695 | 2696 | return self._get_request(url) 2697 | 2698 | def get_draft(self): 2699 | """ 2700 | Get draft policy (TAP) [BETA] 2701 | """ 2702 | 2703 | url = "/v1/tap/draft" 2704 | 2705 | return self._get_request(url) 2706 | 2707 | def update_draft(self, rules: List[PolicyRule]): 2708 | """ 2709 | Update draft policy (TAP) [BETA] 2710 | @param rules: list of policy rules 2711 | """ 2712 | 2713 | url = "/v1/tap/draft" 2714 | body = {} 2715 | 2716 | if rules is not None and isinstance(rules, list): 2717 | if any([not isinstance(x, PolicyRule) for x in rules]): 2718 | raise FireblocksApiException("Expected rules of type List[PolicyRule]") 2719 | body['rules'] = [rule.to_dict() for rule in rules] 2720 | 2721 | return self._put_request(url, body) 2722 | 2723 | def publish_draft(self, draft_id: str): 2724 | """ 2725 | Publish draft policy (TAP) [BETA] 2726 | """ 2727 | 2728 | url = "/v1/tap/draft" 2729 | 2730 | body = { 2731 | "draftId": draft_id 2732 | } 2733 | 2734 | return self._post_request(url, body) 2735 | 2736 | def publish_policy_rules(self, rules: List[PolicyRule]): 2737 | """ 2738 | Publish policy rules (TAP) [BETA] 2739 | @param rules: list of rules 2740 | """ 2741 | 2742 | url = "/v1/tap/publish" 2743 | body = {} 2744 | 2745 | if rules is not None and isinstance(rules, list): 2746 | if any([not isinstance(x, PolicyRule) for x in rules]): 2747 | raise FireblocksApiException("Expected rules of type List[PolicyRule]") 2748 | body['rules'] = [rule.to_dict() for rule in rules] 2749 | 2750 | return self._post_request(url, body) 2751 | 2752 | def get_smart_transfer_tickets(self, paged_smart_transfer_request_filters: GetSmartTransferFilters): 2753 | """Gets a page of smart transfer for your tenant according to filters given 2754 | Args: 2755 | paged_smart_transfer_request_filters (object, optional): Possible filters to apply for request 2756 | """ 2757 | 2758 | url = "/v1/smart-transfers" 2759 | 2760 | params = {} 2761 | 2762 | if paged_smart_transfer_request_filters.query is not None: 2763 | params['q'] = paged_smart_transfer_request_filters.query 2764 | 2765 | if paged_smart_transfer_request_filters.statuses is not None: 2766 | params['statuses'] = paged_smart_transfer_request_filters.statuses 2767 | 2768 | if paged_smart_transfer_request_filters.network_id is not None: 2769 | params['networkId'] = paged_smart_transfer_request_filters.network_id 2770 | 2771 | if paged_smart_transfer_request_filters.created_by_me is not None: 2772 | params['createdByMe'] = bool(paged_smart_transfer_request_filters.created_by_me) 2773 | 2774 | if paged_smart_transfer_request_filters.expires_after is not None: 2775 | params['expiresAfter'] = paged_smart_transfer_request_filters.expires_after 2776 | 2777 | if paged_smart_transfer_request_filters.expires_before is not None: 2778 | params['expiresBefore'] = paged_smart_transfer_request_filters.expires_before 2779 | 2780 | if paged_smart_transfer_request_filters.ticket_type is not None: 2781 | params['type'] = paged_smart_transfer_request_filters.ticket_type 2782 | 2783 | if paged_smart_transfer_request_filters.external_ref_id is not None: 2784 | params['externalRefId'] = paged_smart_transfer_request_filters.external_ref_id 2785 | 2786 | if paged_smart_transfer_request_filters.after is not None: 2787 | params['after'] = paged_smart_transfer_request_filters.after 2788 | 2789 | if paged_smart_transfer_request_filters.limit is not None: 2790 | params['limit'] = int(paged_smart_transfer_request_filters.limit) 2791 | 2792 | if params: 2793 | url = url + "?" + urllib.parse.urlencode(params) 2794 | 2795 | return self._get_request(url) 2796 | 2797 | def create_smart_transfer_ticket(self, ticket_type: str, created_by_network_id: str, terms=None, 2798 | expires_in: Optional[int] = None, submit: bool = True, note: Optional[str] = None, 2799 | external_ref_id: Optional[str] = None, idempotency_key: str = None): 2800 | """Creates new Smart Transfer ticket 2801 | Args: 2802 | ticket_type (str): Type of the ticket (ASYNC) 2803 | created_by_network_id (str): NetworkId that is used for ticket creation 2804 | expires_in (int): Ticket expiration in hours. Optional 2805 | submit (bool): Flag that will submit ticket immediately - create ticket with OPEN status (ticket will be created in DRAFT otherwise). Optional 2806 | note (str): Note. Optional; 2807 | terms (list, optional): Ticket terms array. 2808 | Each term should have the following keys: 2809 | - 'asset': Asset 2810 | - 'amount': Amount 2811 | - 'fromNetworkId': Source networkId 2812 | - 'toNetworkId': Destination networkId 2813 | Default is an empty list. 2814 | external_ref_id (str): External Reference ID. Optional; 2815 | idempotency_key: Idempotency key 2816 | """ 2817 | 2818 | url = f"/v1/smart-transfers" 2819 | 2820 | if terms is None: 2821 | terms = [] 2822 | 2823 | payload = { 2824 | "createdByNetworkId": created_by_network_id, 2825 | "type": ticket_type, 2826 | "terms": terms, 2827 | "submit": submit 2828 | } 2829 | 2830 | if expires_in is not None: 2831 | payload["expiresIn"] = expires_in 2832 | if note is not None: 2833 | payload["note"] = note 2834 | if external_ref_id is not None: 2835 | payload["externalRefId"] = external_ref_id 2836 | 2837 | return self._post_request(url, payload, idempotency_key) 2838 | 2839 | def get_smart_transfer_ticket(self, ticket_id: str): 2840 | """Fetch single Smart Transfer ticket 2841 | Args: 2842 | ticket_id (str): ID of the ticket 2843 | """ 2844 | 2845 | url = f"/v1/smart-transfers/{ticket_id}" 2846 | 2847 | return self._get_request(url) 2848 | 2849 | def set_smart_transfer_ticket_expires_in(self, ticket_id: str, expires_in: int): 2850 | """Set expiration for ticket. 2851 | Args: 2852 | ticket_id (str): ID of the ticket 2853 | expires_in (int): Expires in (number of hours) 2854 | """ 2855 | 2856 | url = f"/v1/smart-transfers/{ticket_id}/expires-in" 2857 | 2858 | payload = { 2859 | "expiresIn": expires_in 2860 | } 2861 | 2862 | return self._put_request(url, payload) 2863 | 2864 | def set_smart_transfer_ticket_external_ref_id(self, ticket_id: str, external_ref_id: str): 2865 | """Set External Ref. ID for Ticket 2866 | Args: 2867 | ticket_id (str): ID of the ticket 2868 | external_ref_id (str): ticket External Ref. id 2869 | """ 2870 | 2871 | url = f"/v1/smart-transfers/{ticket_id}/external-id" 2872 | 2873 | payload = { 2874 | "externalRefId": external_ref_id 2875 | } 2876 | 2877 | return self._put_request(url, payload) 2878 | 2879 | def submit_smart_transfer_ticket(self, ticket_id: str, expires_in: int): 2880 | """Submit Smart Transfer ticket - change status to OPEN 2881 | Args: 2882 | ticket_id (str): ID of the ticket 2883 | expires_in (int): Expires in (number of hours) 2884 | """ 2885 | 2886 | url = f"/v1/smart-transfers/{ticket_id}/submit" 2887 | 2888 | payload = { 2889 | "expiresIn": expires_in 2890 | } 2891 | 2892 | return self._put_request(url, payload) 2893 | 2894 | def fulfill_smart_transfer_ticket(self, ticket_id: str): 2895 | """Manually fulfill ticket, in case when all terms (legs) are funded manually 2896 | Args: 2897 | ticket_id (str): ID of the ticket 2898 | """ 2899 | 2900 | url = f"/v1/smart-transfers/{ticket_id}/fulfill" 2901 | 2902 | return self._put_request(url) 2903 | 2904 | def cancel_smart_transfer_ticket(self, ticket_id: str): 2905 | """Cancel Smart Transfer ticket 2906 | Args: 2907 | ticket_id (str): ID of the ticket 2908 | """ 2909 | 2910 | url = f"/v1/smart-transfers/{ticket_id}/cancel" 2911 | 2912 | return self._put_request(url) 2913 | 2914 | def create_smart_transfer_ticket_term(self, ticket_id: str, asset: str, amount, from_network_id: str, 2915 | to_network_id: str, idempotency_key: str = None): 2916 | """Create new Smart Transfer ticket term/leg (when ticket in DRAFT) 2917 | Args: 2918 | ticket_id (str): Ticket ID 2919 | asset (str): ID of asset 2920 | amount (double): Amount 2921 | from_network_id (str): Source network id 2922 | to_network_id (str): Destination network id 2923 | idempotency_key (str): Idempotency key 2924 | """ 2925 | 2926 | url = f"/v1/smart-transfers/{ticket_id}/terms" 2927 | 2928 | payload = { 2929 | "asset": asset, 2930 | "amount": amount, 2931 | "fromNetworkId": from_network_id, 2932 | "toNetworkId": to_network_id, 2933 | } 2934 | 2935 | return self._post_request(url, payload, idempotency_key) 2936 | 2937 | def get_smart_transfer_ticket_term(self, ticket_id: str, term_id: str): 2938 | """Gets Smart Transfer ticket term/leg 2939 | Args: 2940 | ticket_id (str): Ticket ID 2941 | term_id (str): Term ID 2942 | """ 2943 | 2944 | url = f"/v1/smart-transfers/{ticket_id}/terms/{term_id}" 2945 | 2946 | return self._get_request(url) 2947 | 2948 | def update_smart_transfer_ticket_term(self, ticket_id: str, term_id: str, asset: str, amount, from_network_id: str, 2949 | to_network_id: str): 2950 | """Update Smart Transfer ticket term/leg 2951 | Args: 2952 | ticket_id (str): Ticket ID 2953 | term_id (str): Term ID 2954 | asset (str): ID of asset 2955 | amount (double): Amount 2956 | from_network_id (str): Source network id 2957 | to_network_id (str): Destination network id 2958 | """ 2959 | 2960 | url = f"/v1/smart-transfers/{ticket_id}/terms/{term_id}" 2961 | 2962 | payload = { 2963 | "asset": asset, 2964 | "amount": amount, 2965 | "fromNetworkId": from_network_id, 2966 | "toNetworkId": to_network_id, 2967 | } 2968 | 2969 | return self._put_request(url, payload) 2970 | 2971 | def delete_smart_transfer_ticket_term(self, ticket_id: str, term_id: str): 2972 | """Delete Smart Transfer ticket term/leg 2973 | Args: 2974 | ticket_id (str): Ticket ID 2975 | term_id (str): Term ID 2976 | """ 2977 | 2978 | url = f"/v1/smart-transfers/{ticket_id}/terms/{term_id}" 2979 | 2980 | return self._delete_request(url) 2981 | 2982 | def fund_smart_transfer_ticket_term(self, ticket_id: str, term_id, asset: str, amount, network_connection_id: str, 2983 | source_id: str, source_type: str, fee: Optional[str] = None, 2984 | fee_level: Optional[str] = None): 2985 | """Fund Smart Transfer ticket term/leg 2986 | Args: 2987 | ticket_id (str): Ticket ID 2988 | term_id (str): Term ID 2989 | asset (str): ID of asset 2990 | amount (str): String representation of amount ("1.2" e.g.) 2991 | network_connection_id (str): Connection id 2992 | source_id (str): Source id for transaction 2993 | source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: 2994 | VAULT_ACCOUNT, EXCHANGE, FIAT_ACCOUNT 2995 | fee (double, optional): Sathoshi/Latoshi per byte. 2996 | fee_level: The fee level of the dropping transaction (HIGH, MEDIUM, LOW) 2997 | """ 2998 | 2999 | url = f"/v1/smart-transfers/{ticket_id}/terms/{term_id}/fund" 3000 | 3001 | payload = { 3002 | "asset": asset, 3003 | "amount": amount, 3004 | "networkConnectionId": network_connection_id, 3005 | "srcId": source_id, 3006 | "srcType": source_type, 3007 | } 3008 | 3009 | if fee is not None: 3010 | payload["fee"] = fee 3011 | 3012 | if fee_level is not None: 3013 | if fee_level not in FEE_LEVEL: 3014 | raise FireblocksApiException("Got invalid fee level: " + fee_level) 3015 | payload["feeLevel"] = fee_level 3016 | 3017 | return self._put_request(url, payload) 3018 | 3019 | def manually_fund_smart_transfer_ticket_term(self, ticket_id: str, term_id, tx_hash: str): 3020 | """Manually fund Smart Transfer ticket term/leg 3021 | Args: 3022 | ticket_id (str): Ticket ID 3023 | term_id (str): Term ID 3024 | tx_hash (str): Transaction hash 3025 | """ 3026 | 3027 | url = f"/v1/smart-transfers/{ticket_id}/terms/{term_id}/manually-fund" 3028 | 3029 | payload = { 3030 | "txHash": tx_hash, 3031 | } 3032 | 3033 | return self._put_request(url, payload) 3034 | 3035 | def set_smart_transfer_user_group_ids(self, user_group_ids): 3036 | """Set Smart Transfer user group ids 3037 | Args: 3038 | user_group_ids (list): List of user groups ids to receive Smart Transfer notifications 3039 | """ 3040 | 3041 | url = "/v1/smart-transfers/settings/user-groups" 3042 | 3043 | payload = { 3044 | "userGroupIds": user_group_ids, 3045 | } 3046 | 3047 | return self._post_request(url, payload) 3048 | 3049 | def get_smart_transfer_user_group_ids(self): 3050 | """Fetch Smart Transfer user group ids that will receive Smart Transfer notifications 3051 | """ 3052 | 3053 | url = "/v1/smart-transfers/settings/user-groups" 3054 | 3055 | return self._get_request(url) 3056 | 3057 | def get_linked_tokens(self, status: Optional[TokenLinkStatus] = None, page_size: Optional[int] = None, page_cursor: Optional[str] = None): 3058 | request_filter = {} 3059 | 3060 | if status: 3061 | request_filter["status"] = status.value 3062 | 3063 | if page_size: 3064 | request_filter["pageSize"] = page_size 3065 | 3066 | if page_cursor: 3067 | request_filter["pageCursor"] = page_cursor 3068 | 3069 | return self._get_request("/v1/tokenization/tokens", query_params=request_filter) 3070 | 3071 | def get_pending_linked_tokens(self, page_size: Optional[int] = None, page_cursor: Optional[str] = None): 3072 | return self.get_linked_tokens(TokenLinkStatus.PENDING, page_size, page_cursor) 3073 | 3074 | def issue_new_token(self, request: CreateTokenRequest): 3075 | return self._post_request("/v1/tokenization/tokens", request.to_dict()) 3076 | 3077 | def get_linked_token(self, id: str): 3078 | return self._get_request(f"/v1/tokenization/tokens/{id}") 3079 | 3080 | def get_linked_tokens_count(self): 3081 | return self._get_request(f"/v1/tokenization/tokens/count") 3082 | 3083 | def link_token(self, type: TokenLinkType, ref_id: str, display_name: Optional[str] = None): 3084 | body = { 3085 | "type": type, 3086 | "refId": ref_id, 3087 | } 3088 | 3089 | if display_name: 3090 | body["displayName"] = display_name 3091 | 3092 | return self._post_request(f"/v1/tokenization/tokens/link", body) 3093 | 3094 | def link_contract_by_address(self, type: TokenLinkType, base_asset_id: str, contract_address: str, display_name: Optional[str] = None): 3095 | body = { 3096 | "type": type, 3097 | "baseAssetId": base_asset_id, 3098 | "contractAddress": contract_address, 3099 | } 3100 | 3101 | if display_name: 3102 | body["displayName"] = display_name 3103 | 3104 | return self._post_request(f"/v1/tokenization/tokens/link", body) 3105 | 3106 | def unlink_token(self, id: str): 3107 | return self._delete_request(f"/v1/tokenization/tokens/{id}") 3108 | 3109 | def create_new_collection(self, request: CreateCollectionRequest): 3110 | return self._post_request("/v1/tokenization/collections", request.to_dict()) 3111 | 3112 | def get_linked_collections(self, status: Optional[TokenLinkStatus] = None, page_size: Optional[int] = None, page_cursor: Optional[str] = None): 3113 | request_filter = {} 3114 | 3115 | if status: 3116 | request_filter["status"] = status.value 3117 | 3118 | if page_size: 3119 | request_filter["pageSize"] = page_size 3120 | 3121 | if page_cursor: 3122 | request_filter["pageCursor"] = page_cursor 3123 | 3124 | return self._get_request("/v1/tokenization/collections", query_params=request_filter) 3125 | 3126 | def get_linked_collection(self, id: str): 3127 | return self._get_request(f"/v1/tokenization/collections/{id}") 3128 | 3129 | def unlinked_collection(self, id: str): 3130 | return self._delete_request(f"/v1/tokenization/collections/{id}") 3131 | 3132 | def mint_nft(self, request: MintCollectionTokenRequest): 3133 | return self._post_request("/v1/tokenization/collections/tokens/mint", request.to_dict()) 3134 | 3135 | def burn_nft(self, request: BurnCollectionTokenRequest): 3136 | return self._post_request("/v1/tokenization/collections/tokens/burn", request.to_dict()) 3137 | 3138 | def get_contract_templates( 3139 | self, 3140 | initialization_phase: Optional[ContractInitializationPhase] = None, 3141 | type: Optional[ContractTemplateType] = None, 3142 | page_size: Optional[int] = None, 3143 | page_cursor: Optional[str] = None 3144 | ): 3145 | request_filter = {} 3146 | 3147 | if initialization_phase: 3148 | request_filter["initializationPhase"] = initialization_phase.value 3149 | 3150 | if type: 3151 | request_filter["type"] = type.value 3152 | 3153 | if page_size: 3154 | request_filter["pageSize"] = page_size 3155 | 3156 | if page_cursor: 3157 | request_filter["pageCursor"] = page_cursor 3158 | 3159 | return self._get_request("/v1/tokenization/templates", query_params=request_filter) 3160 | 3161 | def upload_contract_template(self, request: ContractUploadRequest): 3162 | return self._post_request("/v1/tokenization/templates", request.to_dict()) 3163 | 3164 | def get_contract_template(self, template_id: str): 3165 | return self._get_request(f"/v1/tokenization/templates/{template_id}") 3166 | 3167 | def get_contract_template_deploy_function(self, template_id: str, with_docs: bool=False): 3168 | return self._get_request(f"/v1/tokenization/templates/{template_id}/deploy_function?withDocs=${with_docs}") 3169 | 3170 | def get_contract_template_supported_blockchains(self, template_id: str): 3171 | return self._get_request(f"/v1/tokenization/templates/{template_id}/supported_blockchains") 3172 | 3173 | def delete_contract_template(self, template_id: str): 3174 | return self._delete_request(f"/v1/tokenization/templates/{template_id}") 3175 | 3176 | def deploy_contract(self, template_id: str, request: ContractDeployRequest): 3177 | return self._post_request(f"/v1/tokenization/templates/{template_id}/deploy", request.to_dict()) 3178 | 3179 | def get_contracts_by_filter(self, 3180 | contract_template_id: Optional[str] = None, 3181 | base_asset_id: Optional[str] = None, 3182 | contract_address: Optional[str] = None, 3183 | page_size: Optional[int] = None, 3184 | page_cursor: Optional[str] = None 3185 | ): 3186 | request_filter = {} 3187 | 3188 | if contract_template_id: 3189 | request_filter["contractTemplateId"] = contract_template_id 3190 | 3191 | if base_asset_id: 3192 | request_filter["baseAssetId"] = base_asset_id 3193 | 3194 | if contract_address: 3195 | request_filter["contractAddress"] = contract_address 3196 | 3197 | if page_size: 3198 | request_filter["pageSize"] = page_size 3199 | 3200 | if page_cursor: 3201 | request_filter["pageCursor"] = page_cursor 3202 | 3203 | return self._get_request("/v1/tokenization/contracts", query_params=request_filter) 3204 | 3205 | def get_contract_address(self, base_asset_id: str, tx_hash: str): 3206 | return self._get_request(f"/v1/contract_interactions/base_asset_id/{base_asset_id}/tx_hash/{tx_hash}") 3207 | 3208 | def get_contract_by_address(self, base_asset_id: str, contract_address: str): 3209 | return self._get_request(f"/v1/contract_interactions/base_asset_id/{base_asset_id}/contract_address/{contract_address}") 3210 | 3211 | def get_contract_abi(self, base_asset_id: str, contract_address: str): 3212 | return self._get_request(f"/v1/contract_interactions/base_asset_id/{base_asset_id}/contract_address/{contract_address}/functions") 3213 | 3214 | def fetch_or_scrape_abi(self, base_asset_id: str, contract_address: str): 3215 | return self._post_request("/v1/tokenization/contracts/fetch_abi",{ 3216 | "baseAssetId": base_asset_id, 3217 | "contractAddress": contract_address 3218 | }) 3219 | 3220 | def save_abi(self, base_asset_id: str, contract_address: str, abi: List[AbiFunction], name: Optional[str] = None): 3221 | return self._post_request("/v1/tokenization/contracts/abi",{ 3222 | "baseAssetId": base_asset_id, 3223 | "contractAddress": contract_address, 3224 | "abi": abi, 3225 | "name": name, 3226 | }) 3227 | 3228 | def read_contract_call_function(self, base_asset_id: str, contract_address: str, request: ReadCallFunction): 3229 | return self._post_request(f"/v1/contract_interactions/base_asset_id/{base_asset_id}/contract_address/{contract_address}/functions/read", request.to_dict()) 3230 | 3231 | def write_contract_call_function(self, base_asset_id: str, contract_address: str, request: WriteCallFunction): 3232 | return self._post_request(f"/v1/contract_interactions/base_asset_id/{base_asset_id}/contract_address/{contract_address}/functions/write", request.to_dict()) 3233 | 3234 | def _get_request(self, path, page_mode=False, query_params: Dict = None, ncw_wallet_id: str=None): 3235 | if query_params: 3236 | path = path + "?" + urllib.parse.urlencode(query_params) 3237 | token = self.token_provider.sign_jwt(path) 3238 | headers = {"Authorization": f"Bearer {token}"} 3239 | if ncw_wallet_id is not None: 3240 | headers["X-End-User-Wallet-Id"] = ncw_wallet_id 3241 | 3242 | response = self.http_session.get( 3243 | self.base_url + path, headers=headers, timeout=self.timeout 3244 | ) 3245 | return handle_response(response, page_mode) 3246 | 3247 | def _delete_request(self, path): 3248 | token = self.token_provider.sign_jwt(path) 3249 | headers = {"Authorization": f"Bearer {token}"} 3250 | response = self.http_session.delete( 3251 | self.base_url + path, headers=headers, timeout=self.timeout 3252 | ) 3253 | return handle_response(response) 3254 | 3255 | def _post_request(self, path, body=None, idempotency_key=None, ncw_wallet_id=None): 3256 | body = body or {} 3257 | 3258 | token = self.token_provider.sign_jwt(path, body) 3259 | headers = {"Authorization": f"Bearer {token}"} 3260 | if idempotency_key is not None: 3261 | headers["Idempotency-Key"] = idempotency_key 3262 | if ncw_wallet_id is not None: 3263 | headers["X-End-User-Wallet-Id"] = ncw_wallet_id 3264 | 3265 | response = self.http_session.post( 3266 | self.base_url + path, headers=headers, json=body, timeout=self.timeout 3267 | ) 3268 | return handle_response(response) 3269 | 3270 | def _put_request(self, path, body=None, query_params=None): 3271 | body = body or {} 3272 | if query_params: 3273 | path = path + "?" + urllib.parse.urlencode(query_params) 3274 | 3275 | token = self.token_provider.sign_jwt(path, body) 3276 | headers = { 3277 | "Authorization": f"Bearer {token}", 3278 | "Content-Type": "application/json", 3279 | } 3280 | response = self.http_session.put( 3281 | self.base_url + path, 3282 | headers=headers, 3283 | data=json.dumps(body), 3284 | timeout=self.timeout, 3285 | ) 3286 | return handle_response(response) 3287 | 3288 | def _patch_request(self, path, body=None): 3289 | body = body or {} 3290 | 3291 | token = self.token_provider.sign_jwt(path, body) 3292 | headers = {"Authorization": f"Bearer {token}"} 3293 | response = self.http_session.patch( 3294 | self.base_url + path, headers=headers, json=body, timeout=self.timeout 3295 | ) 3296 | return handle_response(response) 3297 | 3298 | @staticmethod 3299 | def _get_user_agent(anonymous_platform): 3300 | user_agent = f"fireblocks-sdk-py/{version('fireblocks_sdk')}" 3301 | if not anonymous_platform: 3302 | user_agent += ( 3303 | f" ({platform.system()} {platform.release()}; " 3304 | f"{platform.python_implementation()} {platform.python_version()}; " 3305 | f"{platform.machine()})" 3306 | ) 3307 | return user_agent 3308 | -------------------------------------------------------------------------------- /fireblocks_sdk/sdk_token_provider.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | import json 3 | import time 4 | import math 5 | import secrets 6 | from hashlib import sha256 7 | 8 | 9 | class SdkTokenProvider: 10 | def __init__(self, private_key, api_key, seconds_jwt_exp): 11 | self.private_key = private_key 12 | self.api_key = api_key 13 | self.seconds_jwt_exp = seconds_jwt_exp 14 | 15 | def sign_jwt(self, path, body_json=""): 16 | timestamp = time.time() 17 | nonce = secrets.randbits(63) 18 | timestamp_secs = math.floor(timestamp) 19 | path= path.replace("[", "%5B") 20 | path= path.replace("]", "%5D") 21 | token = { 22 | "uri": path, 23 | "nonce": nonce, 24 | "iat": timestamp_secs, 25 | "exp": timestamp_secs + self.seconds_jwt_exp, 26 | "sub": self.api_key, 27 | "bodyHash": sha256(json.dumps(body_json).encode("utf-8")).hexdigest() 28 | } 29 | 30 | return jwt.encode(token, key=self.private_key, algorithm="RS256") 31 | -------------------------------------------------------------------------------- /fireblocks_sdk/tokenization_api_types.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from enum import Enum 3 | from typing import Optional, List, Union, Dict 4 | 5 | from .api_types import convert_class_to_dict 6 | 7 | class CollectionLinkType(str, Enum): 8 | NON_FUNGIBLE_TOKEN = "NON_FUNGIBLE_TOKEN" 9 | SEMI_FUNGIBLE_TOKEN = "SEMI_FUNGIBLE_TOKEN" 10 | 11 | class BaseDictClass(ABC): 12 | def to_dict(self): 13 | return convert_class_to_dict(self.__dict__) 14 | 15 | 16 | class Parameter(BaseDictClass): 17 | def __init__( 18 | self, 19 | name: str, 20 | type: str, 21 | internal_type: str, 22 | description: Optional[str] = None, 23 | components: Optional[List['Parameter']] = None 24 | ): 25 | self.name = name 26 | self.type = type 27 | self.internal_type = internal_type 28 | self.description = description 29 | self.components = components 30 | 31 | 32 | class ParameterWithValue(Parameter): 33 | def __init__( 34 | self, 35 | name: str, 36 | type: str, 37 | internal_type: str, 38 | value: Union[str, int, float, bool], 39 | function_value: 'LeanAbiFunction', 40 | description: Optional[str] = None, 41 | components: Optional[List['Parameter']] = None 42 | ): 43 | super().__init__(name, type, internal_type, description, components) 44 | self.function_value = function_value 45 | self.value = value 46 | 47 | 48 | class LeanAbiFunction(BaseDictClass): 49 | def __init__(self, inputs: List[ParameterWithValue], name: Optional[str] = ""): 50 | self.inputs = inputs 51 | self.name = name 52 | 53 | 54 | class EVMTokenCreateParams(BaseDictClass): 55 | def __init__( 56 | self, 57 | contract_id: str, 58 | deploy_function_params: Optional[List[ParameterWithValue]] = None 59 | ): 60 | self.contract_id = contract_id 61 | self.deploy_function_params = deploy_function_params 62 | 63 | 64 | class StellarRippleCreateParams(BaseDictClass): 65 | def __init__(self, issuer_address: Optional[str] = None, symbol: Optional[str] = None, name: Optional[str] = None): 66 | self.symbol = symbol 67 | self.name = name 68 | self.issuer_address = issuer_address 69 | 70 | 71 | class CreateTokenRequest(BaseDictClass): 72 | def __init__( 73 | self, 74 | vault_account_id: str, 75 | create_params: Union[EVMTokenCreateParams, StellarRippleCreateParams], 76 | asset_id: Optional[str] = None, 77 | blockchain_id: Optional[str] = None, 78 | display_name: Optional[str] = None, 79 | ): 80 | self.vault_account_id = vault_account_id 81 | self.create_params = create_params 82 | self.asset_id = asset_id 83 | self.blockchain_id = blockchain_id 84 | self.display_name = display_name 85 | 86 | class CreateCollectionRequest(BaseDictClass): 87 | def __init__( 88 | self, 89 | base_asset_id: str, 90 | vault_account_id: str, 91 | type: CollectionLinkType, 92 | name: str, 93 | admin_address: str, 94 | display_name: Optional[str] = None, 95 | ): 96 | self.base_asset_id = base_asset_id 97 | self.vault_account_id = vault_account_id 98 | self.type = type 99 | self.name = name 100 | self.admin_address = admin_address 101 | self.display_name = display_name 102 | 103 | class MintCollectionTokenRequest(BaseDictClass): 104 | def __init__( 105 | self, 106 | to: str, 107 | tokenId: str, 108 | vaultAccountId: str, 109 | amount: Optional[str] = None, 110 | metadataURI: Optional[str] = None, 111 | metadata: Optional[str] = None, 112 | ): 113 | self.to = to 114 | self.tokenId = tokenId 115 | self.vaultAccountId = vaultAccountId 116 | self.amount = amount 117 | self.metadataURI = metadataURI 118 | self.metadata = metadata 119 | 120 | 121 | class BurnCollectionTokenRequest(BaseDictClass): 122 | def __init__( 123 | self, 124 | tokenId: str, 125 | vaultAccountId: str, 126 | amount: Optional[str] = None, 127 | ): 128 | self.tokenId = tokenId 129 | self.vaultAccountId = vaultAccountId 130 | self.amount = amount 131 | 132 | class ContractDeployRequest(BaseDictClass): 133 | def __init__( 134 | self, 135 | asset_id: str, 136 | vault_account_id: str, 137 | deploy_function_params: Optional[List[ParameterWithValue]] = None 138 | ): 139 | self.asset_id = asset_id 140 | self.vault_account_id = vault_account_id 141 | self.deploy_function_params = deploy_function_params 142 | 143 | 144 | class AbiFunction(BaseDictClass): 145 | def __init__( 146 | self, 147 | name: str, 148 | type: str, 149 | state_mutability: str, 150 | inputs: List[Parameter], 151 | outputs: Optional[List[Parameter]] = None, 152 | description: Optional[str] = None, 153 | returns: Optional[Dict[str, str]] = None 154 | ): 155 | self.name = name 156 | self.type = type 157 | self.state_mutability = state_mutability 158 | self.inputs = inputs 159 | self.outputs = outputs 160 | self.description = description 161 | self.returns = returns 162 | 163 | 164 | class ContractInitializationPhase(str, Enum): 165 | ON_DEPLOYMENT = "ON_DEPLOYMENT" 166 | POST_DEPLOYMENT = "POST_DEPLOYMENT" 167 | 168 | 169 | class ContractTemplateType(str, Enum): 170 | FUNGIBLE_TOKEN = "FUNGIBLE_TOKEN" 171 | NON_FUNGIBLE_TOKEN = "NON_FUNGIBLE_TOKEN" 172 | NON_TOKEN = "NON_TOKEN" 173 | UUPS_PROXY = "UUPS_PROXY" 174 | UUPS_PROXY_FUNGIBLE_TOKEN = "UUPS_PROXY_FUNGIBLE_TOKEN" 175 | UUPS_PROXY_NON_FUNGIBLE_TOKEN = "UUPS_PROXY_NON_FUNGIBLE_TOKEN" 176 | 177 | class TokenLinkType(str, Enum): 178 | FUNGIBLE_TOKEN = "FUNGIBLE_TOKEN" 179 | NON_FUNGIBLE_TOKEN = "NON_FUNGIBLE_TOKEN" 180 | 181 | class InputFieldMetadataTypes(str, Enum): 182 | EncodedFunctionCallFieldType = "encodedFunctionCall", 183 | DeployedContractAddressFieldType = "deployedContractAddress", 184 | SupportedAssetAddressFieldType = "supportedAssetAddress" 185 | 186 | 187 | class TokenLinkStatus(str, Enum): 188 | PENDING = "PENDING", 189 | COMPLETED = "COMPLETED" 190 | 191 | 192 | class EncodedFunctionCallFieldMetadata: 193 | def __init__(self, template_id: str, function_signature: str): 194 | self.template_id = template_id 195 | self.function_signature = function_signature 196 | 197 | 198 | class DeployedContractAddressFieldMetadata: 199 | def __init__(self, template_id: str): 200 | self.template_id = template_id 201 | 202 | 203 | class FieldMetadata(BaseDictClass): 204 | def __init__(self, type: InputFieldMetadataTypes, 205 | info: Union[EncodedFunctionCallFieldMetadata, DeployedContractAddressFieldMetadata]): 206 | self.type = type 207 | self.info = info 208 | 209 | 210 | class ContractUploadRequest(BaseDictClass): 211 | def __init__( 212 | self, 213 | name: str, 214 | description: str, 215 | long_description: str, 216 | bytecode: str, 217 | sourcecode: str, 218 | initialization_phase: ContractInitializationPhase, 219 | abi: Optional[List[AbiFunction]] = None, 220 | compiler_output_metadata: Optional[object] = None, 221 | docs: Optional[object] = None, 222 | attributes: Optional[Dict[str, str]] = None, 223 | type: Optional[ContractTemplateType] = None, 224 | input_fields_metadata: Optional[Dict[str, FieldMetadata]] = None, 225 | ): 226 | self.name = name 227 | self.description = description 228 | self.long_description = long_description 229 | self.bytecode = bytecode 230 | self.sourcecode = sourcecode 231 | self.initialization_phase = initialization_phase 232 | self.abi = abi 233 | self.compiler_output_metadata = compiler_output_metadata 234 | self.docs = docs 235 | self.attributes = attributes 236 | self.type = type 237 | self.input_fields_metadata = input_fields_metadata 238 | 239 | 240 | class ReadCallFunction(BaseDictClass): 241 | def __init__(self, abi_function: AbiFunction): 242 | self.abiFunction = abi_function 243 | 244 | 245 | class WriteCallFunction(BaseDictClass): 246 | def __init__( 247 | self, 248 | vault_account_id: str, 249 | abi_function: AbiFunction, 250 | amount: Optional[str] = None, 251 | fee_level: Optional[str] = None, 252 | note: Optional[str] = None, 253 | ): 254 | self.vault_account_id = vault_account_id 255 | self.abi_function = abi_function 256 | self.amount = amount 257 | self.fee_level = fee_level 258 | self.note = note 259 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'fireblocks_sdk', 4 | packages = ['fireblocks_sdk'], 5 | version = '2.16.1', 6 | license='MIT', 7 | description = 'Fireblocks python SDK', 8 | long_description="""Fireblocks python SDK""", 9 | long_description_content_type='text/markdown', 10 | url = 'https://github.com/fireblocks/fireblocks-sdk-py', 11 | download_url = 'https://github.com/fireblocks/fireblocks-sdk-py/archive/v2.16.1.tar.gz', 12 | keywords = ['Fireblocks', 'SDK'], 13 | install_requires=[ 14 | 'PyJWT>=2.8.0', 15 | 'cryptography>=2.7', 16 | 'requests>=2.22.0', 17 | ], 18 | classifiers=[ 19 | 'Development Status :: 5 - Production/Stable', 20 | 'Intended Audience :: Developers', 21 | 'Topic :: Software Development', 22 | 'License :: OSI Approved :: MIT License', 23 | 'Programming Language :: Python :: 3.8', 24 | ], 25 | ) 26 | --------------------------------------------------------------------------------