lifi-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint-type:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.10"
16
+ - run: python -m pip install --upgrade pip
17
+ - run: pip install -e '.[dev,exec]'
18
+ - name: ruff
19
+ run: ruff check .
20
+ - name: mypy --strict
21
+ run: mypy
22
+
23
+ test:
24
+ runs-on: ubuntu-latest
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: actions/setup-python@v5
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+ - run: python -m pip install --upgrade pip
35
+ - run: pip install -e '.[dev]'
36
+ - name: pytest
37
+ run: pytest -v
@@ -0,0 +1,218 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
lifi_py-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robert Ruben
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.
lifi_py-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: lifi-py
3
+ Version: 0.1.0
4
+ Summary: Python client for the LI.FI API: bridge + DEX aggregator for any-to-any cross-chain swaps.
5
+ Project-URL: Homepage, https://github.com/robertruben98/lifi-py
6
+ Project-URL: Documentation, https://docs.li.fi/api-reference/introduction
7
+ Project-URL: Repository, https://github.com/robertruben98/lifi-py
8
+ Project-URL: Issues, https://github.com/robertruben98/lifi-py/issues
9
+ Author: Robert Ruben
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: bridge,cross-chain,defi,dex,ethereum,li.fi,lifi,web3
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: httpx>=0.24
25
+ Requires-Dist: pydantic>=2.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.8; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Requires-Dist: respx>=0.20; extra == 'dev'
31
+ Requires-Dist: ruff>=0.4; extra == 'dev'
32
+ Provides-Extra: exec
33
+ Requires-Dist: web3>=6.0; extra == 'exec'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # lifi-py
37
+
38
+ A typed Python client for the [LI.FI API](https://docs.li.fi/api-reference/introduction)
39
+ — the bridge + DEX aggregator for **any-to-any cross-chain** swaps and bridges
40
+ across 74 chains.
41
+
42
+ The killer feature: `get_quote()` returns a **ready-to-sign `transactionRequest`**
43
+ directly — one call, no separate assemble step.
44
+
45
+ - Sync (`LifiClient`) and async (`AsyncLifiClient`) clients on top of `httpx`.
46
+ - All responses parsed into `pydantic` v2 models with full type hints (`py.typed`).
47
+ - Built-in rate-limit awareness: reads the `ratelimit-*` headers, throttles
48
+ proactively, and retries `429`s with backoff.
49
+ - Optional `web3.py` integration for signing/sending the returned transaction.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install lifi-py
55
+ # with web3 signing helpers:
56
+ pip install 'lifi-py[exec]'
57
+ ```
58
+
59
+ ## Quickstart — cross-chain quote with a ready-to-sign tx (keyless, <5 lines)
60
+
61
+ ```python
62
+ from lifi_py import LifiClient
63
+
64
+ quote = LifiClient().get_quote(
65
+ from_chain=42161, to_chain=8453, # Arbitrum -> Base
66
+ from_token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC (Arbitrum)
67
+ to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC (Base)
68
+ from_amount="1000000", from_address="0x47E2D28169738039755586743E2dfCF3bd643f86",
69
+ )
70
+ print(quote.estimate.to_amount, quote.transaction_request.to) # ~995000, 0x1231DEB6...
71
+ ```
72
+
73
+ `quote.transaction_request` is an EVM transaction ready to sign and broadcast.
74
+
75
+ ## Signing & sending (optional, `[exec]` extra)
76
+
77
+ ```python
78
+ tx = quote.transaction_request.as_web3_tx() # hex fields decoded to ints, web3-shaped
79
+ signed = account.sign_transaction({**tx, "nonce": w3.eth.get_transaction_count(account.address)})
80
+ tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
81
+ ```
82
+
83
+ ## Tracking a bridge to completion
84
+
85
+ ```python
86
+ client = LifiClient()
87
+ status = client.poll_status(tx_hash="0x...", bridge=quote.tool) # polls until DONE/FAILED
88
+ print(status.status, status.is_done)
89
+ ```
90
+
91
+ `get_status()` does a single check; `poll_status()` polls with exponential backoff
92
+ until the transfer reaches a terminal state (or raises `TimeoutError`).
93
+
94
+ ## Async
95
+
96
+ ```python
97
+ import asyncio
98
+ from lifi_py import AsyncLifiClient
99
+
100
+ async def main():
101
+ async with AsyncLifiClient() as client:
102
+ quote = await client.get_quote(...)
103
+
104
+ asyncio.run(main())
105
+ ```
106
+
107
+ ## Endpoints
108
+
109
+ | Method | API endpoint |
110
+ | --- | --- |
111
+ | `get_quote(...)` | `GET /quote` — best single-step route + `transactionRequest` |
112
+ | `get_status(...)` / `poll_status(...)` | `GET /status` — track a transfer |
113
+ | `get_routes(...)` | `POST /advanced/routes` — multiple candidate routes |
114
+ | `get_step_transaction(step)` | `POST /advanced/stepTransaction` — calldata for one step |
115
+ | `get_chains()` | `GET /chains` |
116
+ | `get_tokens()` | `GET /tokens` |
117
+ | `get_tools()` | `GET /tools` — bridges + exchanges |
118
+ | `get_connections(...)` | `GET /connections` |
119
+
120
+ ## Authentication & rate limits
121
+
122
+ An API key is **optional** — every endpoint above works keyless. A key only raises
123
+ your rate limits:
124
+
125
+ ```python
126
+ client = LifiClient(api_key="your-key") # sent as `x-lifi-api-key`
127
+ client = LifiClient(api_key="k", api_key_header="x-custom-key") # configurable header
128
+ client = LifiClient(base_url="https://staging.li.quest/v1") # configurable base URL
129
+ ```
130
+
131
+ The client reads the API's `ratelimit-limit` / `ratelimit-remaining` / `ratelimit-reset`
132
+ response headers (exposed via `client.rate_limit`). When the quota is exhausted it
133
+ pauses before the next request; on a `429` it retries with backoff respecting the
134
+ reset window, raising `LifiRateLimitError` once retries are exhausted.
135
+
136
+ ## Configuration
137
+
138
+ | Argument | Default | Description |
139
+ | --- | --- | --- |
140
+ | `api_key` | `None` | Optional; raises rate limits. |
141
+ | `base_url` | `https://li.quest/v1` | API base URL. |
142
+ | `api_key_header` | `x-lifi-api-key` | Header name for the key. |
143
+ | `timeout` | `30.0` | Per-request timeout (seconds). |
144
+ | `max_retries` | `3` | Max `429` retries before raising. |
145
+ | `http_client` | `None` | Inject your own `httpx.Client` / `AsyncClient`. |
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ pip install -e '.[dev,exec]'
151
+ ruff check .
152
+ mypy
153
+ pytest # unit tests (mocked HTTP, no network)
154
+ pytest -m integration # live smoke tests against the keyless API
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,124 @@
1
+ # lifi-py
2
+
3
+ A typed Python client for the [LI.FI API](https://docs.li.fi/api-reference/introduction)
4
+ — the bridge + DEX aggregator for **any-to-any cross-chain** swaps and bridges
5
+ across 74 chains.
6
+
7
+ The killer feature: `get_quote()` returns a **ready-to-sign `transactionRequest`**
8
+ directly — one call, no separate assemble step.
9
+
10
+ - Sync (`LifiClient`) and async (`AsyncLifiClient`) clients on top of `httpx`.
11
+ - All responses parsed into `pydantic` v2 models with full type hints (`py.typed`).
12
+ - Built-in rate-limit awareness: reads the `ratelimit-*` headers, throttles
13
+ proactively, and retries `429`s with backoff.
14
+ - Optional `web3.py` integration for signing/sending the returned transaction.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install lifi-py
20
+ # with web3 signing helpers:
21
+ pip install 'lifi-py[exec]'
22
+ ```
23
+
24
+ ## Quickstart — cross-chain quote with a ready-to-sign tx (keyless, <5 lines)
25
+
26
+ ```python
27
+ from lifi_py import LifiClient
28
+
29
+ quote = LifiClient().get_quote(
30
+ from_chain=42161, to_chain=8453, # Arbitrum -> Base
31
+ from_token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC (Arbitrum)
32
+ to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC (Base)
33
+ from_amount="1000000", from_address="0x47E2D28169738039755586743E2dfCF3bd643f86",
34
+ )
35
+ print(quote.estimate.to_amount, quote.transaction_request.to) # ~995000, 0x1231DEB6...
36
+ ```
37
+
38
+ `quote.transaction_request` is an EVM transaction ready to sign and broadcast.
39
+
40
+ ## Signing & sending (optional, `[exec]` extra)
41
+
42
+ ```python
43
+ tx = quote.transaction_request.as_web3_tx() # hex fields decoded to ints, web3-shaped
44
+ signed = account.sign_transaction({**tx, "nonce": w3.eth.get_transaction_count(account.address)})
45
+ tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
46
+ ```
47
+
48
+ ## Tracking a bridge to completion
49
+
50
+ ```python
51
+ client = LifiClient()
52
+ status = client.poll_status(tx_hash="0x...", bridge=quote.tool) # polls until DONE/FAILED
53
+ print(status.status, status.is_done)
54
+ ```
55
+
56
+ `get_status()` does a single check; `poll_status()` polls with exponential backoff
57
+ until the transfer reaches a terminal state (or raises `TimeoutError`).
58
+
59
+ ## Async
60
+
61
+ ```python
62
+ import asyncio
63
+ from lifi_py import AsyncLifiClient
64
+
65
+ async def main():
66
+ async with AsyncLifiClient() as client:
67
+ quote = await client.get_quote(...)
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ## Endpoints
73
+
74
+ | Method | API endpoint |
75
+ | --- | --- |
76
+ | `get_quote(...)` | `GET /quote` — best single-step route + `transactionRequest` |
77
+ | `get_status(...)` / `poll_status(...)` | `GET /status` — track a transfer |
78
+ | `get_routes(...)` | `POST /advanced/routes` — multiple candidate routes |
79
+ | `get_step_transaction(step)` | `POST /advanced/stepTransaction` — calldata for one step |
80
+ | `get_chains()` | `GET /chains` |
81
+ | `get_tokens()` | `GET /tokens` |
82
+ | `get_tools()` | `GET /tools` — bridges + exchanges |
83
+ | `get_connections(...)` | `GET /connections` |
84
+
85
+ ## Authentication & rate limits
86
+
87
+ An API key is **optional** — every endpoint above works keyless. A key only raises
88
+ your rate limits:
89
+
90
+ ```python
91
+ client = LifiClient(api_key="your-key") # sent as `x-lifi-api-key`
92
+ client = LifiClient(api_key="k", api_key_header="x-custom-key") # configurable header
93
+ client = LifiClient(base_url="https://staging.li.quest/v1") # configurable base URL
94
+ ```
95
+
96
+ The client reads the API's `ratelimit-limit` / `ratelimit-remaining` / `ratelimit-reset`
97
+ response headers (exposed via `client.rate_limit`). When the quota is exhausted it
98
+ pauses before the next request; on a `429` it retries with backoff respecting the
99
+ reset window, raising `LifiRateLimitError` once retries are exhausted.
100
+
101
+ ## Configuration
102
+
103
+ | Argument | Default | Description |
104
+ | --- | --- | --- |
105
+ | `api_key` | `None` | Optional; raises rate limits. |
106
+ | `base_url` | `https://li.quest/v1` | API base URL. |
107
+ | `api_key_header` | `x-lifi-api-key` | Header name for the key. |
108
+ | `timeout` | `30.0` | Per-request timeout (seconds). |
109
+ | `max_retries` | `3` | Max `429` retries before raising. |
110
+ | `http_client` | `None` | Inject your own `httpx.Client` / `AsyncClient`. |
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ pip install -e '.[dev,exec]'
116
+ ruff check .
117
+ mypy
118
+ pytest # unit tests (mocked HTTP, no network)
119
+ pytest -m integration # live smoke tests against the keyless API
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,29 @@
1
+ """Async variant: fetch a keyless cross-chain quote with AsyncLifiClient.
2
+
3
+ Run: python examples/async_quote.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+
10
+ from lifi_py import AsyncLifiClient
11
+
12
+
13
+ async def main() -> None:
14
+ async with AsyncLifiClient() as client:
15
+ quote = await client.get_quote(
16
+ from_chain=42161, # Arbitrum
17
+ to_chain=8453, # Base
18
+ from_token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
19
+ to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
20
+ from_amount="1000000",
21
+ from_address="0x47E2D28169738039755586743E2dfCF3bd643f86",
22
+ )
23
+ print(f"{quote.tool}: receive {quote.estimate.to_amount} USDC on Base")
24
+ assert quote.transaction_request is not None
25
+ print(f"tx to: {quote.transaction_request.to}")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ asyncio.run(main())
@@ -0,0 +1,63 @@
1
+ """Reproduce the validated flow: a keyless cross-chain quote, then status polling.
2
+
3
+ Quotes 1 USDC from Arbitrum -> Base. The quote includes a ready-to-sign
4
+ ``transactionRequest``; once you sign and broadcast it, feed the resulting tx
5
+ hash to ``poll_status`` to track the bridge to completion.
6
+
7
+ Run: python examples/quote_and_status.py
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from lifi_py import LifiClient
13
+
14
+ # Chain ids and token addresses for the validated example.
15
+ ARBITRUM = 42161
16
+ BASE = 8453
17
+ USDC_ARBITRUM = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
18
+ USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
19
+ WALLET = "0x47E2D28169738039755586743E2dfCF3bd643f86"
20
+
21
+
22
+ def main() -> None:
23
+ # No API key needed — keyless quotes work out of the box.
24
+ with LifiClient() as client:
25
+ quote = client.get_quote(
26
+ from_chain=ARBITRUM,
27
+ to_chain=BASE,
28
+ from_token=USDC_ARBITRUM,
29
+ to_token=USDC_BASE,
30
+ from_amount="1000000", # 1 USDC (6 decimals)
31
+ from_address=WALLET,
32
+ )
33
+
34
+ print(f"Best tool: {quote.tool}")
35
+ print(f"You receive: {quote.estimate.to_amount} (min {quote.estimate.to_amount_min})")
36
+ print(f"Duration: ~{quote.estimate.execution_duration}s")
37
+
38
+ tx = quote.transaction_request
39
+ assert tx is not None
40
+ print("\nReady-to-sign transaction:")
41
+ print(f" to: {tx.to}")
42
+ print(f" chainId: {tx.chain_id}")
43
+ print(f" value: {tx.value}")
44
+ print(f" data: {tx.data[:42] if tx.data else None}...")
45
+
46
+ # For signing with web3.py (extra: pip install 'lifi-py[exec]'):
47
+ # web3_tx = tx.as_web3_tx()
48
+ # signed = account.sign_transaction(web3_tx)
49
+ # tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
50
+ #
51
+ # Then track the bridge until it completes on the destination chain:
52
+ # final = client.poll_status(tx_hash=tx_hash.hex(), bridge=quote.tool)
53
+ # print(final.status) # DONE / FAILED
54
+
55
+ if client.rate_limit is not None:
56
+ print(
57
+ f"\nRate limit: {client.rate_limit.remaining}/{client.rate_limit.limit} "
58
+ f"remaining (resets in {client.rate_limit.reset}s)"
59
+ )
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()