Metadata-Version: 2.4
Name: tristero
Version: 0.3.0
Summary: Library for trading on Tristero
Author-email: pty1 <pty11@proton.me>
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: certifi>=2023.7.22
Requires-Dist: eth-account>=0.8.0
Requires-Dist: glom>=25.12.0
Requires-Dist: httpx>=0.23.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: tenacity>=8.0.0
Requires-Dist: web3>=6.0.0
Requires-Dist: websockets>=10.0
Dynamic: license-file

# Tristero
[![PyPI version](https://badge.fury.io/py/tristero.svg)](https://badge.fury.io/py/tristero)
[![Python Support](https://img.shields.io/pypi/pyversions/tristero.svg)](https://pypi.org/project/tristero/)

This repository is home to Tristero's trading library.

### Installation
```
pip install tristero
```

### Quick Start

Execute a cross-chain swap in just a few lines:

```py
import os
import asyncio

from eth_account import Account

from tristero import ChainID, TokenSpec, execute_permit2_swap, make_async_w3


async def main() -> None:
    private_key = os.getenv("TEST_ACCOUNT_PRIVKEY")
    if not private_key:
        raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")

    account = Account.from_key(private_key)

    arbitrum_rpc = os.getenv("ARBITRUM_RPC_URL", "https://arbitrum-one-rpc.publicnode.com")
    w3 = make_async_w3(arbitrum_rpc)

    # Example: USDC on Arbitrum -> USDT on Base
    result = await execute_permit2_swap(
        w3=w3,
        account=account,
        src_t=TokenSpec(chain_id=ChainID(42161), token_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831"),  # USDC (Arbitrum)
        dst_t=TokenSpec(chain_id=ChainID(8453), token_address="0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2"),  # USDT (Base)
        raw_amount=1_000_000,  # 1 USDC (6 decimals)
        timeout=300,
    )
    print(result)


asyncio.run(main())
```

### Usage Examples

#### Quote (read-only)

```py
import asyncio
import os

from eth_account import Account

from tristero import ChainID, get_quote


async def main() -> None:
    private_key = os.getenv("TEST_ACCOUNT_PRIVKEY")
    if not private_key:
        raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")

    wallet = Account.from_key(private_key).address

    quote = await get_quote(
        from_wallet=wallet,
        to_wallet=wallet,
        from_chain_id=str(ChainID(42161).value),
        from_address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831",  # USDC (Arbitrum)
        to_chain_id=str(ChainID(42161).value),
        to_address="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",  # WETH (Arbitrum)
        amount=1_000_000,
    )
    print(quote)


asyncio.run(main())
```

#### Margin: Direct Open

```py
import asyncio
import os

from eth_account import Account
from tristero import open_margin_position


async def main() -> None:
    private_key = os.getenv("TEST_ACCOUNT_PRIVKEY", "")
    if not private_key:
        raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")

    wallet = Account.from_key(private_key).address

    chain_id = "42161"
    quote_currency = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"  # USDC (Arbitrum)
    base_currency = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"  # WETH (Arbitrum)
    leverage = 2
    collateral = "1000000"  # 1 USDC (6 decimals)

    result = await open_margin_position(
        private_key=private_key,
        chain_id=chain_id,
        wallet_address=wallet,
        quote_currency=quote_currency,
        base_currency=base_currency,
        leverage_ratio=leverage,
        collateral_amount=collateral,
        wait_for_result=True,
        timeout=120,
    )
    print(result)


asyncio.run(main())
```

#### Margin: List Positions / Close Position

```py
import asyncio
import os

from eth_account import Account
from tristero import close_margin_position, list_margin_positions


async def main() -> None:
    private_key = os.getenv("TEST_ACCOUNT_PRIVKEY", "")
    if not private_key:
        raise RuntimeError("Set TEST_ACCOUNT_PRIVKEY")

    wallet = Account.from_key(private_key).address
    chain_id = "42161"

    positions = await list_margin_positions(wallet)
    open_pos = next((p for p in positions if p.status == "open"), None)
    if not open_pos:
        raise RuntimeError("no open positions")

    result = await close_margin_position(
        private_key=private_key,
        chain_id=chain_id,
        position_id=open_pos.taker_token_id,
        escrow_contract=open_pos.escrow_address,
        authorized=open_pos.filler_address,
        cash_settle=False,
        fraction_bps=10_000,
        deadline_seconds=3600,
        wait_for_result=True,
        timeout=120,
    )
    print(result)


asyncio.run(main())
```

### How it works

Tristero supports two primary swap mechanisms:

#### Permit2 Swaps (EVM-to-EVM)
- **Quote & Approve** - Request a quote and approve tokens via Permit2 (gasless approval)
- **Sign & Submit** - Sign an EIP-712 order and submit for execution
- **Monitor** - Track swap progress via WebSocket updates

#### Feather Swaps (UTXO-based)
- **Quote & Deposit** - Request a quote to receive a deposit address
- **Manual Transfer** - Send funds to the provided deposit address
- **Monitor** - Track swap completion via WebSocket updates

This library provides both high-level convenience functions and lower-level components for precise control.
