> For the complete documentation index, see [llms.txt](https://docs.ston.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ston.fi/es/seccion-para-desarrolladores/quickstart/python.md).

# Guía de Omniston (Python)

Esta guía te llevará a través de la creación de un **basado en terminal** cliente de intercambio de tokens usando el **Omniston** protocolo para intercambiar activos entre distintos DEXes (STON.fi V1, STON.fi V2, DeDust, etc.). En lugar de una interfaz web y TonConnect, usaremos un **monedero local de TON** (creado con `tonsdk`) y enviaremos transacciones a través de **Toncenter**. La guía es apta para principiantes y asume una experiencia mínima con TON.

> **Nota**: Este inicio rápido usa intencionalmente una **CLI de un solo archivo** para mayor claridad. Más adelante puedes modularizarla o empaquetarla (ver **Aplicación de ejemplo avanzada**).
>
> **Nota**: Para mayor fiabilidad al transmitir transacciones, establece una **TONCENTER\_API\_KEY** (ver **Configurar activos y red**).

***

## Índice

1. [Introducción](#id-1.-introduction)
2. [Configuración del proyecto](#id-2.-setting-up-the-project)
3. [Configuración del monedero](#id-3.-wallet-setup)
4. [Configurar activos y red](#id-4.-configure-assets--network)
5. [Implementación de la CLI (Paso a paso)](#id-5.-implementing-the-cli-stepbystep)
6. [Solicitar una cotización](#id-6.-requesting-a-quote)
7. [Construcción de una transacción y envío](#id-7.-building-a-transaction--sending-it)
8. [Probar tu intercambio](#id-8.-testing-your-swap)
9. [Conclusión](#id-9.-conclusion)
10. [Demo en vivo](#id-10.-live-demo)
11. [Uso de agentes de IA para la implementación automatizada](#id-11.-using-ai-agents-for-automated-implementation)

***

## 1. Introducción

En este inicio rápido, construirás una mínima **CLI en Python** que puede:

* Crear o reutilizar un monedero local de TON (a través de **`tonsdk`**).
* Cargar la configuración de red y tokens desde **`.env`** y **`swap_config.json`**.
* Solicitar una **RFQ** (cotización) de Omniston a través de **WebSockets**.
* Construir una transferencia usando el constructor de transacciones de Omniston.
* Enviar la transacción a **Toncenter** para ejecutar el intercambio.

Usarás:

* **`tonsdk`** – generación de monedero, firma y BOC.
* **`websockets`** – para comunicarte con Omniston.
* **`python-dotenv`** – para cargar variables de entorno.
* **API de Toncenter** – para transmitir la transacción firmada.

> **Nota de alcance**: Este inicio rápido es solo para TON. Muestra activos de TON y direcciones de billeteras TON, y no cubre la `chain_id` selección entre cadenas ni la coincidencia de cadenas. Para ver las cadenas compatibles y las reglas de coincidencia de cadenas, consulta [Omniston API v1beta8](/es/seccion-para-desarrolladores/omniston/v1beta8.md) y [Cómo funciona Omniston](/es/seccion-para-desarrolladores/omniston/how-omniston-works.md).

***

## 2. Configuración del proyecto

### 2.1 Crea el espacio de trabajo

```bash
mkdir omniston-python
cd omniston-python
```

### 2.2 Crea el entorno virtual

```bash
python3 -m venv .venv
source .venv/bin/activate        # macOS/Linux
# .venv\\Scripts\\Activate.ps1  # Windows PowerShell
```

### 2.3 Instala las dependencias

1. Crea `requirements.txt` y añade:

   ```
   python-dotenv>=1.0,<2
   tonsdk>=1.0.13
   websockets>=11,<13
   ```
2. Instala los paquetes:

   ```bash
   pip install -r requirements.txt
   ```
3. Crea un script CLI de un solo archivo:

   ```bash
   touch omniston_cli.py
   ```

***

## 3. Configuración del monedero

La CLI persiste un monedero en `data/wallet.json` e imprime una mnemónica una sola vez; guárdala de forma segura.

### 3.1 Genera o carga un monedero

En este paso pegarás las definiciones principales y los ayudantes del monedero (mantenidos en la parte superior de `omniston_cli.py`).

**Consejo**: Los bloques de código a continuación provienen textualmente de la implementación funcional; pégalos tal cual y en el orden indicado.

### 3.2 Financia e implementa el monedero

Debes financiar la dirección e implementar el contrato del monedero antes de enviar un intercambio. Los ayudantes siguientes se encargan de consultar Toncenter y enviar el BOC de inicialización cuando sea necesario.

***

## 4. Configurar activos y red

Definirás localmente los endpoints RPC y la pareja de intercambio predeterminada.

### 4.1 Crea el archivo .env

Crea `.env` en la raíz del proyecto:

```env
TONCENTER_API_URL=https://toncenter.com/api/v2
TONCENTER_API_KEY=pon-aquí-tu-clave-api
OMNISTON_WS_URL=wss://omni-ws.ston.fi
```

> **Obtener una clave API de Toncenter**: Usar la API sin una clave API está limitado a 1 solicitud por segundo. Para obtener límites de tasa más altos:
>
> 1. Contacta a [@toncenter](https://t.me/toncenter) en Telegram
> 2. Solicita una clave API para tu proyecto
> 3. Copia la clave y pégala en tu `.env` archivo
>
> `TONCENTER_API_KEY` es necesario si quieres ejecutar transacciones. Sin él, tendrás problemas de limitación de tasa y el envío de transacciones fallará.

### 4.2 Define el swap\_config.json

Crea `swap_config.json`:

```json
{
  "from_token_address": "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c",
  "from_token_decimals": 9,
  "to_token_address": "EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO",
  "to_token_decimals": 9,
  "amount": "0.01",
  "max_slippage_bps": 500,
  "max_outgoing_messages": 4,
  "gasless_mode": "GASLESS_SETTLEMENT_POSSIBLE",
  "flexible_referrer_fee": false
}
```

Direcciones de tokens comunes:

* TON nativo: `EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c`
* USDT: `EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs`
* STON: `EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO`

`gasless_mode` acepta "GASLESS\_SETTLEMENT\_UNSPECIFIED", "GASLESS\_SETTLEMENT\_POSSIBLE", "GASLESS\_SETTLEMENT\_REQUIRED" o sus equivalentes numéricos 0/1/2.

`flexible_referrer_fee` permite que Omniston reduzca la comisión real del referidor por debajo de `referrer_fee_bps` cuando un resolver puede ofrecer un mejor precio. Déjalo en `false` para aplicar la comisión exacta.

***

## 5. Implementación de la CLI (Paso a paso)

Abre `omniston_cli.py` y pega los bloques de ayudantes restantes abajo exactamente como se muestran.

### 5.1 Define los tipos de dominio

```python
# omniston_cli.py
import asyncio
import base64
import json
import os
import sys
import time
import urllib.parse
import urllib.request
import uuid
from dataclasses import dataclass
from decimal import Decimal, ROUND_DOWN, getcontext
from pathlib import Path
from typing import Dict, List, Optional, Tuple

import websockets
from dotenv import load_dotenv
from tonsdk.boc import Cell
from tonsdk.contract.wallet import Wallets, WalletVersionEnum
from tonsdk.crypto import mnemonic_new
from tonsdk.utils import bytes_to_b64str


DATA_DIR = Path("data")
WALLET_FILE = DATA_DIR / "wallet.json"
CONFIG_FILE = Path("swap_config.json")


@dataclass
class WalletData:
    mnemonic: List[str]
    address_hex: str
    address_bounceable: str
    workchain: int
    version: str

    def to_dict(self) -> Dict[str, object]:
        return {
            "mnemonic": self.mnemonic,
            "address_hex": self.address_hex,
            "address_bounceable": self.address_bounceable,
            "workchain": self.workchain,
            "version": self.version,
        }

    @classmethod
    def from_dict(cls, payload: Dict[str, object]) -> "WalletData":
        return cls(
            mnemonic=list(payload["mnemonic"]),
            address_hex=str(payload["address_hex"]),
            address_bounceable=str(payload.get("address_bounceable", "")),
            workchain=int(payload.get("workchain", 0)),
            version=str(payload.get("version", WalletVersionEnum.v4r2.value)),
        )


@dataclass
class SwapConfig:
    from_token_address: str
    from_token_decimals: int
    to_token_address: str
    to_token_decimals: int
    amount: Decimal
    max_slippage_bps: int
    max_outgoing_messages: int
    gasless_mode: int
    flexible_referrer_fee: bool


GASLESS_SETTLEMENT_MAP = {
    "GASLESS_SETTLEMENT_UNSPECIFIED": 0,
    "GASLESS_SETTLEMENT_POSSIBLE": 1,
    "GASLESS_SETTLEMENT_REQUIRED": 2,
}


def _parse_gasless_mode(raw: object) -> int:
    if isinstance(raw, int):
        return raw
    if isinstance(raw, str):
        token = raw.strip()
        if not token:
            return 1
        upper = token.upper()
        if upper in GASLESS_SETTLEMENT_MAP:
            return GASLESS_SETTLEMENT_MAP[upper]
        return int(token)
    raise TypeError("gasless_mode must be str or int")


def load_config() -> SwapConfig:
    if not CONFIG_FILE.exists():
        raise FileNotFoundError("falta swap_config.json")
    with CONFIG_FILE.open("r", encoding="utf-8") as handle:
        payload = json.load(handle)
    gasless_mode = _parse_gasless_mode(payload.get("gasless_mode", "GASLESS_SETTLEMENT_POSSIBLE"))
    return SwapConfig(
        from_token_address=str(payload["from_token_address"]),
        from_token_decimals=int(payload["from_token_decimals"]),
        to_token_address=str(payload["to_token_address"]),
        to_token_decimals=int(payload["to_token_decimals"]),
        amount=Decimal(str(payload["amount"])),
        max_slippage_bps=int(payload.get("max_slippage_bps", 500)),
        max_outgoing_messages=int(payload.get("max_outgoing_messages", 4)),
        gasless_mode=gasless_mode,
        flexible_referrer_fee=bool(payload.get("flexible_referrer_fee", False)),
    )


def ensure_data_dir() -> None:
    DATA_DIR.mkdir(parents=True, exist_ok=True)


def prompt_yes_no(message: str, default: bool = False) -> bool:
    suffix = " [Y/n]" if default else " [y/N]"
    while True:
        reply = input(f"{message}{suffix} ").strip().lower()
        if not reply:
            return default
        if reply in {"y", "yes"}:
            return True
        if reply in {"n", "no"}:
            return False


getcontext().prec = 28
# Código de blockchain solo TON usado en este inicio rápido
BLOCKCHAIN_ID = 607  # Equivalente a Blockchain.TON en estos ejemplos, no al mapa chain_id entre cadenas
QUOTE_TIMEOUT = 15
TRANSFER_TIMEOUT = 30


TONCENTER_API_URL = "https://toncenter.com/api/v2"
TONCENTER_API_KEY = ""
OMNISTON_WS_URL = "wss://omni-ws.ston.fi"


def load_env() -> None:
    global TONCENTER_API_URL, TONCENTER_API_KEY, OMNISTON_WS_URL
    load_dotenv()
    TONCENTER_API_URL = os.getenv("TONCENTER_API_URL", TONCENTER_API_URL)
    TONCENTER_API_KEY = os.getenv("TONCENTER_API_KEY", TONCENTER_API_KEY)
    OMNISTON_WS_URL = os.getenv("OMNISTON_WS_URL", OMNISTON_WS_URL)
```

### 5.2 Ayudantes del monedero

```python
def save_wallet(wallet: WalletData) -> None:
    ensure_data_dir()
    with WALLET_FILE.open("w", encoding="utf-8") as handle:
        json.dump(wallet.to_dict(), handle, indent=2)


def load_wallet() -> Optional[WalletData]:
    if not WALLET_FILE.exists():
        return None
    with WALLET_FILE.open("r", encoding="utf-8") as handle:
        payload = json.load(handle)
    return WalletData.from_dict(payload)


def ensure_wallet() -> WalletData:
    ensure_data_dir()
    wallet = load_wallet()
    if wallet:
        print(f"Monedero cargado desde {WALLET_FILE}")
        return wallet

    mnemonic = mnemonic_new()
    version = WalletVersionEnum.v4r2
    _, _, _, contract = Wallets.from_mnemonics(mnemonic, version, 0)
    wallet = WalletData(
        mnemonic=mnemonic,
        address_hex=contract.address.to_string(False),
        address_bounceable=contract.address.to_string(True, True, False),
        workchain=contract.address.wc,
        version=version.value,
    )
    save_wallet(wallet)
    print(f"Nuevo monedero creado en {WALLET_FILE}")
    print("Mnemónica (guárdala de forma segura):")
    print(" ".join(wallet.mnemonic))
    return wallet


def build_init_boc(wallet: WalletData) -> str:
    version = WalletVersionEnum(wallet.version)
    _, _, _, contract = Wallets.from_mnemonics(wallet.mnemonic, version, wallet.workchain)
    message = contract.create_init_external_message()["message"]
    return bytes_to_b64str(message.to_boc(False))
```

### 5.3 Ayudantes de Toncenter

```python
def toncenter_get(endpoint: str, params: Dict[str, object]) -> Dict[str, object]:
    query = dict(params)
    if TONCENTER_API_KEY:
        query.setdefault("api_key", TONCENTER_API_KEY)
    url = f"{TONCENTER_API_URL.rstrip('/')}/{endpoint}"
    if query:
        url = f"{url}?{urllib.parse.urlencode(query)}"
    request = urllib.request.Request(url, headers={"Accept": "application/json"})
    with urllib.request.urlopen(request, timeout=15) as response:
        return json.loads(response.read().decode("utf-8"))


def toncenter_post(endpoint: str, body: Dict[str, object]) -> Dict[str, object]:
    query = {}
    if TONCENTER_API_KEY:
        query["api_key"] = TONCENTER_API_KEY
    url = f"{TONCENTER_API_URL.rstrip('/')}/{endpoint}"
    if query:
        url = f"{url}?{urllib.parse.urlencode(query)}"
    data = json.dumps(body).encode("utf-8")
    request = urllib.request.Request(
        url,
        data=data,
        headers={"Accept": "application/json", "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=15) as response:
        return json.loads(response.read().decode("utf-8"))


def send_boc(boc_base64: str) -> bool:
    response = toncenter_post("sendBoc", {"boc": boc_base64})
    return bool(response.get("ok", True))


def fetch_balance(address: str) -> Optional[Decimal]:
    try:
        result = toncenter_get("getAddressBalance", {"address": address})
    except Exception as exc:
        print(f"No se pudo obtener el saldo: {exc}")
        return None
    raw = result.get("result")
    if raw is None:
        return None
    return Decimal(str(raw)) / Decimal(1_000_000_000)


def lookup_wallet_seqno(address: str) -> Optional[int]:
    try:
        result = toncenter_get("getWalletInformation", {"address": address})
    except Exception:
        return None
    data = result.get("result")
    if not data:
        return None
    seqno = data.get("seqno")
    return int(seqno) if seqno is not None else None


def ensure_wallet_deployed(wallet: WalletData) -> bool:
    if lookup_wallet_seqno(wallet.address_hex) is not None:
        return True

    print("Desplegando monedero (requiere fondos en la dirección)...")
    if not send_boc(build_init_boc(wallet)):
        print("Toncenter rechazó el mensaje de despliegue.")
        return False

    for _ in range(10):
        time.sleep(2)
        if lookup_wallet_seqno(wallet.address_hex) is not None:
            print("Despliegue del monedero confirmado.")
            return True

    print("Aún no se confirmó el despliegue del monedero; inténtalo de nuevo una vez que se procese la transacción.")
    return False
```

### 5.4 Ayudantes de cotización

```python
def _token_to_units(amount: Decimal, decimals: int) -> int:
    return int((amount * Decimal(10) ** decimals).to_integral_value(ROUND_DOWN))


def _units_to_token(units: int, decimals: int) -> Decimal:
    return Decimal(units) / (Decimal(10) ** decimals)


def format_amount(value: Decimal, decimals: int) -> str:
    quantum = Decimal("1").scaleb(-decimals)
    text = f"{value.quantize(quantum, rounding=ROUND_DOWN):f}"
    return text.rstrip("0").rstrip(".") if "." in text else text


def _extract_quote(data: Optional[Dict[str, object]]) -> Optional[Dict[str, object]]:
    if not isinstance(data, dict):
        return None
    if "bid_units" in data and "ask_units" in data:
        return data
    if "quote" in data and isinstance(data["quote"], dict):
        return data["quote"]
    for value in data.values():
        if isinstance(value, dict):
            found = _extract_quote(value)
            if found:
                return found
    return None


def _extract_event_error(data: Dict[str, object]) -> Optional[object]:
    if not isinstance(data, dict):
        return None
    if data.get("error"):
        return data["error"]

    result = data.get("result")
    if isinstance(result, dict):
        if result.get("error"):
            return result["error"]
        event_type = str(result.get("type", "")).lower()
        if "rejected" in event_type:
            return result.get("error") or result

    params = data.get("params")
    if isinstance(params, dict):
        return _extract_event_error(params)
    return None


def _format_error(error: object) -> str:
    if isinstance(error, dict):
        message = error.get("message") or error.get("reason")
        if isinstance(message, str) and message.strip():
            return message.strip()
        try:
            text = json.dumps(error)
            if text.strip():
                return text
        except Exception:
            pass
    text = str(error)
    return text if text.strip() else repr(error)


def describe_quote(quote: Dict[str, object], config: SwapConfig) -> (Decimal, str):
    ask_units = quote.get("ask_units") or quote.get("askUnits")
    bid_units = quote.get("bid_units") or quote.get("bidUnits")
    if ask_units is None or bid_units is None:
        return Decimal("0"), "La cotización no tiene unidades"

    ask_amount = _units_to_token(int(ask_units), config.to_token_decimals)
    bid_amount = _units_to_token(int(bid_units), config.from_token_decimals)
    summary = (
        f"Intercambiar {format_amount(bid_amount, config.from_token_decimals)} -> "
        f"{format_amount(ask_amount, config.to_token_decimals)}"
    )
    return ask_amount, summary


def request_quote(config: SwapConfig, wallet: WalletData) -> Dict[str, object]:
    bid_units = str(_token_to_units(config.amount, config.from_token_decimals))
    request_id = str(uuid.uuid4())
    params = {
        "bid_asset_address": {"blockchain": BLOCKCHAIN_ID, "address": config.from_token_address},
        "ask_asset_address": {"blockchain": BLOCKCHAIN_ID, "address": config.to_token_address},
        "amount": {"bid_units": bid_units},
        "referrer_fee_bps": 0,
        "settlement_methods": [0],  # 0 == SWAP
        "settlement_params": {
            "max_price_slippage_bps": config.max_slippage_bps,
            "max_outgoing_messages": config.max_outgoing_messages,
            "gasless_settlement": config.gasless_mode,
            "flexible_referrer_fee": config.flexible_referrer_fee,
            "wallet_address": {"blockchain": BLOCKCHAIN_ID, "address": wallet.address_hex},
        },
    }

    payload = {
        "jsonrpc": "2.0",
        "id": request_id,
        "method": "v1beta7.quote",
        "params": params,
    }

    async def _run() -> Dict[str, object]:
        async with websockets.connect(OMNISTON_WS_URL, ping_interval=20, ping_timeout=20) as ws:
            await ws.send(json.dumps(payload))
            deadline = time.time() + QUOTE_TIMEOUT
            print("Esperando la cotización...")
            while time.time() < deadline:
                timeout = max(0.1, deadline - time.time())
                try:
                    raw = await asyncio.wait_for(ws.recv(), timeout=timeout)
                except asyncio.TimeoutError:
                    continue
                data = json.loads(raw)
                event_error = _extract_event_error(data)
                if event_error:
                    raise RuntimeError(_format_error(event_error))
                quote = _extract_quote(data.get("result")) or _extract_quote(data)
                if quote:
                    return quote
            raise RuntimeError("Se agotó el tiempo esperando la cotización de Omniston")

    try:
        return asyncio.run(_run())
    except Exception as exc:
        raise RuntimeError(f"La solicitud de cotización a Omniston falló: {exc!r}") from exc
```

### 5.5 Ayudantes de transferencia

```python
def _decode_cell(raw: Optional[str]) -> Optional[Cell]:
    if raw is None:
        return None
    raw = raw.strip()
    if not raw:
        return None
    try:
        data = base64.b64decode(raw)
    except Exception:
        try:
            data = bytes.fromhex(raw)
        excepto ValueError:
            return None
    try:
        return Cell.one_from_boc(data)
    except Exception:
        return None


def _extract_transfer(data: Optional[Dict[str, object]]) -> Optional[Dict[str, object]]:
    if not isinstance(data, dict):
        return None
    if "ton" in data and isinstance(data["ton"], dict):
        return data
    if "transaction" in data and isinstance(data["transaction"], dict):
        return data["transaction"]
    for value in data.values():
        if isinstance(value, dict):
            found = _extract_transfer(value)
            if found:
                return found
    return None


def _build_transfer(quote: Dict[str, object], wallet: WalletData) -> Dict[str, object]:
    request_id = str(uuid.uuid4())
    params = {
        "quote": quote,
        "source_address": {"blockchain": BLOCKCHAIN_ID, "address": wallet.address_hex},
        "destination_address": {"blockchain": BLOCKCHAIN_ID, "address": wallet.address_hex},
        "gas_excess_address": {"blockchain": BLOCKCHAIN_ID, "address": wallet.address_hex},
        "use_recommended_slippage": True,
    }
    payload = {
        "jsonrpc": "2.0",
        "id": request_id,
        "method": "v1beta7.transaction.build_transfer",
        "params": params,
    }

    async def _run() -> Dict[str, object]:
        async with websockets.connect(OMNISTON_WS_URL, ping_interval=20, ping_timeout=20) as ws:
            await ws.send(json.dumps(payload))
            deadline = time.time() + TRANSFER_TIMEOUT
            while time.time() < deadline:
                raw = await asyncio.wait_for(ws.recv(), timeout=max(0.1, deadline - time.time()))
                data = json.loads(raw)
                if data.get("error"):
                    raise RuntimeError(data["error"])
                transfer = _extract_transfer(data.get("result")) or _extract_transfer(data)
                if transfer:
                    return {"jsonrpc": data.get("jsonrpc", "2.0"), "result": transfer}
            raise TimeoutError("Se agotó el tiempo de espera para la creación de la transferencia")

    return asyncio.run(_run())


def execute_swap(quote: Dict[str, object], wallet: WalletData) -> bool:
    if not TONCENTER_API_KEY:
        print("TONCENTER_API_KEY no está configurada; no se puede enviar la transacción automáticamente.")
        return False

    try:
        transfer = _build_transfer(quote, wallet)
    except Exception as exc:
        print(f"No se pudo construir la transferencia: {exc}")
        return False

    ton_section = transfer.get("result", {}).get("ton") if isinstance(transfer, dict) else None
    messages = ton_section.get("messages") if isinstance(ton_section, dict) else None
    if not isinstance(messages, list) or not messages:
        print("No se devolvieron mensajes de transferencia.")
        return False

    msg = messages[0]
    payload_cell = _decode_cell(
        msg.get("payload")
        or msg.get("message_boc")
        or msg.get("messageBoc")
        or msg.get("boc")
    )
    state_init_cell = _decode_cell(
        msg.get("state_init") or msg.get("jetton_wallet_state_init")
    )

    seqno = lookup_wallet_seqno(wallet.address_hex)
    if seqno is None:
        print("No se pudo obtener el seqno de la billetera; asegúrate de que la billetera esté desplegada y financiada.")
        return False

    version = WalletVersionEnum(wallet.version)
    _, _, _, contract = Wallets.from_mnemonics(wallet.mnemonic, version, wallet.workchain)

    target_address = msg.get("target_address") or msg.get("address")
    amount_field = msg.get("send_amount") or msg.get("amount")
    try:
        amount_value = int(str(amount_field))
    except Exception:
        print("Cantidad de envío inválida en el mensaje de transferencia.")
        return False

    external = contract.create_transfer_message(
        target_address,
        amount_value,
        seqno,
        payload=payload_cell,
        state_init=state_init_cell,
    )

    boc = base64.b64encode(external["message"].to_boc(False)).decode("ascii")
    print("Enviando el swap a través de Toncenter...")
    ok = send_boc(boc)
    print("Swap enviado." if ok else "Toncenter rechazó la transacción.")
    return ok
```

### 5.6 Punto de entrada del comando

```python
MIN_INIT_BALANCE_TON = Decimal("0.25")


def main() -> None:
    load_env()
    config = load_config()

    print("Omniston Python Quickstart")
    print("==========================")

    wallet = ensure_wallet()
    print(f"Billetera: {wallet.address_bounceable}")

    balance = fetch_balance(wallet.address_hex) or Decimal("0")
    print(f"Saldo: {format_amount(balance, 9)} TON")

    if balance < MIN_INIT_BALANCE_TON:
        need = format_amount(MIN_INIT_BALANCE_TON, 9)
        print(f"Por favor, financia tu billetera con al menos {need} TON y luego vuelve a ejecutar este script.")
        print(f"Envía TON a: {wallet.address_bounceable}")
        return

    if not ensure_wallet_deployed(wallet):
        return

    if not prompt_yes_no("¿Solicitar una cotización ahora?", default=True):
        print("Cotización omitida.")
        return

    try:
        quote = request_quote(config, wallet)
    except Exception as exc:
        print(f"La solicitud de cotización falló: {exc}")
        return

    received, summary = describe_quote(quote, config)
    if received <= Decimal("0"):
        print("La cotización devolvió salida cero; abortando.")
        return

    if not prompt_yes_no(f"¿Intercambiar {summary}?", default=False):
        print("Swap cancelado.")
        return

    print(f"Ejecutando swap: {summary}")
    execute_swap(quote, wallet)


if __name__ == "__main__":
    main()
```

***

## 6. Solicitar una cotización

Ejecuta la CLI y sigue la indicación:

```bash
python omniston_cli.py
```

Cuando aceptes "¿Solicitar una cotización ahora?", la CLI hará lo siguiente:

* Construirá una solicitud usando tu par y cantidad configurados.
* Se conectará a Omniston por WebSockets (v1beta7.quote).
* Mostrará un resumen legible para humanos (por ejemplo, Swap 0.01 -> 12.34).

Si no se puede producir una cotización a tiempo (predeterminado: \~15 s), verás un mensaje de tiempo de espera o de error.

***

## 7. Construcción de una transacción y envío

Después de que apruebes el resumen de la cotización, la CLI:

1. Llama al constructor de transacciones de Omniston (v1beta7.transaction.build\_transfer) para obtener mensajes TON.
2. Firma un mensaje externo con tu billetera.
3. Envía el BOC resultante a Toncenter.

**Importante**: El envío automático requiere `TONCENTER_API_KEY` en `.env`. Sin ello, la CLI omite la difusión.

***

## 8. Prueba de tu Swap

1. Activa tu entorno virtual y asegúrate de que `.env` y `swap_config.json` existan.
2. Financia tu billetera y ejecuta:

   ```bash
   python omniston_cli.py
   ```
3. Confirma:
   * La billetera se crea/carga y se imprime el saldo.
   * El despliegue se completa (o ya está desplegado).
   * Se recibe y resume una cotización.
   * Al aprobarse, la transacción se envía y ves "Swap enviado.".
   * Revisa tu billetera en un explorador de TON para confirmar el swap.

Si algo falla, la CLI imprime un mensaje claro (por ejemplo, tiempo de espera, falta de clave API, problema de seqno).

***

## 9. Conclusión

Ahora tienes una CLI mínima en Python que:

* Genera o reutiliza una billetera TON.
* Carga la configuración local de activos y red.
* Solicita cotizaciones en tiempo real desde Omniston.
* Construye y envía el swap a través de Toncenter.

Ideas para ampliar:

* Banderas personalizadas de CLI (cantidad/par) con argparse.
* Mejor reporte de errores y reintento/backoff.
* Enlaces al explorador después del envío.
* Seguimiento de operaciones con comprobaciones periódicas de estado.

## 10. Demo en vivo

Ejecuta la CLI de Python de Omniston directamente en tu navegador mediante Replit:

* Abre el proyecto en Replit
* Haz un fork en tu cuenta para guardar los cambios
* Añade un `TONCENTER_API_KEY` en los secretos de Replit
* Ejecuta `python omniston_cli.py` en la shell de Replit
* Explora y modifica el código libremente

{% embed url="<https://replit.com/@stonfi/omniston-python-cli?embed=true>" %}

***

## 11. Uso de agentes de IA para la implementación automatizada

También puedes seguir una demostración grabada:

{% embed url="<https://www.youtube.com/watch?v=qaRq40kOsL8>" %}

Para los desarrolladores que buscan acelerar su proceso de desarrollo, puedes aprovechar **agentes de programación de IA** para implementar automáticamente la funcionalidad de swap de Omniston descrita en esta guía. Aunque mostramos **Gemini CLI** en nuestro ejemplo (debido a su generoso plan gratuito), puedes usar cualquier asistente de programación de IA como **Claude Code**, **GitHub Copilot**, **Cursor Agent**, o herramientas similares.

### 11.1 ¿Por qué agentes de IA?

Los agentes modernos de programación de IA pueden:

* Entender documentación compleja e implementar funciones completas
* Configurar automáticamente la estructura del proyecto y las dependencias
* Resolver errores comunes de configuración e instalación
* Proporcionar implementaciones funcionales en minutos en lugar de horas

### 11.2 Configuración con Gemini CLI (ejemplo)

Lo demostraremos con Gemini CLI, pero el enfoque funciona con cualquier agente de IA que pueda leer documentación y ejecutar comandos.

#### 11.2.1 Instalación de Gemini CLI

1. Instala Gemini CLI siguiendo las instrucciones en: <https://github.com/google-gemini/gemini-cli>
2. Autentícate con tu cuenta de Google cuando se te solicite. El plan gratuito incluye:
   * 60 solicitudes al modelo por minuto
   * 1.000 solicitudes al modelo por día

#### 11.2.2 Configuración de la guía de implementación

1. Descarga el archivo de guía correspondiente desde el gist: <https://gist.github.com/mrruby/a6fba69716fc0d5b8eaafd43998b36c0>
   * **Para Claude Code**: Descarga `AGENTS.md` y renómbralo a `CLAUDE.md`
   * **Para otros agentes de IA** (Gemini CLI, GitHub Copilot, Cursor, etc.): Usa `AGENTS.md` tal cual
2. Crea un nuevo directorio para tu proyecto y coloca dentro el archivo de la guía:

   ```bash
   mkdir my-omniston-swap
   cd my-omniston-swap
   # Coloca aquí CLAUDE.md (para Claude Code) o AGENTS.md (para otros agentes)
   ```

#### 11.2.3 Ejecución de la implementación automatizada

1. Desde el directorio de tu proyecto, ejecuta Gemini CLI:

   ```bash
   gemini
   ```
2. Cuando se abra la interfaz de la CLI, escribe:

   ```
   Implementa el swap de Omniston según la Guía de inicio rápido de Omniston en AGENTS.md
   ```
3. El agente de IA:
   * Pedirá permiso para usar comandos como `python3`, `pip`, etc.
   * Creará automáticamente la estructura del proyecto
   * Instalará todas las dependencias necesarias
   * Implementará la funcionalidad completa de swap
   * Configurar archivos de configuración
4. **Importante**: Después de que se complete la implementación, debes configurar manualmente tu clave API de Toncenter:
   * Ve a [TON Center](https://toncenter.com/) y obtén tu clave API
   * Crea un archivo `.env` en la raíz de tu proyecto
   * Añade tu clave API: `TONCENTER_API_KEY=tu_clave_api_aquí`
   * Los agentes de IA no pueden manejar este paso, ya que requiere tus credenciales personales de API
5. Si ocurre algún error durante el proceso:
   * Simplemente vuelve a pegar el mensaje de error al agente de IA
   * Analizará y corregirá el problema automáticamente
   * En la mayoría de los casos, la implementación se completa con éxito en un solo intento

### 11.3 Uso de otros agentes de IA

El mismo enfoque funciona con otros asistentes de programación de IA:

* **Claude Code**: Descarga `AGENTS.md` y renómbralo a `CLAUDE.md`, luego colócalo en tu proyecto y pide a Claude Code que implemente la guía
* **GitHub Copilot**: Usa el archivo `AGENTS.md` tal cual, abre la guía en tu editor y usa Copilot Chat para implementar paso a paso
* **Cursor Agent**: Usa el archivo `AGENTS.md` tal cual, carga la documentación y solicita la implementación completa mediante el modo agente de Cursor
* **Herramientas personalizadas**: Cualquier asistente de IA con acceso a archivos y capacidad de ejecutar comandos puede seguir la guía usando el `AGENTS.md` archivo

> **Nota**: El archivo `AGENTS.md` contiene instrucciones compatibles con todos los agentes de IA (Gemini CLI, GitHub Copilot, Cursor, etc.). Sin embargo, Claude Code requiere que el archivo se llame `CLAUDE.md` para ser reconocido automáticamente, así que debes renombrar `AGENTS.md` a `CLAUDE.md` cuando uses Claude Code.

### 11.4 Beneficios de usar agentes de IA

* **Velocidad**: Obtén una implementación funcional en minutos en lugar de horas
* **Precisión**: Los agentes de IA siguen la guía de inicio rápido con exactitud
* **Manejo de errores**: Resuelven automáticamente la mayoría de los problemas comunes de configuración
* **Herramienta de aprendizaje**: Observa cómo se desarrolla la implementación paso a paso
* **Personalización**: Después de la configuración inicial, puedes modificar el código generado para adaptarlo a tus necesidades específicas
* **Rentable**: Muchas herramientas de programación de IA ofrecen planes gratuitos (como Gemini CLI) o están incluidas en suscripciones existentes

### 11.5 Mejores prácticas

1. **Revisa el código**: Revisa siempre el código generado por IA antes de usarlo en producción
2. **Comprende el flujo**: Usa el código generado como herramienta de aprendizaje para entender el protocolo Omniston
3. **Personaliza**: Adapta el código generado a tus requisitos específicos
4. **Prueba a fondo**: Prueba la implementación con cantidades pequeñas antes de procesar transacciones más grandes
5. **Seguridad**: Nunca subas claves API ni frases semilla al control de versiones

Este enfoque es particularmente útil para:

* Desarrolladores nuevos en el ecosistema TON
* Prototipado rápido y pruebas de concepto
* Aprender con un ejemplo y una implementación funcional
* Evitar errores comunes de configuración y problemas de instalación
* Explorar rápidamente diferentes enfoques de implementación

***

¡Feliz intercambio!


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ston.fi/es/seccion-para-desarrolladores/quickstart/python.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
