SDKs & tools
Python SDK
The layerhop Python package — sync and async httpx clients for quotes, transactions, order tracking and webhook verification.
layerhop is a typed client for the REST API with a sync LayerHop and an async AsyncLayerHop. It mirrors the TypeScript SDK. Python 3.9+; the only runtime dependency is httpx.
Source: github.com/layerhop/layerhop-python — source repository (opens at launch).
The SDK never holds keys. It returns unsigned transactions; your own wallet library (web3.py, eth-account, solders, a KMS…) signs and broadcasts them.
Install#
pip install layerhop
# inside the monorepo:
pip install -e sdks/pythonQuickstart#
Amounts are integer strings in base units (100 USDC = "100000000"), never floats.
from layerhop import LayerHop
lh = LayerHop() # LayerHop(api_key="...", integrator="myapp", base_url="http://localhost:8787")
# 1. Quote
q = lh.quote("base", "USDC", "100000000", "solana", "SOL",
sender="0xYourEvmAddress", recipient="YourSolanaAddress")
best = q["best"]
# 2. Build the unsigned transaction
tx = lh.build_tx(best["quoteId"])
# 3. Sign + send with YOUR wallet library (web3.py shown)
if tx["vm"] == "evm":
# send tx["approval"] first when present
tx_hash = w3.eth.send_transaction({"to": tx["to"], "data": tx["data"],
"value": int(tx["value"]), "chainId": tx["chainId"]}).hex()
# 4. Report the broadcast (enables tracking, history and webhooks)
lh.report_submitted(best["quoteId"], tx_hash)
# 5. Track until filled / refunded / failed
for s in lh.watch(best["provider"], tx_hash):
print(s["state"])For Solana, tx["serializedTx"] is a base64 VersionedTransaction; sign it with solders. When tx.get("submitViaApi") is true, only sign it and call lh.submit(quote_id, signed_b64) (why).
Async:
from layerhop import AsyncLayerHop
async with AsyncLayerHop() as lh:
q = await lh.quote("base", "USDC", "100000000", "solana", "SOL", sender=s, recipient=r)
async for st in lh.watch(q["best"]["provider"], tx_hash):
print(st["state"])Methods#
| Method | Endpoint |
|---|---|
health() · chains() · tokens(chain) |
GET /v1/health · /v1/chains · /v1/tokens |
quote(src_chain, src_token, amount, dst_chain, dst_token, *, sender, recipient, slippage_bps=50) |
GET /v1/quote |
build_tx(quote_id) |
POST /v1/orders → the tx |
simulate(quote_id) |
POST /v1/simulate |
submit(quote_id, signed_transaction) |
POST /v1/orders/submit (Solana submitViaApi only) |
report_submitted(quote_id, tx_hash) |
POST /v1/orders/{quoteId}/submitted |
status(provider, id) |
GET /v1/orders/{provider}/{id} |
watch(provider, id, *, interval=3.0, timeout=1800) |
Polls status and yields on each change until final |
list_orders(*, address, status, cursor, limit) |
GET /v1/orders |
Options: base_url, api_key (sent as x-api-key), channel, integrator, integrator_fee_bps, timeout, http_client / transport. Responses are dicts typed with TypedDict, with the API's camelCase keys.
Errors#
Non-2xx responses raise LayerHopApiError with code, status, message and request_id. The code is one of the stable error codes.
from layerhop import LayerHopApiError
try:
lh.build_tx(quote_id)
except LayerHopApiError as e:
if e.code == "QUOTE_EXPIRED":
... # re-quoteWebhooks#
from layerhop import verify_webhook_signature
ok = verify_webhook_signature(secret, request.get_data(), request.headers["LayerHop-Signature"])The header is t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Verify it against the raw body. The check rejects timestamps more than 300 s off (tolerance) and compares in constant time.