tristero 0.1.7__tar.gz → 0.2.1__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.
- tristero-0.2.1/PKG-INFO +284 -0
- tristero-0.2.1/README.md +268 -0
- {tristero-0.1.7 → tristero-0.2.1}/pyproject.toml +3 -2
- tristero-0.2.1/src/tristero/__init__.py +18 -0
- tristero-0.2.1/src/tristero/api.py +104 -0
- tristero-0.2.1/src/tristero/client.py +192 -0
- tristero-0.2.1/src/tristero/data.py +35 -0
- tristero-0.2.1/src/tristero/files/chains.json +6557 -0
- {tristero-0.1.7 → tristero-0.2.1}/src/tristero/permit2.py +30 -27
- tristero-0.1.7/PKG-INFO +0 -157
- tristero-0.1.7/README.md +0 -142
- tristero-0.1.7/src/tristero/__init__.py +0 -12
- tristero-0.1.7/src/tristero/api.py +0 -158
- tristero-0.1.7/src/tristero/client.py +0 -147
- {tristero-0.1.7 → tristero-0.2.1}/src/tristero/config.py +0 -0
- {tristero-0.1.7 → tristero-0.2.1}/src/tristero/files/__init__.py +0 -0
- {tristero-0.1.7 → tristero-0.2.1}/src/tristero/files/erc20_abi.json +0 -0
- {tristero-0.1.7 → tristero-0.2.1}/src/tristero/files/permit2_abi.json +0 -0
- {tristero-0.1.7 → tristero-0.2.1}/src/tristero/py.typed +0 -0
tristero-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tristero
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Library for trading on Tristero
|
|
5
|
+
Author: pty1
|
|
6
|
+
Author-email: pty1 <pty11@proton.me>
|
|
7
|
+
Requires-Dist: eth-account>=0.8.0
|
|
8
|
+
Requires-Dist: glom>=25.12.0
|
|
9
|
+
Requires-Dist: httpx>=0.23.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Requires-Dist: tenacity>=8.0.0
|
|
12
|
+
Requires-Dist: web3>=6.0.0
|
|
13
|
+
Requires-Dist: websockets>=10.0
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Tristero
|
|
18
|
+
[](https://badge.fury.io/py/tristero)
|
|
19
|
+
[](https://pypi.org/project/tristero/)
|
|
20
|
+
|
|
21
|
+
This repository is home to Tristero's trading library.
|
|
22
|
+
|
|
23
|
+
### Installation
|
|
24
|
+
```
|
|
25
|
+
pip install tristero
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Quick Start
|
|
29
|
+
|
|
30
|
+
Execute a cross-chain swap in just a few lines:
|
|
31
|
+
|
|
32
|
+
```py
|
|
33
|
+
import os
|
|
34
|
+
from tristero.client import TokenSpec, execute_swap
|
|
35
|
+
from eth_account import Account
|
|
36
|
+
from web3 import AsyncWeb3
|
|
37
|
+
from tristero.api import ChainID
|
|
38
|
+
|
|
39
|
+
private_key = os.getenv("EVM_PRIVATE_KEY")
|
|
40
|
+
account = Account.from_key(private_key)
|
|
41
|
+
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://arbitrum-one-rpc.publicnode.com"))
|
|
42
|
+
|
|
43
|
+
result = await execute_swap(
|
|
44
|
+
w3=w3,
|
|
45
|
+
account=account,
|
|
46
|
+
src_t=TokenSpec(chain_id=ChainID.arbitrum, token_address="0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), # USDT
|
|
47
|
+
dst_t=TokenSpec(chain_id=ChainID.base, token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"), # USDC
|
|
48
|
+
raw_amount=10000000 # Raw token amount (multiply by 10^decimals)
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Usage Examples
|
|
53
|
+
|
|
54
|
+
#### Permit2 Swap (EVM-to-EVM)
|
|
55
|
+
|
|
56
|
+
Permit2 swaps enable seamless EVM-to-EVM cross-chain token transfers with gasless approvals and EIP-712 signed orders.
|
|
57
|
+
|
|
58
|
+
```py
|
|
59
|
+
import os
|
|
60
|
+
from tristero.client import TokenSpec, execute_permit2_swap
|
|
61
|
+
from eth_account import Account
|
|
62
|
+
from web3 import AsyncWeb3
|
|
63
|
+
from tristero.api import ChainID
|
|
64
|
+
|
|
65
|
+
private_key = os.getenv("EVM_PRIVATE_KEY")
|
|
66
|
+
account = Account.from_key(private_key)
|
|
67
|
+
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://mainnet.base.org"))
|
|
68
|
+
|
|
69
|
+
# Example: USDC on Base → USDT on Avalanche
|
|
70
|
+
result = await execute_permit2_swap(
|
|
71
|
+
w3=w3,
|
|
72
|
+
account=account,
|
|
73
|
+
src_t=TokenSpec(chain_id=ChainID.base, token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"), # USDC on Base
|
|
74
|
+
dst_t=TokenSpec(chain_id=ChainID.avalanche, token_address="0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"), # USDT on Avalanche
|
|
75
|
+
raw_amount=10 * 10**6 # 10 USDC (6 decimals)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
print(f"Swap completed: {result}")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Feather Swap (UTXO-to-UTXO)
|
|
82
|
+
|
|
83
|
+
Feather swaps facilitate cross-chain transfers between UTXO-based chains like Bitcoin and Monero, providing a deposit address for manual fund transfers.
|
|
84
|
+
|
|
85
|
+
```py
|
|
86
|
+
import asyncio
|
|
87
|
+
from tristero.client import TokenSpec, start_feather_swap, wait_for_feather_completion
|
|
88
|
+
from tristero.api import ChainID
|
|
89
|
+
|
|
90
|
+
# Example: Bitcoin → Monero
|
|
91
|
+
async def btc_to_xmr_swap():
|
|
92
|
+
# Start the swap to get a deposit address
|
|
93
|
+
swap_result = await start_feather_swap(
|
|
94
|
+
src_t=TokenSpec(chain_id=ChainID.bitcoin, token_address="native"), # BTC
|
|
95
|
+
dst_t=TokenSpec(chain_id=ChainID.monero, token_address="native"), # XMR
|
|
96
|
+
dst_addr="YOUR_XMR_RECEIVING_ADDRESS", # Your Monero destination address
|
|
97
|
+
raw_amount=100000 # Amount in satoshis (0.001 BTC)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print(f"Send {swap_result.data['amount']} satoshis to: {swap_result.deposit_address}")
|
|
101
|
+
|
|
102
|
+
# Wait for swap completion after sending funds
|
|
103
|
+
result = await wait_for_feather_completion(swap_result.data['id'])
|
|
104
|
+
print(f"Swap completed: {result}")
|
|
105
|
+
|
|
106
|
+
# Run the swap
|
|
107
|
+
asyncio.run(btc_to_xmr_swap())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Two-Step Permit2 Swap
|
|
111
|
+
|
|
112
|
+
For more control, you can separate the swap initiation from monitoring:
|
|
113
|
+
|
|
114
|
+
```py
|
|
115
|
+
from tristero.client import TokenSpec, start_permit2_swap, wait_for_completion_with_retry
|
|
116
|
+
from eth_account import Account
|
|
117
|
+
from web3 import AsyncWeb3
|
|
118
|
+
from tristero.api import ChainID
|
|
119
|
+
|
|
120
|
+
# Step 1: Start the swap
|
|
121
|
+
account = Account.from_key(os.getenv("EVM_PRIVATE_KEY"))
|
|
122
|
+
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://arbitrum-one-rpc.publicnode.com"))
|
|
123
|
+
|
|
124
|
+
order_id = await start_permit2_swap(
|
|
125
|
+
w3=w3,
|
|
126
|
+
account=account,
|
|
127
|
+
src_t=TokenSpec(chain_id=ChainID.arbitrum, token_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), # USDC
|
|
128
|
+
dst_t=TokenSpec(chain_id=ChainID.polygon, token_address="0xc2132D05D31c914a87C6611C10748AEb04B58e8F"), # USDT
|
|
129
|
+
raw_amount=50 * 10**6 # 50 USDC
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
print(f"Swap initiated with order ID: {order_id}")
|
|
133
|
+
|
|
134
|
+
# Step 2: Wait for completion with retry logic
|
|
135
|
+
result = await wait_for_completion_with_retry(order_id, feather=False)
|
|
136
|
+
print(f"Swap completed successfully!")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### How it works
|
|
140
|
+
|
|
141
|
+
Tristero supports two primary swap mechanisms:
|
|
142
|
+
|
|
143
|
+
#### Permit2 Swaps (EVM-to-EVM)
|
|
144
|
+
- **Quote & Approve** - Request a quote and approve tokens via Permit2 (gasless approval)
|
|
145
|
+
- **Sign & Submit** - Sign an EIP-712 order and submit for execution
|
|
146
|
+
- **Monitor** - Track swap progress via WebSocket updates
|
|
147
|
+
|
|
148
|
+
#### Feather Swaps (UTXO-based)
|
|
149
|
+
- **Quote & Deposit** - Request a quote to receive a deposit address
|
|
150
|
+
- **Manual Transfer** - Send funds to the provided deposit address
|
|
151
|
+
- **Monitor** - Track swap completion via WebSocket updates
|
|
152
|
+
|
|
153
|
+
This library provides both high-level convenience functions and lower-level components for precise control.
|
|
154
|
+
|
|
155
|
+
### API Reference
|
|
156
|
+
|
|
157
|
+
#### Execute Full Swap
|
|
158
|
+
`execute_swap` handles the entire workflow automatically: quoting, signing, submitting, and monitoring.
|
|
159
|
+
```py
|
|
160
|
+
from tristero.client import execute_swap, TokenSpec
|
|
161
|
+
from web3 import AsyncWeb3
|
|
162
|
+
from eth_account.signers.local import LocalAccount
|
|
163
|
+
|
|
164
|
+
w3 = AsyncWeb3(...) # Your Web3 provider
|
|
165
|
+
account: LocalAccount = ... # Your account
|
|
166
|
+
|
|
167
|
+
result = await execute_swap(
|
|
168
|
+
w3=w3,
|
|
169
|
+
account=account,
|
|
170
|
+
src_t=TokenSpec(chain_id=ChainID.ethereum, token_address="0xA0b8..."),
|
|
171
|
+
dst_t=TokenSpec(chain_id=ChainID.arbitrum, token_address="0xaf88..."),
|
|
172
|
+
raw_amount=10*(10**18),
|
|
173
|
+
retry=True,
|
|
174
|
+
timeout=300.0 # 5 minutes
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Execute Permit2 Swap
|
|
179
|
+
`execute_permit2_swap` handles EVM-to-EVM swaps with Permit2 integration.
|
|
180
|
+
```py
|
|
181
|
+
from tristero.client import execute_permit2_swap, TokenSpec
|
|
182
|
+
from web3 import AsyncWeb3
|
|
183
|
+
from eth_account.signers.local import LocalAccount
|
|
184
|
+
|
|
185
|
+
w3 = AsyncWeb3(...) # Your Web3 provider
|
|
186
|
+
account: LocalAccount = ... # Your account
|
|
187
|
+
|
|
188
|
+
result = await execute_permit2_swap(
|
|
189
|
+
w3=w3,
|
|
190
|
+
account=account,
|
|
191
|
+
src_t=TokenSpec(chain_id=ChainID.base, token_address="0x833589f..."), # USDC
|
|
192
|
+
dst_t=TokenSpec(chain_id=ChainID.avalanche, token_address="0x9702230..."), # USDT
|
|
193
|
+
raw_amount=10*(10**6), # 10 USDC (6 decimals)
|
|
194
|
+
retry=True,
|
|
195
|
+
timeout=300.0 # 5 minutes
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Start Feather Swap
|
|
200
|
+
`start_feather_swap` initiates UTXO-based swaps and returns deposit information.
|
|
201
|
+
```py
|
|
202
|
+
from tristero.client import start_feather_swap, TokenSpec
|
|
203
|
+
|
|
204
|
+
swap_result = await start_feather_swap(
|
|
205
|
+
src_t=TokenSpec(chain_id=ChainID.bitcoin, token_address="native"),
|
|
206
|
+
dst_t=TokenSpec(chain_id=ChainID.monero, token_address="native"),
|
|
207
|
+
dst_addr="YOUR_DESTINATION_ADDRESS",
|
|
208
|
+
raw_amount=100000 # Amount in smallest unit
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
deposit_address = swap_result.deposit_address
|
|
212
|
+
order_id = swap_result.data['id']
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Requesting a quote
|
|
216
|
+
|
|
217
|
+
`get_quote` requests a quote for a particular swap, letting you see output amounts and gas fees.
|
|
218
|
+
|
|
219
|
+
```py
|
|
220
|
+
from tristero.api import get_quote, ChainID
|
|
221
|
+
|
|
222
|
+
quote = await get_quote(
|
|
223
|
+
from_wallet="0x1234...", # Source wallet address
|
|
224
|
+
to_wallet="0x5678...", # Destination wallet address
|
|
225
|
+
from_chain_id=ChainID.ethereum, # Source chain
|
|
226
|
+
from_address="0xA0b8...", # Source token address (or "native")
|
|
227
|
+
to_chain_id=ChainID.arbitrum, # Destination chain
|
|
228
|
+
to_address="0xaf88...", # Destination token address (or "native")
|
|
229
|
+
amount=10*(10**18), # Amount in smallest unit (wei)
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Creating a signed order
|
|
234
|
+
`create_order` creates and signs an order without submitting to be filled.
|
|
235
|
+
|
|
236
|
+
```py
|
|
237
|
+
from tristero.api import get_quote, ChainID
|
|
238
|
+
|
|
239
|
+
w3 = AsyncWeb3(...) # Your Web3 provider
|
|
240
|
+
account: LocalAccount = ... # Your account
|
|
241
|
+
|
|
242
|
+
data, sig = await create_order(
|
|
243
|
+
w3,
|
|
244
|
+
account,
|
|
245
|
+
from_chain_id=ChainID.ethereum,
|
|
246
|
+
from_address="0xA0b8...",
|
|
247
|
+
to_chain_id=ChainID.arbitrum,
|
|
248
|
+
to_address="0xaf88...",
|
|
249
|
+
raw_amount=10*(10**18),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Submit order
|
|
255
|
+
|
|
256
|
+
`fill_order` submits a signed order for execution.
|
|
257
|
+
|
|
258
|
+
```py
|
|
259
|
+
from tristero.api import fill_order
|
|
260
|
+
|
|
261
|
+
data, sig = ... # from earlier
|
|
262
|
+
|
|
263
|
+
response = await fill_order(
|
|
264
|
+
signature=str(sig.signature.to_0x_hex()),
|
|
265
|
+
domain=data.domain.model_dump(by_alias=True, mode="json"),
|
|
266
|
+
message=data.message.model_dump(by_alias=True, mode="json"),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
order_id = response['id']
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Monitoring with Built-in Functions
|
|
273
|
+
|
|
274
|
+
For convenience, use the built-in monitoring functions:
|
|
275
|
+
|
|
276
|
+
```py
|
|
277
|
+
from tristero.client import wait_for_completion_with_retry
|
|
278
|
+
|
|
279
|
+
# Monitor Permit2 swap with retry logic
|
|
280
|
+
result = await wait_for_completion_with_retry(order_id, feather=False)
|
|
281
|
+
|
|
282
|
+
# Monitor Feather swap with retry logic
|
|
283
|
+
result = await wait_for_completion_with_retry(order_id, feather=True)
|
|
284
|
+
```
|
tristero-0.2.1/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Tristero
|
|
2
|
+
[](https://badge.fury.io/py/tristero)
|
|
3
|
+
[](https://pypi.org/project/tristero/)
|
|
4
|
+
|
|
5
|
+
This repository is home to Tristero's trading library.
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
```
|
|
9
|
+
pip install tristero
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Quick Start
|
|
13
|
+
|
|
14
|
+
Execute a cross-chain swap in just a few lines:
|
|
15
|
+
|
|
16
|
+
```py
|
|
17
|
+
import os
|
|
18
|
+
from tristero.client import TokenSpec, execute_swap
|
|
19
|
+
from eth_account import Account
|
|
20
|
+
from web3 import AsyncWeb3
|
|
21
|
+
from tristero.api import ChainID
|
|
22
|
+
|
|
23
|
+
private_key = os.getenv("EVM_PRIVATE_KEY")
|
|
24
|
+
account = Account.from_key(private_key)
|
|
25
|
+
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://arbitrum-one-rpc.publicnode.com"))
|
|
26
|
+
|
|
27
|
+
result = await execute_swap(
|
|
28
|
+
w3=w3,
|
|
29
|
+
account=account,
|
|
30
|
+
src_t=TokenSpec(chain_id=ChainID.arbitrum, token_address="0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), # USDT
|
|
31
|
+
dst_t=TokenSpec(chain_id=ChainID.base, token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"), # USDC
|
|
32
|
+
raw_amount=10000000 # Raw token amount (multiply by 10^decimals)
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Usage Examples
|
|
37
|
+
|
|
38
|
+
#### Permit2 Swap (EVM-to-EVM)
|
|
39
|
+
|
|
40
|
+
Permit2 swaps enable seamless EVM-to-EVM cross-chain token transfers with gasless approvals and EIP-712 signed orders.
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
import os
|
|
44
|
+
from tristero.client import TokenSpec, execute_permit2_swap
|
|
45
|
+
from eth_account import Account
|
|
46
|
+
from web3 import AsyncWeb3
|
|
47
|
+
from tristero.api import ChainID
|
|
48
|
+
|
|
49
|
+
private_key = os.getenv("EVM_PRIVATE_KEY")
|
|
50
|
+
account = Account.from_key(private_key)
|
|
51
|
+
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://mainnet.base.org"))
|
|
52
|
+
|
|
53
|
+
# Example: USDC on Base → USDT on Avalanche
|
|
54
|
+
result = await execute_permit2_swap(
|
|
55
|
+
w3=w3,
|
|
56
|
+
account=account,
|
|
57
|
+
src_t=TokenSpec(chain_id=ChainID.base, token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"), # USDC on Base
|
|
58
|
+
dst_t=TokenSpec(chain_id=ChainID.avalanche, token_address="0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"), # USDT on Avalanche
|
|
59
|
+
raw_amount=10 * 10**6 # 10 USDC (6 decimals)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
print(f"Swap completed: {result}")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Feather Swap (UTXO-to-UTXO)
|
|
66
|
+
|
|
67
|
+
Feather swaps facilitate cross-chain transfers between UTXO-based chains like Bitcoin and Monero, providing a deposit address for manual fund transfers.
|
|
68
|
+
|
|
69
|
+
```py
|
|
70
|
+
import asyncio
|
|
71
|
+
from tristero.client import TokenSpec, start_feather_swap, wait_for_feather_completion
|
|
72
|
+
from tristero.api import ChainID
|
|
73
|
+
|
|
74
|
+
# Example: Bitcoin → Monero
|
|
75
|
+
async def btc_to_xmr_swap():
|
|
76
|
+
# Start the swap to get a deposit address
|
|
77
|
+
swap_result = await start_feather_swap(
|
|
78
|
+
src_t=TokenSpec(chain_id=ChainID.bitcoin, token_address="native"), # BTC
|
|
79
|
+
dst_t=TokenSpec(chain_id=ChainID.monero, token_address="native"), # XMR
|
|
80
|
+
dst_addr="YOUR_XMR_RECEIVING_ADDRESS", # Your Monero destination address
|
|
81
|
+
raw_amount=100000 # Amount in satoshis (0.001 BTC)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
print(f"Send {swap_result.data['amount']} satoshis to: {swap_result.deposit_address}")
|
|
85
|
+
|
|
86
|
+
# Wait for swap completion after sending funds
|
|
87
|
+
result = await wait_for_feather_completion(swap_result.data['id'])
|
|
88
|
+
print(f"Swap completed: {result}")
|
|
89
|
+
|
|
90
|
+
# Run the swap
|
|
91
|
+
asyncio.run(btc_to_xmr_swap())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Two-Step Permit2 Swap
|
|
95
|
+
|
|
96
|
+
For more control, you can separate the swap initiation from monitoring:
|
|
97
|
+
|
|
98
|
+
```py
|
|
99
|
+
from tristero.client import TokenSpec, start_permit2_swap, wait_for_completion_with_retry
|
|
100
|
+
from eth_account import Account
|
|
101
|
+
from web3 import AsyncWeb3
|
|
102
|
+
from tristero.api import ChainID
|
|
103
|
+
|
|
104
|
+
# Step 1: Start the swap
|
|
105
|
+
account = Account.from_key(os.getenv("EVM_PRIVATE_KEY"))
|
|
106
|
+
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://arbitrum-one-rpc.publicnode.com"))
|
|
107
|
+
|
|
108
|
+
order_id = await start_permit2_swap(
|
|
109
|
+
w3=w3,
|
|
110
|
+
account=account,
|
|
111
|
+
src_t=TokenSpec(chain_id=ChainID.arbitrum, token_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), # USDC
|
|
112
|
+
dst_t=TokenSpec(chain_id=ChainID.polygon, token_address="0xc2132D05D31c914a87C6611C10748AEb04B58e8F"), # USDT
|
|
113
|
+
raw_amount=50 * 10**6 # 50 USDC
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
print(f"Swap initiated with order ID: {order_id}")
|
|
117
|
+
|
|
118
|
+
# Step 2: Wait for completion with retry logic
|
|
119
|
+
result = await wait_for_completion_with_retry(order_id, feather=False)
|
|
120
|
+
print(f"Swap completed successfully!")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### How it works
|
|
124
|
+
|
|
125
|
+
Tristero supports two primary swap mechanisms:
|
|
126
|
+
|
|
127
|
+
#### Permit2 Swaps (EVM-to-EVM)
|
|
128
|
+
- **Quote & Approve** - Request a quote and approve tokens via Permit2 (gasless approval)
|
|
129
|
+
- **Sign & Submit** - Sign an EIP-712 order and submit for execution
|
|
130
|
+
- **Monitor** - Track swap progress via WebSocket updates
|
|
131
|
+
|
|
132
|
+
#### Feather Swaps (UTXO-based)
|
|
133
|
+
- **Quote & Deposit** - Request a quote to receive a deposit address
|
|
134
|
+
- **Manual Transfer** - Send funds to the provided deposit address
|
|
135
|
+
- **Monitor** - Track swap completion via WebSocket updates
|
|
136
|
+
|
|
137
|
+
This library provides both high-level convenience functions and lower-level components for precise control.
|
|
138
|
+
|
|
139
|
+
### API Reference
|
|
140
|
+
|
|
141
|
+
#### Execute Full Swap
|
|
142
|
+
`execute_swap` handles the entire workflow automatically: quoting, signing, submitting, and monitoring.
|
|
143
|
+
```py
|
|
144
|
+
from tristero.client import execute_swap, TokenSpec
|
|
145
|
+
from web3 import AsyncWeb3
|
|
146
|
+
from eth_account.signers.local import LocalAccount
|
|
147
|
+
|
|
148
|
+
w3 = AsyncWeb3(...) # Your Web3 provider
|
|
149
|
+
account: LocalAccount = ... # Your account
|
|
150
|
+
|
|
151
|
+
result = await execute_swap(
|
|
152
|
+
w3=w3,
|
|
153
|
+
account=account,
|
|
154
|
+
src_t=TokenSpec(chain_id=ChainID.ethereum, token_address="0xA0b8..."),
|
|
155
|
+
dst_t=TokenSpec(chain_id=ChainID.arbitrum, token_address="0xaf88..."),
|
|
156
|
+
raw_amount=10*(10**18),
|
|
157
|
+
retry=True,
|
|
158
|
+
timeout=300.0 # 5 minutes
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Execute Permit2 Swap
|
|
163
|
+
`execute_permit2_swap` handles EVM-to-EVM swaps with Permit2 integration.
|
|
164
|
+
```py
|
|
165
|
+
from tristero.client import execute_permit2_swap, TokenSpec
|
|
166
|
+
from web3 import AsyncWeb3
|
|
167
|
+
from eth_account.signers.local import LocalAccount
|
|
168
|
+
|
|
169
|
+
w3 = AsyncWeb3(...) # Your Web3 provider
|
|
170
|
+
account: LocalAccount = ... # Your account
|
|
171
|
+
|
|
172
|
+
result = await execute_permit2_swap(
|
|
173
|
+
w3=w3,
|
|
174
|
+
account=account,
|
|
175
|
+
src_t=TokenSpec(chain_id=ChainID.base, token_address="0x833589f..."), # USDC
|
|
176
|
+
dst_t=TokenSpec(chain_id=ChainID.avalanche, token_address="0x9702230..."), # USDT
|
|
177
|
+
raw_amount=10*(10**6), # 10 USDC (6 decimals)
|
|
178
|
+
retry=True,
|
|
179
|
+
timeout=300.0 # 5 minutes
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Start Feather Swap
|
|
184
|
+
`start_feather_swap` initiates UTXO-based swaps and returns deposit information.
|
|
185
|
+
```py
|
|
186
|
+
from tristero.client import start_feather_swap, TokenSpec
|
|
187
|
+
|
|
188
|
+
swap_result = await start_feather_swap(
|
|
189
|
+
src_t=TokenSpec(chain_id=ChainID.bitcoin, token_address="native"),
|
|
190
|
+
dst_t=TokenSpec(chain_id=ChainID.monero, token_address="native"),
|
|
191
|
+
dst_addr="YOUR_DESTINATION_ADDRESS",
|
|
192
|
+
raw_amount=100000 # Amount in smallest unit
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
deposit_address = swap_result.deposit_address
|
|
196
|
+
order_id = swap_result.data['id']
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Requesting a quote
|
|
200
|
+
|
|
201
|
+
`get_quote` requests a quote for a particular swap, letting you see output amounts and gas fees.
|
|
202
|
+
|
|
203
|
+
```py
|
|
204
|
+
from tristero.api import get_quote, ChainID
|
|
205
|
+
|
|
206
|
+
quote = await get_quote(
|
|
207
|
+
from_wallet="0x1234...", # Source wallet address
|
|
208
|
+
to_wallet="0x5678...", # Destination wallet address
|
|
209
|
+
from_chain_id=ChainID.ethereum, # Source chain
|
|
210
|
+
from_address="0xA0b8...", # Source token address (or "native")
|
|
211
|
+
to_chain_id=ChainID.arbitrum, # Destination chain
|
|
212
|
+
to_address="0xaf88...", # Destination token address (or "native")
|
|
213
|
+
amount=10*(10**18), # Amount in smallest unit (wei)
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Creating a signed order
|
|
218
|
+
`create_order` creates and signs an order without submitting to be filled.
|
|
219
|
+
|
|
220
|
+
```py
|
|
221
|
+
from tristero.api import get_quote, ChainID
|
|
222
|
+
|
|
223
|
+
w3 = AsyncWeb3(...) # Your Web3 provider
|
|
224
|
+
account: LocalAccount = ... # Your account
|
|
225
|
+
|
|
226
|
+
data, sig = await create_order(
|
|
227
|
+
w3,
|
|
228
|
+
account,
|
|
229
|
+
from_chain_id=ChainID.ethereum,
|
|
230
|
+
from_address="0xA0b8...",
|
|
231
|
+
to_chain_id=ChainID.arbitrum,
|
|
232
|
+
to_address="0xaf88...",
|
|
233
|
+
raw_amount=10*(10**18),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Submit order
|
|
239
|
+
|
|
240
|
+
`fill_order` submits a signed order for execution.
|
|
241
|
+
|
|
242
|
+
```py
|
|
243
|
+
from tristero.api import fill_order
|
|
244
|
+
|
|
245
|
+
data, sig = ... # from earlier
|
|
246
|
+
|
|
247
|
+
response = await fill_order(
|
|
248
|
+
signature=str(sig.signature.to_0x_hex()),
|
|
249
|
+
domain=data.domain.model_dump(by_alias=True, mode="json"),
|
|
250
|
+
message=data.message.model_dump(by_alias=True, mode="json"),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
order_id = response['id']
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### Monitoring with Built-in Functions
|
|
257
|
+
|
|
258
|
+
For convenience, use the built-in monitoring functions:
|
|
259
|
+
|
|
260
|
+
```py
|
|
261
|
+
from tristero.client import wait_for_completion_with_retry
|
|
262
|
+
|
|
263
|
+
# Monitor Permit2 swap with retry logic
|
|
264
|
+
result = await wait_for_completion_with_retry(order_id, feather=False)
|
|
265
|
+
|
|
266
|
+
# Monitor Feather swap with retry logic
|
|
267
|
+
result = await wait_for_completion_with_retry(order_id, feather=True)
|
|
268
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tristero"
|
|
3
|
-
version = "0.1
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "Library for trading on Tristero"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -9,11 +9,12 @@ authors = [
|
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"eth-account>=0.8.0",
|
|
12
|
+
"glom>=25.12.0",
|
|
12
13
|
"httpx>=0.23.0",
|
|
13
14
|
"pydantic>=2.0.0",
|
|
14
15
|
"tenacity>=8.0.0",
|
|
15
16
|
"web3>=6.0.0",
|
|
16
|
-
"websockets>=10.0"
|
|
17
|
+
"websockets>=10.0",
|
|
17
18
|
]
|
|
18
19
|
|
|
19
20
|
[build-system]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from tristero.client import OrderFailedException, StuckException, SwapException, TokenSpec, execute_permit2_swap, start_permit2_swap, start_feather_swap, wait_for_feather_completion, wait_for_completion, wait_for_completion_with_retry
|
|
2
|
+
from tristero.config import set_config
|
|
3
|
+
from tristero.data import ChainID
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ChainID",
|
|
7
|
+
"TokenSpec",
|
|
8
|
+
"StuckException",
|
|
9
|
+
"OrderFailedException",
|
|
10
|
+
"SwapException",
|
|
11
|
+
"start_permit2_swap",
|
|
12
|
+
"start_feather_swap",
|
|
13
|
+
"execute_permit2_swap",
|
|
14
|
+
"wait_for_feather_completion",
|
|
15
|
+
"wait_for_completion",
|
|
16
|
+
"wait_for_completion_with_retry",
|
|
17
|
+
"set_config",
|
|
18
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
import httpx
|
|
3
|
+
from websockets.asyncio.client import connect
|
|
4
|
+
from tristero.config import get_config
|
|
5
|
+
from tristero.data import get_gas_addr
|
|
6
|
+
|
|
7
|
+
class APIException(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
class QuoteException(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def handle_resp(resp: httpx.Response):
|
|
14
|
+
try:
|
|
15
|
+
resp.raise_for_status()
|
|
16
|
+
data = resp.json()
|
|
17
|
+
return data
|
|
18
|
+
except Exception as e:
|
|
19
|
+
try:
|
|
20
|
+
data = resp.json()
|
|
21
|
+
except Exception as json_e:
|
|
22
|
+
if resp.text:
|
|
23
|
+
raise APIException(resp.text)
|
|
24
|
+
else:
|
|
25
|
+
raise e
|
|
26
|
+
raise APIException(data)
|
|
27
|
+
|
|
28
|
+
async def t_get(client: httpx.AsyncClient, url: str):
|
|
29
|
+
resp = await client.get(url, headers=get_config().headers, timeout=20)
|
|
30
|
+
return handle_resp(resp)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def t_post(client: httpx.AsyncClient, url: str, body: Optional[dict[str, Any]]):
|
|
34
|
+
resp = await client.post(url, headers=get_config().headers, json=body, timeout=20)
|
|
35
|
+
return handle_resp(resp)
|
|
36
|
+
|
|
37
|
+
def or_native(chain_id: str, address: str):
|
|
38
|
+
return get_gas_addr(chain_id) if address == "native" else address
|
|
39
|
+
|
|
40
|
+
c = httpx.AsyncClient()
|
|
41
|
+
|
|
42
|
+
async def get_quote(
|
|
43
|
+
from_wallet: str,
|
|
44
|
+
to_wallet: str,
|
|
45
|
+
from_chain_id: str,
|
|
46
|
+
from_address: str,
|
|
47
|
+
to_chain_id: str,
|
|
48
|
+
to_address: str,
|
|
49
|
+
amount: int,
|
|
50
|
+
):
|
|
51
|
+
from_chain_id = from_chain_id
|
|
52
|
+
to_chain_id = to_chain_id
|
|
53
|
+
|
|
54
|
+
from_token_address = or_native(from_chain_id, from_address)
|
|
55
|
+
to_token_address = or_native(to_chain_id, to_address)
|
|
56
|
+
|
|
57
|
+
data = {
|
|
58
|
+
"src_chain_id": from_chain_id,
|
|
59
|
+
"src_token_address": from_token_address,
|
|
60
|
+
"src_token_quantity": str(int(amount)),
|
|
61
|
+
"src_wallet_address": from_wallet,
|
|
62
|
+
"dst_chain_id": to_chain_id,
|
|
63
|
+
"dst_token_address": to_token_address,
|
|
64
|
+
"dst_wallet_address": to_wallet,
|
|
65
|
+
}
|
|
66
|
+
resp = await c.post(
|
|
67
|
+
get_config().quoter_url,
|
|
68
|
+
json=data,
|
|
69
|
+
)
|
|
70
|
+
# print(data, resp)
|
|
71
|
+
try:
|
|
72
|
+
return handle_resp(resp)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise QuoteException(e, data) from e
|
|
75
|
+
|
|
76
|
+
async def fill_order(signature: str, domain: dict[str, Any], message: dict[str, Any]):
|
|
77
|
+
data = {"signature": signature, "domain": domain, "message": message}
|
|
78
|
+
resp = await c.post(
|
|
79
|
+
get_config().filler_url,
|
|
80
|
+
json=data,
|
|
81
|
+
)
|
|
82
|
+
return handle_resp(resp)
|
|
83
|
+
|
|
84
|
+
async def fill_order_feather(src_chain: str, dst_chain: str, dst_address: str, amount: int, client_id: str = ''):
|
|
85
|
+
data = {
|
|
86
|
+
"client_id": client_id,
|
|
87
|
+
"src_chain": src_chain,
|
|
88
|
+
"dst_chain": dst_chain,
|
|
89
|
+
"dst_address": dst_address,
|
|
90
|
+
"amount": amount
|
|
91
|
+
}
|
|
92
|
+
resp = await c.post(
|
|
93
|
+
get_config().filler_url,
|
|
94
|
+
json=data,
|
|
95
|
+
)
|
|
96
|
+
return handle_resp(resp)
|
|
97
|
+
|
|
98
|
+
async def poll_updates(order_id: str):
|
|
99
|
+
ws = await connect(f"{get_config().ws_url}/{order_id}")
|
|
100
|
+
return ws
|
|
101
|
+
|
|
102
|
+
async def poll_updates_feather(order_id: str):
|
|
103
|
+
ws = await connect(f"{get_config().ws_url}/feather/{order_id}")
|
|
104
|
+
return ws
|