tristero 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

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/permit2.py CHANGED
@@ -1,23 +1,23 @@
1
1
  from dataclasses import dataclass
2
2
  import logging
3
- from typing import Any, List, Literal, Optional, TypeVar, Union, cast
3
+ import random
4
+ import time
5
+ from typing import Any, Dict, List, Optional, TypeVar
4
6
  from eth_account import Account
5
7
  from eth_account.datastructures import SignedMessage, SignedTransaction
6
- from eth_account.signers.base import BaseAccount
7
8
  from eth_account.signers.local import LocalAccount
8
- from eth_account.types import TransactionDictType
9
- from pydantic import BaseModel, ConfigDict, Field, field_validator
9
+ from eth_account.messages import encode_typed_data
10
+ from pydantic import BaseModel, ConfigDict, Field
10
11
  from pydantic.alias_generators import to_camel
11
12
  from web3 import AsyncBaseProvider, AsyncWeb3
12
- import random
13
13
  from web3.contract import AsyncContract
14
14
  from functools import cache, lru_cache
15
15
  import json
16
- from pathlib import Path
17
16
  from importlib import resources as impresources
18
17
 
19
18
  from web3 import Web3
20
19
  from web3.eth import AsyncEth
20
+ from eth_utils import to_checksum_address
21
21
 
22
22
  from tristero.api import get_quote
23
23
 
@@ -25,23 +25,36 @@ from .data import (
25
25
  get_permit2_addr,
26
26
  get_wrapped_gas_addr,
27
27
  )
28
+ from .eip712 import (
29
+ EIP712OrderParameters,
30
+ EIP712TokenPermissions,
31
+ EIP712SignedOrder,
32
+ EIP712PermitWitnessTransferFromOrder,
33
+ EIP712CloseWithSwap,
34
+ get_escrow_domain,
35
+ get_close_with_swap_types,
36
+ )
28
37
 
29
38
  logger = logging.getLogger(__name__)
30
39
 
31
40
  P = TypeVar("P", bound=AsyncBaseProvider)
32
41
 
42
+ DEFAULT_PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
43
+
33
44
  PERMIT2_ABI_FILE = impresources.files("tristero.files") / "permit2_abi.json"
34
45
  ERC20_ABI_FILE = impresources.files("tristero.files") / "erc20_abi.json"
35
46
 
36
47
  PERMIT2_ABI = json.loads(PERMIT2_ABI_FILE.read_text())
37
48
  ERC20_ABI = json.loads(ERC20_ABI_FILE.read_text())
38
49
 
50
+
39
51
  @lru_cache(maxsize=None)
40
- def get_permit2(eth: AsyncEth, permit2_address: str):
52
+ def get_permit2_contract(eth: AsyncEth, permit2_address: str):
41
53
  return eth.contract(
42
54
  address=Web3.to_checksum_address(permit2_address), abi=PERMIT2_ABI
43
55
  )
44
56
 
57
+
45
58
  @cache
46
59
  def get_erc20_contract(w3: AsyncWeb3[P], token_address: str) -> AsyncContract:
47
60
  """Get ERC20 contract instance."""
@@ -149,6 +162,11 @@ async def get_permit2_unordered_nonce(c: AsyncContract, wallet_address: str):
149
162
  return wordPos
150
163
 
151
164
 
165
+ def get_random_nonce() -> int:
166
+ """Generate a random nonce for EIP-712 signing."""
167
+ return random.randint(2**128, 2**256 - 1)
168
+
169
+
152
170
  async def prepare_data_for_signature(
153
171
  eth: AsyncEth,
154
172
  sell_data: ChainData,
@@ -170,7 +188,6 @@ async def prepare_data_for_signature(
170
188
  Returns:
171
189
  SignatureData with domain, types, primaryType, and message for signing
172
190
  """
173
- # Validate required fields
174
191
  if not quote.order_data.parameters.min_quantity:
175
192
  raise ValueError("Min quantity is required in the order_data.parameters")
176
193
 
@@ -184,14 +201,12 @@ async def prepare_data_for_signature(
184
201
 
185
202
  deadline = quote.order_data.deadline
186
203
 
187
- # Handle native token address conversion
188
204
  token_address = (
189
205
  get_wrapped_gas_addr(from_chain)
190
206
  if sell_data.token.address == "native"
191
207
  else sell_data.token.address
192
208
  )
193
209
 
194
- # Build witness object
195
210
  witness = SignedOrder(
196
211
  sender=wallet_address,
197
212
  parameters=OrderParameters(
@@ -209,24 +224,21 @@ async def prepare_data_for_signature(
209
224
  custom_data=quote.order_data.custom_data or [],
210
225
  )
211
226
 
212
- # Get Permit2 address
213
227
  permit2_address = get_permit2_addr(from_chain)
214
228
  if not permit2_address:
215
229
  raise ValueError("Permit2 not deployed on this chain.")
216
230
 
217
231
  spender = quote.order_data.router_address
218
232
  nonce = await get_permit2_unordered_nonce(
219
- get_permit2(eth, permit2_address), wallet_address
233
+ get_permit2_contract(eth, permit2_address), wallet_address
220
234
  )
221
235
 
222
- # EIP-712 domain
223
236
  domain = EIP712Domain(
224
237
  name="Permit2",
225
238
  chain_id=sell_data.chain_id,
226
239
  verifying_contract=permit2_address,
227
240
  )
228
241
 
229
- # EIP-712 types
230
242
  types = {
231
243
  "TokenPermissions": [
232
244
  {"name": "token", "type": "address"},
@@ -258,7 +270,6 @@ async def prepare_data_for_signature(
258
270
  ],
259
271
  }
260
272
 
261
- # Build message
262
273
  message = PermitMessage(
263
274
  permitted=TokenPermissions(
264
275
  token=token_address,
@@ -306,16 +317,192 @@ async def sign_permit2(
306
317
  wallet_address,
307
318
  Quote(order_data=order_data),
308
319
  )
309
- # print(
310
- # "Signing the following full message:",
311
- # json.dumps(to_sign.model_dump(mode="json", by_alias=True)),
312
- # )
313
320
  signature = account.sign_typed_data(
314
321
  full_message=to_sign.model_dump(mode="json", by_alias=True)
315
322
  )
316
323
  return (to_sign, signature)
317
324
 
318
325
 
326
+ def sign_margin_order(
327
+ quote: Dict[str, Any],
328
+ private_key: str,
329
+ permit2_address: str = DEFAULT_PERMIT2_ADDRESS,
330
+ nonce: Optional[int] = None,
331
+ ) -> Dict[str, Any]:
332
+ """
333
+ Sign a margin order using EIP-712 typed data.
334
+
335
+ Args:
336
+ quote: Quote response from get_margin_quote
337
+ private_key: Private key for signing
338
+ permit2_address: Permit2 contract address
339
+ nonce: Optional nonce (random if not provided)
340
+
341
+ Returns:
342
+ Signed order payload ready for submission
343
+ """
344
+ order_data = quote.get("order_data")
345
+ if not isinstance(order_data, dict):
346
+ raise ValueError("quote missing order_data")
347
+
348
+ chain_id_str = quote.get("chain_id")
349
+ if isinstance(chain_id_str, dict) and "value" in chain_id_str:
350
+ chain_id = int(chain_id_str["value"])
351
+ else:
352
+ chain_id = int(chain_id_str)
353
+
354
+ domain_data = {
355
+ "name": "Permit2",
356
+ "chainId": chain_id,
357
+ "verifyingContract": to_checksum_address(permit2_address),
358
+ }
359
+
360
+ parameters = EIP712OrderParameters(
361
+ srcAsset=order_data["parameters"]["src_asset"],
362
+ dstAsset=order_data["parameters"]["dst_asset"],
363
+ srcQuantity=int(order_data["parameters"]["src_quantity"]),
364
+ dstQuantity=int(order_data["parameters"]["dst_quantity"]),
365
+ minQuantity=int(order_data["parameters"]["min_quantity"]),
366
+ darkSalt=int(order_data["parameters"]["dark_salt"]),
367
+ )
368
+
369
+ signed_order = EIP712SignedOrder(
370
+ sender=order_data["sender_wallet_address"],
371
+ parameters=parameters,
372
+ deadline=int(order_data["deadline"]),
373
+ target=order_data["target_wallet_address"],
374
+ filler=order_data["filler_wallet_address"],
375
+ orderType=str(order_data["order_type"]),
376
+ customData=[
377
+ bytes.fromhex(cd[2:]) if cd.startswith("0x") else bytes.fromhex(cd)
378
+ for cd in order_data.get("custom_data", [])
379
+ ],
380
+ )
381
+
382
+ token_permissions = EIP712TokenPermissions(
383
+ token=order_data["parameters"]["src_asset"],
384
+ amount=int(order_data["parameters"]["src_quantity"]),
385
+ )
386
+
387
+ if nonce is None:
388
+ nonce = get_random_nonce()
389
+
390
+ permit_witness = EIP712PermitWitnessTransferFromOrder(
391
+ permitted=token_permissions,
392
+ spender=order_data["router_address"],
393
+ nonce=nonce,
394
+ deadline=int(order_data["deadline"]),
395
+ witness=signed_order,
396
+ )
397
+
398
+ typed_data = {
399
+ "types": {
400
+ "EIP712Domain": [
401
+ {"name": "name", "type": "string"},
402
+ {"name": "chainId", "type": "uint256"},
403
+ {"name": "verifyingContract", "type": "address"},
404
+ ],
405
+ "PermitWitnessTransferFrom": permit_witness.TYPE_STRUCT,
406
+ "TokenPermissions": token_permissions.TYPE_STRUCT,
407
+ "SignedOrder": signed_order.TYPE_STRUCT,
408
+ "OrderParameters": parameters.TYPE_STRUCT,
409
+ },
410
+ "primaryType": "PermitWitnessTransferFrom",
411
+ "domain": domain_data,
412
+ "message": permit_witness.eip_signable_struct,
413
+ }
414
+
415
+ acct = Account.from_key(private_key)
416
+ signable = encode_typed_data(full_message=typed_data)
417
+ sig = acct.sign_message(signable).signature.hex()
418
+ if not sig.startswith("0x"):
419
+ sig = "0x" + sig
420
+
421
+ return {
422
+ "signature": sig,
423
+ "domain": domain_data,
424
+ "message": permit_witness.eip_signable_struct,
425
+ }
426
+
427
+
428
+ def sign_close_position(
429
+ chain_id: int,
430
+ position_id: int,
431
+ private_key: str,
432
+ escrow_contract: str,
433
+ authorized: str,
434
+ cash_settle: bool = False,
435
+ fraction_bps: int = 10_000,
436
+ deadline_seconds: int = 3600,
437
+ nonce: Optional[int] = None,
438
+ ) -> Dict[str, Any]:
439
+ """
440
+ Sign a close margin position request using EIP-712.
441
+
442
+ Args:
443
+ chain_id: Chain ID
444
+ position_id: Position ID (NFT token ID)
445
+ private_key: Private key for signing
446
+ escrow_contract: Escrow contract address
447
+ authorized: Authorized filler address
448
+ cash_settle: Whether to cash settle
449
+ fraction_bps: Fraction to close in basis points (10000 = 100%)
450
+ deadline_seconds: Deadline in seconds from now
451
+ nonce: Optional nonce (random if not provided)
452
+
453
+ Returns:
454
+ Signed close position payload
455
+ """
456
+ if not escrow_contract:
457
+ raise ValueError("escrow_contract is required")
458
+ if not authorized:
459
+ raise ValueError("authorized is required")
460
+
461
+ if nonce is None:
462
+ nonce = get_random_nonce()
463
+
464
+ deadline = int(time.time()) + int(deadline_seconds)
465
+
466
+ close_msg = EIP712CloseWithSwap(
467
+ positionId=int(position_id),
468
+ cashSettle=bool(cash_settle),
469
+ fractionBps=int(fraction_bps),
470
+ authorized=str(authorized),
471
+ nonce=int(nonce),
472
+ deadline=int(deadline),
473
+ )
474
+
475
+ domain_data = get_escrow_domain(chain_id, escrow_contract)
476
+ types = get_close_with_swap_types()
477
+
478
+ typed_data = {
479
+ "types": types,
480
+ "primaryType": "CloseWithSwap",
481
+ "domain": domain_data,
482
+ "message": {
483
+ "positionId": close_msg.positionId,
484
+ "cashSettle": close_msg.cashSettle,
485
+ "fractionBps": close_msg.fractionBps,
486
+ "authorized": close_msg.authorized,
487
+ "nonce": close_msg.nonce,
488
+ "deadline": close_msg.deadline,
489
+ },
490
+ }
491
+
492
+ acct = Account.from_key(private_key)
493
+ signable = encode_typed_data(full_message=typed_data)
494
+ sig = acct.sign_message(signable).signature.hex()
495
+ if not sig.startswith("0x"):
496
+ sig = "0x" + sig
497
+
498
+ return {
499
+ "signature": sig,
500
+ "domain": domain_data,
501
+ "message": typed_data["message"],
502
+ "chainId": str(chain_id),
503
+ }
504
+
505
+
319
506
  async def approve_permit2(
320
507
  w3: AsyncWeb3[P],
321
508
  account: LocalAccount,
@@ -325,29 +512,28 @@ async def approve_permit2(
325
512
  maxGas: int = 100000,
326
513
  ):
327
514
  wallet_address = account.address
515
+ permit2_address = get_permit2_addr(chain)
328
516
 
329
517
  erc20 = get_erc20_contract(w3, token_address)
330
- permit2_contract = get_permit2(chain)
331
518
  current_allowance = await erc20.functions.allowance(
332
- wallet_address, permit2_contract
519
+ wallet_address, permit2_address
333
520
  ).call()
334
521
  if current_allowance < required_quantity:
335
522
  logger.info(
336
523
  f"Approving {token_address}: allowance={current_allowance}, required={required_quantity}"
337
524
  )
338
- approve_fn = erc20.functions.approve(permit2_contract, 2**256 - 1)
525
+ approve_fn = erc20.functions.approve(permit2_address, 2**256 - 1)
339
526
  tx = await approve_fn.build_transaction(
340
527
  {
341
528
  "from": wallet_address,
342
529
  "nonce": await w3.eth.get_transaction_count(
343
530
  w3.to_checksum_address(wallet_address)
344
531
  ),
345
- "gas": maxGas, # Adjust as needed
532
+ "gas": maxGas,
346
533
  "gasPrice": await w3.eth.gas_price,
347
534
  }
348
535
  )
349
536
 
350
- # Sign and send transaction
351
537
  tx_data = tx if isinstance(tx, dict) else tx.__dict__
352
538
  signed_tx: SignedTransaction = account.sign_transaction(tx_data)
353
539
  tx_hash = await w3.eth.send_raw_transaction(signed_tx.raw_transaction)
@@ -355,11 +541,13 @@ async def approve_permit2(
355
541
  logger.debug(f"→ Approval tx hash: {tx_hash.hex()}")
356
542
  return tx_hash.hex()
357
543
 
544
+
358
545
  @dataclass
359
546
  class Permit2Order:
360
547
  msg: SignatureData
361
548
  sig: SignedMessage
362
549
 
550
+
363
551
  async def create_permit2_order(
364
552
  w3: AsyncWeb3[P],
365
553
  account: LocalAccount,
@@ -381,8 +569,19 @@ async def create_permit2_order(
381
569
  dst_token,
382
570
  raw_amount,
383
571
  )
384
- if q['orderType'] == 'FEATHER':
385
- raise Exception('Feather routes unsupported for automatic trades')
572
+
573
+ order_type = q.get("orderType") or q.get("order_type")
574
+ if not order_type:
575
+ order_data = q.get("order_data")
576
+ if isinstance(order_data, dict):
577
+ order_type = order_data.get("order_type") or order_data.get("orderType")
578
+
579
+ if not order_type:
580
+ raise ValueError(f"quote missing order type (keys={sorted(q.keys())})")
581
+
582
+ if str(order_type).strip().upper() == "FEATHER":
583
+ raise Exception("Feather routes unsupported for automatic trades")
584
+
386
585
  _ = await approve_permit2(w3, account, src_chain, src_token, raw_amount)
387
586
  msg, sig = await sign_permit2(w3.eth, account, account.address, raw_amount, q)
388
587
  return Permit2Order(msg, sig)
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: tristero
3
+ Version: 0.3.0
4
+ Summary: Library for trading on Tristero
5
+ Author-email: pty1 <pty11@proton.me>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: certifi>=2023.7.22
10
+ Requires-Dist: eth-account>=0.8.0
11
+ Requires-Dist: glom>=25.12.0
12
+ Requires-Dist: httpx>=0.23.0
13
+ Requires-Dist: pydantic>=2.0.0
14
+ Requires-Dist: tenacity>=8.0.0
15
+ Requires-Dist: web3>=6.0.0
16
+ Requires-Dist: websockets>=10.0
17
+ Dynamic: license-file
18
+
19
+ # Tristero
20
+ [![PyPI version](https://badge.fury.io/py/tristero.svg)](https://badge.fury.io/py/tristero)
21
+ [![Python Support](https://img.shields.io/pypi/pyversions/tristero.svg)](https://pypi.org/project/tristero/)
22
+
23
+ This repository is home to Tristero's trading library.
24
+
25
+ ### Installation
26
+ ```
27
+ pip install tristero
28
+ ```
29
+
30
+ ### Quick Start
31
+
32
+ Execute a cross-chain swap in just a few lines:
33
+
34
+ ```py
35
+ import os
36
+ import asyncio
37
+
38
+ from eth_account import Account
39
+
40
+ from tristero import ChainID, TokenSpec, execute_permit2_swap, make_async_w3
41
+
42
+
43
+ async def main() -> None:
44
+ private_key = os.getenv("TEST_ACCOUNT_PRIVKEY")
45
+ if not private_key:
46
+ raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")
47
+
48
+ account = Account.from_key(private_key)
49
+
50
+ arbitrum_rpc = os.getenv("ARBITRUM_RPC_URL", "https://arbitrum-one-rpc.publicnode.com")
51
+ w3 = make_async_w3(arbitrum_rpc)
52
+
53
+ # Example: USDC on Arbitrum -> USDT on Base
54
+ result = await execute_permit2_swap(
55
+ w3=w3,
56
+ account=account,
57
+ src_t=TokenSpec(chain_id=ChainID(42161), token_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), # USDC (Arbitrum)
58
+ dst_t=TokenSpec(chain_id=ChainID(8453), token_address="0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2"), # USDT (Base)
59
+ raw_amount=1_000_000, # 1 USDC (6 decimals)
60
+ timeout=300,
61
+ )
62
+ print(result)
63
+
64
+
65
+ asyncio.run(main())
66
+ ```
67
+
68
+ ### Usage Examples
69
+
70
+ #### Quote (read-only)
71
+
72
+ ```py
73
+ import asyncio
74
+ import os
75
+
76
+ from eth_account import Account
77
+
78
+ from tristero import ChainID, get_quote
79
+
80
+
81
+ async def main() -> None:
82
+ private_key = os.getenv("TEST_ACCOUNT_PRIVKEY")
83
+ if not private_key:
84
+ raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")
85
+
86
+ wallet = Account.from_key(private_key).address
87
+
88
+ quote = await get_quote(
89
+ from_wallet=wallet,
90
+ to_wallet=wallet,
91
+ from_chain_id=str(ChainID(42161).value),
92
+ from_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC (Arbitrum)
93
+ to_chain_id=str(ChainID(42161).value),
94
+ to_address="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH (Arbitrum)
95
+ amount=1_000_000,
96
+ )
97
+ print(quote)
98
+
99
+
100
+ asyncio.run(main())
101
+ ```
102
+
103
+ #### Margin: Direct Open
104
+
105
+ ```py
106
+ import asyncio
107
+ import os
108
+
109
+ from eth_account import Account
110
+ from tristero import open_margin_position
111
+
112
+
113
+ async def main() -> None:
114
+ private_key = os.getenv("TEST_ACCOUNT_PRIVKEY", "")
115
+ if not private_key:
116
+ raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")
117
+
118
+ wallet = Account.from_key(private_key).address
119
+
120
+ chain_id = "42161"
121
+ quote_currency = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" # USDC (Arbitrum)
122
+ base_currency = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" # WETH (Arbitrum)
123
+ leverage = 2
124
+ collateral = "1000000" # 1 USDC (6 decimals)
125
+
126
+ result = await open_margin_position(
127
+ private_key=private_key,
128
+ chain_id=chain_id,
129
+ wallet_address=wallet,
130
+ quote_currency=quote_currency,
131
+ base_currency=base_currency,
132
+ leverage_ratio=leverage,
133
+ collateral_amount=collateral,
134
+ wait_for_result=True,
135
+ timeout=120,
136
+ )
137
+ print(result)
138
+
139
+
140
+ asyncio.run(main())
141
+ ```
142
+
143
+ #### Margin: List Positions / Close Position
144
+
145
+ ```py
146
+ import asyncio
147
+ import os
148
+
149
+ from eth_account import Account
150
+ from tristero import close_margin_position, list_margin_positions
151
+
152
+
153
+ async def main() -> None:
154
+ private_key = os.getenv("TEST_ACCOUNT_PRIVKEY", "")
155
+ if not private_key:
156
+ raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")
157
+
158
+ wallet = Account.from_key(private_key).address
159
+ chain_id = "42161"
160
+
161
+ positions = await list_margin_positions(wallet)
162
+ open_pos = next((p for p in positions if p.status == "open"), None)
163
+ if not open_pos:
164
+ raise RuntimeError("no open positions")
165
+
166
+ result = await close_margin_position(
167
+ private_key=private_key,
168
+ chain_id=chain_id,
169
+ position_id=open_pos.taker_token_id,
170
+ escrow_contract=open_pos.escrow_address,
171
+ authorized=open_pos.filler_address,
172
+ cash_settle=False,
173
+ fraction_bps=10_000,
174
+ deadline_seconds=3600,
175
+ wait_for_result=True,
176
+ timeout=120,
177
+ )
178
+ print(result)
179
+
180
+
181
+ asyncio.run(main())
182
+ ```
183
+
184
+ ### How it works
185
+
186
+ Tristero supports two primary swap mechanisms:
187
+
188
+ #### Permit2 Swaps (EVM-to-EVM)
189
+ - **Quote & Approve** - Request a quote and approve tokens via Permit2 (gasless approval)
190
+ - **Sign & Submit** - Sign an EIP-712 order and submit for execution
191
+ - **Monitor** - Track swap progress via WebSocket updates
192
+
193
+ #### Feather Swaps (UTXO-based)
194
+ - **Quote & Deposit** - Request a quote to receive a deposit address
195
+ - **Manual Transfer** - Send funds to the provided deposit address
196
+ - **Monitor** - Track swap completion via WebSocket updates
197
+
198
+ This library provides both high-level convenience functions and lower-level components for precise control.
@@ -0,0 +1,22 @@
1
+ tristero/__init__.py,sha256=lKstpKnnzd2PnvI79SwWnOwOMkDla9Nt_HbyrhnV6s0,1854
2
+ tristero/api.py,sha256=DLNYsFj81TId3EfCwlXRfxUws-7lLTkMO1znF9D3abU,7900
3
+ tristero/client.py,sha256=WMPthsJ5Rb6tOkBiHe4533r3Kqcdk_6bvlOPOJEB9HA,19125
4
+ tristero/config.py,sha256=v7ohAv-KbnFpWQbyXYD78QABFTW1vm1P4GkIV2-5uC0,1002
5
+ tristero/data.py,sha256=8Y1865c5tLVHDCwDRjyMlp7xR4KXAej_4yK8WcZCTcU,1512
6
+ tristero/permit2.py,sha256=lYawwcechaxQuEe1QUMd4tR0FBnuNri1Gs0tXdTK_eY,17604
7
+ tristero/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ tristero/eip712/__init__.py,sha256=xTsAPEiwYidBaya3py_wMVFH4mOd4Md7hH5eTGFwvSI,1256
9
+ tristero/eip712/eip712_auto.py,sha256=h-AwA8BsIAvZoe3TB8fzlvNKX17ZVG_NwbroqZ_LWl4,7293
10
+ tristero/eip712/eip712_struct.py,sha256=aWBOdsGarQnxONA5hr-DVdlrUD-yvtu_yi3G9DrU9cQ,2286
11
+ tristero/eip712/escrow_utils.py,sha256=EOXpzxJ_29A0yvEloI5sxYe33atMhkoIAV4pkAia92s,869
12
+ tristero/eip712/nested_types.py,sha256=rsB-pZ6K8EeulU__6z0pY_6LHWkjh7N6776ekl9Jcnw,1972
13
+ tristero/eip712/simple_types.py,sha256=9rHqz7XbbdwdOMCKbQr8HGYwPgqC3S6sQbm2I7DbQVM,11865
14
+ tristero/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ tristero/files/chains.json,sha256=04jFywe_V0wrS0tcjdCUHMxIutaLJ_k5ABbbmkyZ9jY,256150
16
+ tristero/files/erc20_abi.json,sha256=jvsJ6aCwhMcmo3Yy1ajt5lPl_nTRg7tv-tGj87xzTOg,12800
17
+ tristero/files/permit2_abi.json,sha256=NV0AUUA9kqFPk56njvRRzUyjBhrBncKIMd3PrSH0LCc,17817
18
+ tristero-0.3.0.dist-info/licenses/LICENSE,sha256=b-9ikwk9ICk964mtUbqVkDgSf3S6d52HTQxyGHmNo9M,10925
19
+ tristero-0.3.0.dist-info/METADATA,sha256=Z4XmDOqGpCmt21zhOto5VB1FL8Lil8HGDf2GbJA-qQc,5318
20
+ tristero-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
21
+ tristero-0.3.0.dist-info/top_level.txt,sha256=xO745nTllCKw6Fdu-a2ZYv5-ZVOKl2vt9ccRUjCVXfI,9
22
+ tristero-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+