Skip to main content

Library for trading on Tristero

Project description

Tristero

PyPI version Python Support

This repository is home to Tristero's trading library.

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.

Installation

pip install tristero

Environment Configuration

Tristero supports three environments: PRODUCTION (default), STAGING, and LOCAL.

Set the environment globally at startup:

from tristero import set_config

set_config("STAGING")  # all subsequent calls use the staging API

Or override per call:

quote = await get_swap_quote(..., env="LOCAL")

Every user-facing function in the SDK accepts an optional env keyword argument.

Quick Start

Spot Swap (quote, sign, submit)

import asyncio
import json
import os

from eth_account import Account

from tristero import get_swap_quote, sign_and_submit, make_async_w3


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
    w3 = make_async_w3(os.getenv("ARB_RPC_URL", "https://arbitrum-one-rpc.publicnode.com"))

    # 1. Get a quote (USDC -> WETH on Arbitrum)
    quote = await get_swap_quote(
        wallet=wallet,
        src_chain=42161,
        src_token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831",  # USDC
        dst_chain=42161,
        dst_token="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",  # WETH
        amount=1_000_000,  # 1 USDC (6 decimals)
    )
    print(json.dumps(quote, indent=2))

    # 2. Sign and submit (w3 required for Permit2 approval on source chain)
    result = await sign_and_submit(quote, private_key, w3=w3, wait=True, timeout=300)
    print(result)


asyncio.run(main())

Margin Position (quote, sign, submit)

import asyncio
import json
import os

from eth_account import Account

from tristero import get_swap_quote, sign_and_submit


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

    # 1. Get a margin quote (2x leveraged USDC/WETH on Arbitrum)
    quote = await get_swap_quote(
        wallet=wallet,
        src_chain=42161,
        src_token="0xaf88d065e77c8cC2239327C5EDb3A432268e5831",  # USDC (collateral)
        dst_chain=42161,
        dst_token="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",  # WETH (base)
        amount=1_000_000,  # 1 USDC collateral (6 decimals)
        leverage=2,
    )
    print(json.dumps(quote, indent=2))

    # 2. Sign and submit (no w3 needed for margin)
    result = await sign_and_submit(quote, private_key, wait=True, timeout=120)
    print(result)


asyncio.run(main())

More Examples

Spot Swap (direct execution)

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("ARB_RPC_URL", "https://arbitrum-one-rpc.publicnode.com")
    w3 = make_async_w3(arbitrum_rpc)

    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())

Margin: Direct Open

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

    result = await open_margin_position(
        private_key=private_key,
        chain_id="42161",
        wallet_address=wallet,
        quote_currency="0xaf88d065e77c8cC2239327C5EDb3A432268e5831",  # USDC
        base_currency="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",  # WETH
        leverage_ratio=2,
        collateral_amount="1000000",  # 1 USDC (6 decimals)
        wait_for_result=True,
        timeout=120,
    )
    print(result)


asyncio.run(main())

Margin: List Positions / Close Position

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

    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="42161",
        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())

Feather: Start (get deposit address)

Feather swaps are deposit-based: you start an order to receive a deposit_address, send funds to it manually, then optionally wait for completion.

Submit only:

import asyncio

from tristero import ChainID, TokenSpec, start_feather_swap


async def main() -> None:
    # Example: ETH (native) -> XMR (native)
    src_t = TokenSpec(chain_id=ChainID.ethereum, token_address="native")
    dst_t = TokenSpec(chain_id=ChainID.monero, token_address="native")

    # Replace with your own destination address on the destination chain.
    dst_addr = "YOUR_XMR_ADDRESS"

    swap = await start_feather_swap(
        src_t=src_t,
        dst_t=dst_t,
        dst_addr=dst_addr,
        raw_amount=100_000_000_000_000_000,  # 0.1 ETH in wei
    )

    order_id = (
        (swap.data or {}).get("id")
        or (swap.data or {}).get("order_id")
        or (swap.data or {}).get("orderId")
        or ""
    )

    print("order_id:", order_id)
    print("deposit_address:", swap.deposit_address)


asyncio.run(main())

Submit + wait (WebSocket):

import asyncio

from tristero import ChainID, OrderType, TokenSpec, start_feather_swap, wait_for_completion


async def main() -> None:
    src_t = TokenSpec(chain_id=ChainID.ethereum, token_address="native")
    dst_t = TokenSpec(chain_id=ChainID.monero, token_address="native")
    dst_addr = "YOUR_XMR_ADDRESS"

    swap = await start_feather_swap(
        src_t=src_t,
        dst_t=dst_t,
        dst_addr=dst_addr,
        raw_amount=100_000_000_000_000_000,
    )

    order_id = (
        (swap.data or {}).get("id")
        or (swap.data or {}).get("order_id")
        or (swap.data or {}).get("orderId")
        or ""
    )
    if not order_id:
        raise RuntimeError(f"Feather swap response missing order id: {swap.data}")

    print("deposit_address:", swap.deposit_address)
    print("Waiting for completion...")
    completion = await wait_for_completion(order_id, order_type=OrderType.FEATHER)
    print(completion)


asyncio.run(main())

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tristero-0.3.5.tar.gz (50.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tristero-0.3.5-py3-none-any.whl (53.0 kB view details)

Uploaded Python 3

File details

Details for the file tristero-0.3.5.tar.gz.

File metadata

  • Download URL: tristero-0.3.5.tar.gz
  • Upload date:
  • Size: 50.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tristero-0.3.5.tar.gz
Algorithm Hash digest
SHA256 d60669fa89528fb727caefd0bc9b99adb151f402c35738dc87d718a4ec2a5dfe
MD5 be4b6aef75059974bf4c9f2ae598fd0e
BLAKE2b-256 1143c4b26a8a37552d895a359107e05bf96643ab00655eae4f5ab2e52dc6d035

See more details on using hashes here.

File details

Details for the file tristero-0.3.5-py3-none-any.whl.

File metadata

  • Download URL: tristero-0.3.5-py3-none-any.whl
  • Upload date:
  • Size: 53.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tristero-0.3.5-py3-none-any.whl
Algorithm Hash digest
SHA256 8560d7c0ea785a5ea458b392e0af508c965d090b05123c171413e6856eea2cb7
MD5 770c1db07bc398a9fac76036af165c26
BLAKE2b-256 495384128f42851efc13dcf68de238539dbb91d060cd09b87a5a46464c6461be

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page