# Skyline Bridge — AI Agent Reference (TESTNET) > This document is the authoritative guide for AI agents operating the Skyline bridge on behalf of users. > Read it in full before making any API calls. > > **This is the testnet guide.** Every API, RPC, Ogmios and explorer URL below points at a test network. Never use it to move mainnet funds. --- ## What is Skyline? Skyline is a decentralized cross-chain bridge connecting Cardano, Apex Fusion (Prime, Vector, Nexus), Solana and EVM-compatible chains. It locks tokens on the source chain and releases wrapped equivalents on the destination chain. The bridge is trustless: a decentralised set of validators monitors source chains, co-signs batches, and a Relayer delivers funds to the destination address. --- ## API Base URL ``` https://partner-web-api-skyline.testnet.ethernal.work ``` All paths in this document are relative to this base URL. --- ## Recognised Chain IDs These are the chain IDs the API recognises. A chain being listed here does **not** mean it is currently bridgeable — availability is decided by `enabledChains` and `directionConfig` of the selected bridging mode (Phase 1 Step 1). | Chain ID | Type | Network | |-------------|---------|------------------------------------| | `cardano` | UTXO | Cardano Preview testnet | | `prime` | UTXO | Apex Fusion – Prime testnet | | `vector` | UTXO | Apex Fusion – Vector testnet | | `nexus` | EVM | Apex Fusion – Nexus testnet | | `polygon` | EVM | Polygon Amoy | | `ethereum` | EVM | Ethereum Sepolia | | `unichain` | EVM | Unichain Sepolia | | `sei` | EVM | Sei testnet | | `katana` | EVM | Katana Bokuto | | `solana` | SVM | Solana devnet | > **Note:** `prime` and `vector` are Apex Fusion chains that use Cardano-style UTXO addresses (e.g. `addr_test1...`) and lovelace denomination — treat them as UTXO chains, not EVM. Use `POST /transaction/createCardano` for them. > **Note:** `solana` is an SVM chain — neither UTXO nor EVM. It uses base58 addresses (e.g. `9xQeWv...`), lamport denomination (1 SOL = 1,000,000,000 lamports), and its own build endpoint `POST /transaction/createSolana`. Destination addresses must be on-curve base58 public keys; reject anything else before building. **Source chain type determines which transaction-building endpoint to call** (see Step 3 below). The phases of the workflow are the same for every chain pair; the chain-specific details (signing format, broadcast method) are called out in each phase. --- ## Known RPC URLs Use these to broadcast signed transactions and poll for receipts without asking the user. | Chain ID | RPC URL | EVM Chain ID | |------------|------------------------------------------------|--------------| | `nexus` | `https://rpc.nexus.testnet.apexfusion.org` | `9070` | For EVM chains not listed here, ask the user to provide an RPC URL before proceeding. If the EVM chain ID is not listed above, fetch it via `eth_chainId` before presenting `RAW_TX` to the user — signing scripts require it to protect against replay attacks. ## Known Solana RPC URL Use this to broadcast signed Solana transactions and poll for confirmation without asking the user. | Chain ID | RPC URL | Cluster | |----------|------------------------------------|-----------| | `solana` | `https://api.devnet.solana.com` | devnet | Solana speaks JSON-RPC over HTTP POST like EVM chains, but with different method names and base64 payloads — see Phase 3. ## Known Ogmios Submission URLs (UTXO chains) Use these to submit signed UTXO transactions on behalf of the user — do **not** ask the user to submit manually if the chain is listed here. | Chain ID | Ogmios URL | |-----------|---------------------------------------------------------| | `prime` | `http://ogmios.prime.testnet.apexfusion.org:1337` | | `vector` | `https://ogmios.vector.testnet.apexfusion.org` | | `cardano` | `https://preview-ogmios.onprem.ethernal.work` | Submit via Ogmios v6 JSON-RPC (HTTP POST): ```json { "jsonrpc": "2.0", "method": "submitTransaction", "params": { "transaction": { "cbor": "" } }, "id": 1 } ``` A successful response contains `result.transaction.id` — this is the `txHash`. Use it in Phase 4 to find and track the bridge transaction. For UTXO chains **not** listed here, ask the user to provide the Ogmios URL before proceeding. --- ## Explorer URLs Use these to construct transaction links when reporting results to the user. | Chain ID | Explorer Base URL | Tx URL format | |-------------|------------------------------------------------------------|----------------------------------------------------------------------------| | `prime` | `https://beta-explorer.prime.testnet.apexfusion.org/en` | `https://beta-explorer.prime.testnet.apexfusion.org/en/transactions/` | | `vector` | `https://explorer.vector.testnet.apexfusion.org` | `https://explorer.vector.testnet.apexfusion.org/transaction/hash/` | | `cardano` | `https://preview.cardanoscan.io` | `https://preview.cardanoscan.io/transaction/` | | `nexus` | `https://explorer.nexus.testnet.apexfusion.org` | `https://explorer.nexus.testnet.apexfusion.org/tx/` | | `polygon` | `https://amoy.polygonscan.com` | `https://amoy.polygonscan.com/tx/` | | `ethereum` | `https://sepolia.etherscan.io` | `https://sepolia.etherscan.io/tx/` | | `katana` | `https://bokuto.katanascan.com` | `https://bokuto.katanascan.com/tx/` | | `sei` | `https://testnet.seiscan.io` | `https://testnet.seiscan.io/tx/` | | `unichain` | `https://unichain-sepolia.blockscout.com` | `https://unichain-sepolia.blockscout.com/tx/` | | `solana` | `https://explorer.solana.com` | `https://explorer.solana.com/tx/?cluster=devnet` | **Rules:** - `prime`: append `/transactions/` - `cardano`: append `/transaction/` - `vector`: append `/transaction/hash/` - EVM chains (all others): append `/tx/` - Solana: append `/tx/?cluster=devnet` — the cluster query is required, the explorer defaults to mainnet otherwise - Always include explorer links in the `BRIDGE TRANSACTION SUMMARY` when a `txHash` is available. --- ## Interaction Model The agent never controls the user's private key. The workflow has **four phases**: Build, User Signs, Broadcast & Confirm, Track. There is **no registration call**. Once the transaction is on the source chain, the bridge's indexers discover it on their own. The agent finds the resulting bridge transaction by its source chain and tx hash. ``` ┌─────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────┐ │ Agent │ │ Skyline API│ │ RPC / Ogmios │ │ User │ └────┬────┘ └─────┬──────┘ └──────┬───────┘ └────┬─────┘ │ │ │ │ │── 1. GET /settings ─> │ │ │<─ mode, token IDs, limits ── │ │ │── 2. GET /balance ──> │ │ │<─ source balances ── │ │ │ │ │ │ │── 3. POST /transaction/create* ─> │ │ │<─ unsigned raw tx(s) + fees ── │ │ │ │ │ │ │── 4. Return raw tx(s) + cost preview ─────────────────>│ │ │ │ 5. User │ │ │ │ signs │ │ │ │ (off-chain)│ │<── 6. signed tx ───────────────────────────────────────│ │ │ │ │ │── 7. broadcast ────────────────────────> │ │ (EVM: eth_sendRawTransaction, Solana: sendTransaction, │ UTXO: Ogmios submitTransaction) │ │<─ txHash ──────────────────────────────│ │ │── 8. confirm (EVM receipt / Solana status) ─> │ │ │ │ │ │── 9. Poll GET /bridgeTransaction/byTxHash/{originChain}/{txHash} ─> │<─ 404 until indexed, then bridge tx (id, status) ── │ │ │ │ │ │── 10. Poll GET /bridgeTransaction/{id} ─> │ │── 11. Report status updates + final summary ──────────>│ ``` **Key rules:** - The agent builds the transaction; the user only signs it. - For **EVM source chains**: once the user returns the signed transaction, the agent broadcasts it to the chain RPC, waits for it to be mined, then tracks the bridge to completion — without any further input from the user. - For **UTXO source chains** (cardano, prime, vector): if the chain has a known Ogmios URL, the agent submits the signed transaction on the user's behalf via `submitTransaction` and takes `txHash` from `result.transaction.id`. If the chain is not listed, ask the user for the Ogmios URL before proceeding. - For **Solana**: the agent broadcasts the signed base64 transaction via `sendTransaction`, confirms it with `getSignatureStatuses`, then tracks the bridge — without any further input from the user. - The bridge picks the transaction up from the source chain by itself. Never call any endpoint to "submit", "register" or "activate" a bridge transaction — none exists. - The user must provide their addresses before the agent can proceed. --- ## Information to Collect from the User Before any API call, collect: | Field | Notes | |----------------------|-------------------------------------------------------------------------------| | `senderAddress` | The user's address on the **source** chain | | `destinationAddress` | The user's address on the **destination** chain (may be the same as sender) | | `originChain` | Source chain ID (from the table above) | | `destinationChain` | Destination chain ID | | token | The source token the user wants to bridge (e.g. "AP3X", "ADA", "cAP3X") | | `amount` | Human-readable (e.g., "1 AP3X", "500 ADA") — convert to smallest unit for API | If the user says "bridge to the same address", set `destinationAddress = senderAddress`. > **Testnet token naming:** Users may refer to testnet tokens with a `t` prefix (e.g. `tAP3X`, `tADA`). Strip the leading `t` and match against `ecosystemTokens` by the base name (e.g. `AP3X`, `ADA`). If no match is found with the prefix stripped, search as-is. --- ## Amount Units and Decimals All amounts sent to and returned by the API are **integers in the token's smallest unit**. Always convert with integer (or decimal-string) arithmetic — never with floating point. **Native currencies:** | Currency | Smallest unit | Decimals | |--------------------------------------------|---------------|----------| | ADA / AP3X on UTXO chains (cardano, prime, vector) | lovelace (DFM) | 6 | | Native currency of an EVM chain (AP3X on Nexus, ETH, …) | wei | 18 | | SOL | lamports | 9 | **Wrapped tokens:** every bridgeable token that is not a chain's native currency is a wrapped representation of another chain's currency, and it **keeps the decimals of the currency it wraps**. Example: cAP3X on Cardano is wrapped AP3X from Prime, so it has 6 decimals (1 cAP3X = 1,000,000 units). Resolve the wrapped currency from the token name in `ecosystemTokens`. On EVM chains you can confirm an ERC-20 token's decimals by calling its `decimals()` function (the token's `chainSpecific` is the contract address): ```json { "jsonrpc": "2.0", "method": "eth_call", "params": [{ "to": "", "data": "0x313ce567" }, "latest"], "id": 1 } ``` The result is a hex-encoded uint8 — e.g. `0x...0000000000000000000000000000000000000000000000000000000000000012` → `0x12` = 18 decimals. If it disagrees with the wrapped-currency rule, trust the contract. > **`chainSpecific` interpretation for EVM chains:** If a token's `chainSpecific` value is `'lovelace'` on an EVM chain, it means the token is the **native currency** of that chain (analogous to ETH on Ethereum). It does **not** mean the token uses 6-decimal Lovelace denomination. Native EVM currencies always use 18 decimals (wei). **Fee currency:** `bridgingFee` and `operationFee` in the build response are always denominated in the **native currency of the origin chain** (e.g. AP3X on Nexus, ADA on Cardano, SOL on Solana). They are never in the bridged token. Always label them accordingly when presenting to the user. --- ## Bridging Workflow The workflow has **4 phases**. The decision points are in Phase 1: Step 1 selects the bridging mode (reactor or skyline), and Step 3 selects the build endpoint by source chain type. --- ### Phase 1 — Build #### Step 1 — Fetch Settings and Select the Bridging Mode ``` GET [API URL]/settings ``` The response has one settings block per bridging mode under `settingsPerMode`: `reactor` and `skyline`. The API picks the mode for a request as follows, and the agent must do the same: 1. Resolve the requested token in `settingsPerMode.reactor.bridgingSettings.ecosystemTokens`. If `settingsPerMode.reactor.bridgingSettings.directionConfig[originChain].destChain[destinationChain]` contains an entry with that token's `id` as `srcTokenID`, the mode is **reactor**. 2. Otherwise, repeat the check in `settingsPerMode.skyline.bridgingSettings`. If the route contains the token, the mode is **skyline**. 3. If neither contains it, the token cannot be bridged on this route (see "If the token is not found" below). With the current configuration, **reactor** covers AP3X between Apex chains (`prime`, `vector`, `nexus`); everything else is **skyline**. Always run the check rather than relying on this summary. From here on, `` means the selected mode. Read **every** setting (`enabledChains`, `ecosystemTokens`, `directionConfig`, fees, limits) from `settingsPerMode.` only. Ignore the top-level `directionConfig`, `ecosystemTokens` and `enabledChains` fields, and ignore the other mode's block. Then validate **in order**: **1. Confirm chains are enabled** Check `settingsPerMode..enabledChains` contains both `originChain` and `destinationChain`. If either is missing, stop and inform the user that the route is not supported. **2. Resolve and validate the token** - Find the requested token by name in `settingsPerMode..bridgingSettings.ecosystemTokens` to get its `id`. - Check that this `id` appears in `directionConfig[originChain].tokens` — this confirms the token exists on the source chain. The token entry also gives `chainSpecific` (ERC-20 contract, SPL mint, or `'lovelace'` for the native currency). - Check that `directionConfig[originChain].destChain[destinationChain]` contains an entry with this `srcTokenID` — this confirms the token can be bridged on this specific route. - **If the token is not found on this route in either mode**, do not proceed. Instead: - List all tokens available for the route by reading `directionConfig[originChain].destChain[destinationChain]` in both modes and resolving each `srcTokenID` back to a name via that mode's `ecosystemTokens`. - Present the list to the user and ask them to choose one. > Example: A user asks to bridge "ETH" from Nexus to Cardano. ETH (id: 20) exists in `ecosystemTokens` but does not appear in `directionConfig.nexus.destChain.cardano`. The agent must not call the build endpoint — instead it should list the tokens that ARE available on that route (e.g. xADA, myTestToken, etc.) and ask the user to pick one. **3. Validate the amount** Use `settingsPerMode..bridgingSettings`: - **Maximum:** `maxAmountAllowedToBridge` when bridging the origin chain's native currency; `maxTokenAmountAllowedToBridge` for any other token. A value of `"0"` means no limit. - **Minimum:** `minValueToBridge` for the native currency; `minColCoinsAllowedToBridge[originChain]` for other tokens. When the destination is a UTXO chain, the amount must also leave at least `minUtxoChainValue[destinationChain]` on the destination. Reject amounts outside this range before calling the build endpoint. These checks are a pre-filter: the build endpoint applies the final limits and returns `400` with the reason if the amount is rejected — show that reason to the user. **Response shape (relevant parts, same for `reactor` and `skyline`):** ```json { "settingsPerMode": { "skyline": { "enabledChains": ["cardano", "prime", "vector", "nexus", ...], "bridgingSettings": { "minValueToBridge": 1000000, "maxAmountAllowedToBridge": "...", "maxTokenAmountAllowedToBridge": "...", "minColCoinsAllowedToBridge": { "": "..." }, "minUtxoChainValue": { "": 1000000 }, "minChainFeeForBridging": { "": "..." }, "minOperationFee": { "": "..." }, "directionConfig": { "": { "destChain": { "": [{ "srcTokenID": 1, "dstTokenID": 2 }] }, "tokens": { "1": { "chainSpecific": "...", "lockUnlock": true, "isWrappedCurrency": false } } } }, "ecosystemTokens": [{ "id": 1, "name": "ADA" }] } }, "reactor": { "...": "same shape" } } } ``` --- #### Step 2 — Check the Source Balance Before building, confirm the sender can afford the transfer: ``` GET [API URL]/balance?chain=&address=[&tokens=] ``` - `tokens` — for an ERC-20 token pass its contract address, for an SPL token its mint (both are the token's `chainSpecific`). **Never** pass the numeric `tokenID` from `ecosystemTokens`. Omit for UTXO chains: all assets on the address are returned. Response: ```json { "chain": "nexus", "address": "0x...", "amount": "", "tokens": [{ "unit": "", "amount": "" }] } ``` - Bridging the **native currency**: `amount` must exceed the bridged amount. - Bridging **any other token**: the matching `tokens[].amount` must be ≥ the bridged amount, and the native `amount` must be non-zero (fees are paid in native currency). If the balance is short, stop and tell the user how much is missing. The exact check against the real fees happens after the build (Step 3). --- #### Step 3 — Build the Transaction **Choose the endpoint based on the source chain type:** | Source chain | Endpoint | |-------------------------------|-----------------------------------| | `cardano`, `prime`, `vector` | `POST /transaction/createCardano` | | `solana` | `POST /transaction/createSolana` | | Any EVM chain | `POST /transaction/createEth` | The endpoints accept the same request body. The API selects the bridging mode itself, using the rule in Step 1: ```json { "senderAddress": "", "originChain": "", "destinationChain": "", "destinationAddress": "", "amount": "", "tokenID": 1 } ``` `tokenID` is the integer token `id` from settings. Optional fields (omit to use API-calculated defaults): | Field | Description | |----------------|----------------------------------------------------------------| | `bridgingFee` | Fee covering destination chain submission cost | | `operationFee` | Fee covering bridge operational costs | | `utxoCacheKey` | UTXO caching key (UTXO source chains only, can be omitted) | --- **EVM response** (`POST /transaction/createEth`). All numeric fields are **hex strings**: ```json { "approvalTx": { "from": "0xSender", "to": "0xTokenContract", "value": "0x0", "data": "0x095ea7b3..." }, "bridgingTx": { "ethTx": { "from": "0xSender", "to": "0xBridgeContract", "value": "0x214e8348c4f0000", "data": "0xabcdef..." }, "bridgingFee": "0x16345785d8a0000", "operationFee": "0xb1a2bc2ec50000", "tokenAmount": "0xde0b6b3a7640000", "tokenID": 3 } } ``` (This example bridges 1 ERC-20 token with 18 decimals: bridging fee 0.1, operation fee 0.05, so `value` = 0.15 in the native currency.) **Understanding the EVM fee structure:** | Field | Skyline mode | Reactor mode | |---------------------------|------------------------------------------------------------------------------|-----------------------------| | `bridgingTx.ethTx.value` | `bridgingFee + operationFee`, plus the bridged amount when bridging the native currency | `amount + bridgingFee` | | `bridgingTx.bridgingFee` | Bridging fee, in origin native currency | Bridging fee | | `bridgingTx.operationFee` | Operation fee, in origin native currency (may be `0x0`) | Always `0x0` | | `bridgingTx.tokenAmount` | The bridged amount for an ERC-20 token; `0x0` for the native currency | Always `0x0` | | `bridgingTx.tokenID` | Token ID | `0` | - `ethTx.value` is the native currency that leaves the wallet with the bridge transaction. For an ERC-20 token, `tokenAmount` is taken from the wallet separately by the contract call. - **Native currency bridging:** recover the bridged amount for display as `value − bridgingFee − operationFee`. - On top of this, the user pays the network (gas) fee for each transaction — see Phase 2. - If `approvalTx` is **non-null**: the user must sign and submit it first (ERC-20 allowance), wait for confirmation, then sign and submit `bridgingTx.ethTx`. The `approvalTx.to` field is the token contract; the spender encoded in `approvalTx.data` is the bridge gateway contract. Users can independently verify this spender address against `GET /settings/getBridgingAddresses?chainId=`. - If `approvalTx` is **null or absent entirely**: only `bridgingTx.ethTx` needs to be signed. Treat a missing `approvalTx` key the same as `null`. `approvalTx` only tells you whether an approval is needed — it does **not** tell you the token type. Native currency never needs one, and some ERC-20 tokens (e.g. xADA on Nexus) don't either. Determine the token type from `chainSpecific`. **UTXO response** (`POST /transaction/createCardano`, for `cardano`, `prime` and `vector`): ```json { "txRaw": "84a500...", "txHash": "abc123...", "bridgingFee": "1100000", "operationFee": "500000", "amount": "5000000", "nativeTokenAmount": null } ``` - `txRaw` is the **unsigned** transaction (CBOR hex). The user signs it afterwards. There is no `approvalTx` for UTXO chains. - When bridging a **native UTXO token** (not lovelace), the response has `amount: "0"` and `nativeTokenAmount` as an array: `[{"tokenID": , "amount": ""}]`. When bridging lovelace, `nativeTokenAmount` is `null` or an empty array. - The network fee is not a separate field. It is field `2` of the transaction body inside `txRaw` (decode the CBOR to read it). > **TTL / expiry:** UTXO transactions encode a TTL (time-to-live) slot in the tx body. TTLs are short — typically only a few minutes. Always warn the user to sign and return promptly after receiving the `txRaw`. If the user requests a rebuild, or if submission is rejected due to expiry, call `POST /transaction/createCardano` again with the same parameters to get a fresh `txRaw` with a new TTL. The same Ogmios submission flow applies to the rebuilt transaction. **Solana response** (`POST /transaction/createSolana`): ```json { "approvalTx": { "txRaw": "AQABBc0v...", "lastValidBlockHeight": "298345120" }, "bridgingTx": { "solTx": { "txRaw": "AQABBc0v...", "lastValidBlockHeight": "298345120" }, "bridgingFee": "1100000", "operationFee": "500000", "tokenAmount": "5000000000", "tokenID": 3 } } ``` - `bridgingTx.solTx.txRaw` is the **unsigned** transaction encoded as **base64** — not hex, unlike EVM and UTXO chains. Never present it as hex. - `approvalTx` has the same shape and appears only for SPL token bridges that need a token account or allowance set up first. If it is `null` or absent, only `bridgingTx.solTx` needs signing. When present, the user signs and submits it first, the agent waits for confirmation, then the user signs the bridging tx — the same one-at-a-time rule as the EVM two-tx flow. - **Native SOL vs SPL token:** `bridgingTx.tokenAmount` is `"0"` when bridging native SOL and non-zero when bridging an SPL token. - The bridged SOL amount (for native SOL), `bridgingFee` and `operationFee` all leave the wallet in lamports. For an SPL token, `tokenAmount` leaves the wallet in that token's units. The Solana network fee comes on top. > **Expiry:** `lastValidBlockHeight` is the block height past which the transaction is no longer accepted. Solana blockhashes stay valid only ~60–90 seconds, so this window is much tighter than the UTXO TTL — warn the user to sign and return immediately. Keep `lastValidBlockHeight`: Phase 3 needs it to decide expiry. --- #### Step 4 — Re-check the Balance Against the Real Cost Using the build response and an estimate of the network fee (Phase 2), check again that the `/balance` result covers: - **native balance** ≥ (bridged amount if it is the native currency) + `bridgingFee` + `operationFee` + network fee(s) for every transaction to sign (approval + bridge); - **token balance** ≥ bridged amount, for a non-native token. If not, stop before asking the user to sign, and report the shortfall per currency. --- ### Phase 2 — User Signs Return the raw transaction(s) to the user. Always include a clear cost summary so the user can make an informed decision before signing. **Network fee estimate:** - EVM: `gas × maxFeePerGas` per transaction (see `RAW_TX` below), summed over approval + bridge. - UTXO: the fee in field `2` of the tx body. - Solana: 5,000 lamports per signature, plus any priority fee in the transaction. **Always present to the user before signing, using this exact structure:** ``` ══════════════════════════════════════════ BRIDGE TRANSACTION PREVIEW ══════════════════════════════════════════ Token : Amount : Source : — Destination : — Bridging fee : Operation fee : Network fee : ~ Total cost : [+ — only when the token is not the native currency] ══════════════════════════════════════════ ``` - **Native currency bridged:** Total = bridged amount + bridging fee + operation fee + network fee, all in one currency. - **Any other token:** Total = bridging fee + operation fee + network fee in the native currency, **plus** the bridged amount on its own line in the token. Never add amounts in different currencies together. If the bridge fees (bridging + operation) exceed 10% of the bridged amount **and both are in the same currency**, add a warning line below the structure: `⚠ Bridging fee is % of the bridged amount.` When the currencies differ, don't compute a percentage; just show the fees. **If two transactions are needed (EVM or Solana approval):** - Present **Tx 1 (Approval)** and **Tx 2 (Bridge)** clearly labelled. - Tell the user to sign **Tx 1 only** first and provide the signed transaction back. - The agent broadcasts Tx 1, waits for on-chain confirmation, then presents Tx 2 and asks for its signed transaction. - Do **not** ask for both signed transactions at once — they must be handled one at a time. **If only one transaction is needed:** state that a single transaction is needed. **Ask the user to provide back the complete signed transaction** — the agent will handle broadcasting and everything that follows. #### Format per source chain **UTXO chains** — present the unsigned `txRaw` hex directly for signing — **not** `txHash`. `txHash` is for reference only; tell the user this explicitly. Ask for the fully assembled signed transaction (CBOR hex). Some signing tools (e.g. CIP-30 `signTx`) return only a witness set instead. Tell them apart by **decoding the CBOR**, not by its prefix: - a 4-element array `[tx_body, witness_set, is_valid, auxiliary_data]` is a full transaction — use it as is; - a map is a witness set — assemble the full transaction: 1. Decode the original `txRaw` from the build response. 2. Merge the user's witnesses into element `[1]`: add each vkey witness (map key `0`) to any already present, and keep every other existing witness entry. Do not replace the set wholesale. 3. Keep `tx_body` (element `[0]`) as the **original bytes** — do not re-serialise it, or the transaction hash and signatures will no longer match. 4. Re-encode the array as CBOR — this is the complete signed transaction. 5. Proceed without asking the user for anything further. **Solana** — present the unsigned `txRaw` **base64** string directly for signing and ask for the signed transaction back, also base64-encoded. Do **not** use the `RAW_TX` format — it is EVM-only, and Solana transactions carry no `gas`, `nonce` or `chainId` fields. State the expiry explicitly: the transaction is void once the chain passes `lastValidBlockHeight`, typically under two minutes away. **EVM** — always present each transaction using this exact `RAW_TX` format so the user can paste it directly into their signing script: ```js RAW_TX = { from: "", to: "", value: "", data: "", gas: "", maxFeePerGas: "", maxPriorityFeePerGas: "", nonce: "", chainId: , }; ``` - `from` — the user's source chain address - `to` — the contract address (token contract for approval, bridge contract for bridge tx) - `value` — `value` from the API response, hex-encoded (`"0x0"` for approvals) - `data` — hex-encoded calldata from the API response - `gas` — fetch via `eth_estimateGas` against the RPC before presenting to the user - `maxFeePerGas` / `maxPriorityFeePerGas` — fetch current values via `eth_gasPrice` and `eth_maxPriorityFeePerGas` - `nonce` — fetch via `eth_getTransactionCount(, "pending")` **immediately before presenting the transaction**. In the two-tx EVM flow, fetch the nonce fresh for each transaction after the previous one is confirmed — do not compute the second nonce by incrementing the first. - `chainId` — use the known value from the RPC table, or fetch via `eth_chainId` if not listed For EVM transactions, never present the transaction in any other format (e.g. prose fields, JSON object, or raw hex string). Wait for the signed transaction before proceeding to Phase 3. --- ### Phase 3 — Broadcast & Confirm The agent always broadcasts on the user's behalf. Never ask the user to submit manually, except to supply a missing RPC or Ogmios URL. #### EVM source chains **Step 1 — Broadcast** via `eth_sendRawTransaction` to the chain's RPC URL: ``` POST Content-Type: application/json { "jsonrpc": "2.0", "method": "eth_sendRawTransaction", "params": [""], "id": 1 } ``` The `result` field is the `txHash`. **Step 2 — Wait for mining** by polling `eth_getTransactionReceipt` every 5 seconds until `result` is non-null: ```json { "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": [""], "id": 1 } ``` Check `result.status`: - `"0x1"` — success, proceed to Phase 4 (or, for an approval, present the bridge tx) - `"0x0"` — transaction reverted, report failure to user and stop If there is still no receipt after **5 minutes**, stop polling and tell the user the transaction is pending with an **unknown outcome**: give them the `txHash` and explorer link. Do **not** rebuild or re-sign automatically — the original transaction may still be mined, and a second one would bridge twice. #### UTXO source chains (cardano, prime, vector) If the chain has a known Ogmios URL (see table above), submit on the user's behalf using `submitTransaction` and take `txHash` from `result.transaction.id`. Then proceed directly to Phase 4. If the chain has no known Ogmios URL, ask the user to provide the Ogmios URL before proceeding. #### Solana source chains **Step 1 — Broadcast** via `sendTransaction`, passing the signed transaction as base64: ```json { "jsonrpc": "2.0", "method": "sendTransaction", "params": [ "", { "encoding": "base64", "skipPreflight": false, "preflightCommitment": "confirmed" } ], "id": 1 } ``` The `result` field is the transaction signature — this is the `txHash` used to track the bridge in Phase 4 and for explorer links. **Step 2 — Confirm** by polling `getSignatureStatuses` every second: ```json { "jsonrpc": "2.0", "method": "getSignatureStatuses", "params": [[""], { "searchTransactionHistory": true }], "id": 1 } ``` Evaluate `result.value[0]` in this order: 1. `err` is non-null — the transaction **failed** (failed transactions still reach `confirmed`/`finalized`). Report the error and stop. 2. `err` is `null` and `confirmationStatus` is `"confirmed"` or `"finalized"` — success, proceed to Phase 4. 3. `result.value[0]` is `null` — not seen yet. Keep polling, and check expiry with `getBlockHeight` (commitment `confirmed`): - current block height ≤ `lastValidBlockHeight` — the transaction can still land; keep polling. - current block height > `lastValidBlockHeight` — query `getSignatureStatuses` once more with `searchTransactionHistory: true`. If it is still `null`, the transaction **expired** and was never accepted: rebuild via `POST /transaction/createSolana` (Phase 1 Step 3) and ask the user to sign the new one. If it now has a status, go back to 1–2. Never rebuild based on elapsed time alone — an RPC node can lag, and rebuilding while the original is still valid can bridge twice. If the status still can't be resolved, report it to the user as **unknown** with the signature and explorer link, and don't rebuild. --- ### Phase 4 — Track There is no registration call. The bridge's indexers watch the source chain and create the bridge transaction record by themselves once the transaction is on-chain. The agent only looks it up and follows it. #### Step 1 — Find the bridge transaction ``` GET [API URL]/bridgeTransaction/byTxHash/{originChain}/{txHash} ``` | Path param | Value | |---------------|---------------------------------------------------------------------------------------------------| | `originChain` | Source chain ID (from the Recognised Chain IDs table) | | `txHash` | Source tx hash from Phase 3, in exactly this format (the lookup is an exact match): | | | EVM: `0x`-prefixed **lowercase** hex (lowercase it if your tooling returned mixed case) | | | UTXO: 64-char hex **without** `0x` — Ogmios `result.transaction.id` as returned | | | Solana: base58 signature as returned by `sendTransaction` | - `404` means the indexers haven't picked the transaction up yet. This is normal right after confirmation: poll every 5 seconds. A hash in the wrong format also returns `404` forever — check the format above before assuming the bridge is slow. - `400` means `originChain` is not a valid chain ID — fix the request; don't retry it unchanged. - `200` returns the bridge transaction. Save its `id` and continue with Step 2. **Response** — same shape as `GET /bridgeTransaction/{id}`: ```json { "id": 12345, "status": "Pending", "originChain": "...", "destinationChain": "...", "sourceTxHash": "...", "destinationTxHash": null, "senderAddress": "...", "receiverAddresses": "...", "amount": "...", "nativeTokenAmount": "...", "tokenID": 1, "createdAt": "...", "finishedAt": null, "isLayerZero": false, "isRefund": false } ``` A successful submission (Ogmios accepting a UTXO transaction, or `sendTransaction` returning a signature) does not prove inclusion: a UTXO transaction can still expire before it lands. If the lookup keeps returning `404`, check the source explorer link before telling the user the transaction is on-chain. While waiting, tell the user once: `Waiting for the bridge to pick up the transaction…`. If it is still `404` after **5 minutes**, tell the user it is taking longer than usual, give them the source tx link, and keep polling. After **30 minutes** of `404`, stop and tell the user to check later with the same request — never re-send or rebuild the transaction because it hasn't been indexed. #### Step 2 — Poll the status ``` GET [API URL]/bridgeTransaction/{id} ``` Poll every 20 seconds until a terminal status is reached. **Stop polling after 30 minutes** regardless of status — if no terminal state is reached by then, inform the user that the bridge is taking longer than expected and ask them to check the status manually via `GET [API URL]/bridgeTransaction/{id}` using the bridge tx ID provided earlier. If the last status was `FailedToExecuteOnDestination`, also say that destination execution has failed so far and whether a refund is underway (`isRefund`). Always track on your own and proactively keep the user posted. For non-terminal states, send an update after **every** 20-second poll so the user can see tracking is active, even when the status is unchanged: - **First non-terminal status:** send the full `BRIDGE TRANSACTION PROGRESS` block. - **Every later non-terminal poll (changed or unchanged):** send only this single line: `Bridging not finished yet, current status: ` #### Status Lifecycle | Status | Terminal | Action | |---------------------------------|----------|-----------------------------------------------------------------------------------------------------------------| | `Pending` | no | Keep polling | | `DiscoveredOnSource` | no | Keep polling | | `SubmittedToBridge` | no | Keep polling | | `IncludedInBatch` | no | Keep polling | | `SubmittedToDestination` | no | Keep polling | | `ExecutedOnDestination` | **yes** | Check `isRefund`. If `true`: inform the user the bridge did **not** execute and their funds have been refunded. If `false`: report `destinationTxHash` as proof of delivery. Done. | | `FailedToExecuteOnDestination` | no | Keep polling. Destination execution failed, but the bridge outcome is not final yet. If `isRefund` is `true`, tell the user a refund is underway. | | `InvalidRequest` | **yes** | Report error. Do not retry. Ask user to start a new request. | Typical bridging time: **2–10 minutes**. For the **first** non-terminal status, report progress to the user using this exact structure: ``` ══════════════════════════════════════════ BRIDGE TRANSACTION PROGRESS ══════════════════════════════════════════ Bridging not finished yet, current status: Bridge tx ID : Source tx : Destination tx : Tracking : I am polling every 20 seconds and will send an update after every check. Next check : <"Polling again in 20 seconds" | "Manual check: GET [API URL]/bridgeTransaction/{id}"> ══════════════════════════════════════════ ``` Do **not** use the final summary format below until a terminal status is reached. When a terminal status is reached, always report the outcome using this exact structure: ``` ══════════════════════════════════════════ BRIDGE TRANSACTION SUMMARY ══════════════════════════════════════════ Status : Result : Token : Amount : Source : — Destination : — Bridging fee : Operation fee : Total cost : Source tx : Source tx link : Destination tx : Dest tx link : Bridge tx ID : ══════════════════════════════════════════ ``` Set **Result** as follows: | Status | `isRefund` | Result | |--------------------------------|------------|------------------| | `ExecutedOnDestination` | `false` | `Delivered` | | `ExecutedOnDestination` | `true` | `Refunded` | | `InvalidRequest` | any | `Rejected` | - Build the tx links with the per-chain rules in the Explorer URLs section. - If `destinationTxHash` is null, omit both the `Destination tx` and `Dest tx link` lines. --- ## Error Handling | HTTP Status | Meaning | |-------------|----------------------------------------------------------------------| | `200` | Success | | `400` | Bad request — invalid parameters or constraint violation | | `404` | Not found — unknown bridge tx ID, or (for `byTxHash`) not indexed yet | | `500` | Internal server error — retry with exponential back-off | Common causes of a `400` on the build endpoints: - Amount below the minimum or above the maximum for the selected mode (see Phase 1 Step 1) - `tokenID` not valid for the chosen direction (check `directionConfig`) - `destinationAddress` not valid on the destination chain - Insufficient source chain balance to cover amount + fees Show the error message from the response to the user. --- ## Utility Endpoints ### Check Validator Set Change Status ``` GET [API URL]/settings/reactorValidatorChangeStatus ``` Response: `{ "inProgress": true | false }` If `inProgress` is `true`, new bridge requests may experience additional delay. Inform the user but proceed — the transaction will queue automatically. ### Get Bridging Addresses for a Chain ``` GET [API URL]/settings/getBridgingAddresses?chainId= ``` Response: `{ "addresses": ["0x...", "0x..."] }` The bridge contract address is embedded in the transaction built by the API, but this endpoint is useful for verification. ### Get Address Balance ``` GET [API URL]/balance?chain=&address=
[&tokens=] ``` `tokens` is a comma-separated list of token addresses, not `tokenID`s: ERC-20 contract addresses on EVM chains, mint addresses on Solana (omit for all SPL tokens). It is ignored on UTXO chains, which always return all assets. See Phase 1 Step 2. ### Query Transaction History ``` POST [API URL]/bridgeTransaction/filter Content-Type: application/json { "senderAddress": "
", "originChain": "", "page": 0, "perPage": 20 } ``` All fields are optional: `senderAddress`, `originChain`, `destinationChain`, `receiverAddress`, `amountFrom`, `amountTo`, `nativeTokenAmountFrom`, `nativeTokenAmountTo`, `onlyReactor`, `displayStatus` (`success`, `failed`, `pending`, `refunded`, `refunding`), `orderBy`, `order`, `page`, `perPage`. Amount filters are in 18-decimal wei. Filter by at least `senderAddress` to find a user's transactions. `page` is **zero-based**: the first page is `0` (default `0`, max `10000`). `perPage` defaults to `10` (max `100`). Starting at `1` skips the newest transactions. --- ## Quick Reference ``` User: "I want to bridge X [token] from [source] to [destination]." Agent collects (if not provided): senderAddress — user's address on [source] destinationAddress — user's address on [destination] ──── Phase 1: Build ──────────────────────────────────────────────── 1. GET /settings Select mode: route + token in settingsPerMode.reactor → reactor, else in settingsPerMode.skyline → skyline, else list available tokens and ask the user Validate (from the selected mode only): originChain + destinationChain in enabledChains token on the route (directionConfig) amount within min/max (native: minValueToBridge / maxAmountAllowedToBridge; other tokens: minColCoinsAllowedToBridge / maxTokenAmountAllowedToBridge) Extract: tokenID, chainSpecific, decimals (wrapped token = decimals of the wrapped currency) 2. GET /balance?chain=&address=[&tokens=] Stop if the balance can't cover the amount 3. if originChain in {cardano, prime, vector}: POST /transaction/createCardano elif originChain == solana: POST /transaction/createSolana else: POST /transaction/createEth Body: { senderAddress, originChain, destinationChain, destinationAddress, amount (in smallest unit), tokenID } Extract: txRaw | bridgingTx, approvalTx (EVM/Solana), fees Re-check balance ≥ amount + fees + network fee; stop and report the shortfall otherwise ──── Phase 2: User signs ─────────────────────────────────────────── 4. Present BRIDGE TRANSACTION PREVIEW to user: - Fees and network fee in origin native currency; bridged token on its own line - Never add different currencies together - If fees > 10% of amount (same currency only), add warning line - If approvalTx non-null: sign + submit approvalTx first, wait for confirmation, THEN sign bridgingTx - EVM: RAW_TX = { from, to, value, data, gas, maxFeePerGas, maxPriorityFeePerGas, nonce, chainId }; UTXO: unsigned txRaw hex (accept full tx or witness set) Solana: unsigned txRaw base64 Ask user for: the signed transaction only 5. [Wait for user to provide the signed transaction] ──── Phase 3: Broadcast & Confirm ────────────────────────────────── 6. EVM: eth_sendRawTransaction → txHash poll eth_getTransactionReceipt every 5s (max 5 min) 0x1 = success, 0x0 = reverted (stop + report), timeout = unknown (report, don't rebuild) UTXO: Ogmios submitTransaction → txHash = result.transaction.id (no known Ogmios URL: ask the user for one) Solana: sendTransaction (base64) → signature = txHash poll getSignatureStatuses every 1s: err != null → failed; confirmed/finalized + err null → success null → check getBlockHeight vs lastValidBlockHeight; rebuild only once expired AND still unknown ──── Phase 4: Track (no registration call) ───────────────────────── 7. POLL GET /bridgeTransaction/byTxHash/{originChain}/{txHash} every 5s txHash: EVM 0x + lowercase hex | UTXO 64 hex, no 0x | Solana base58 (exact match) 404 → not indexed yet, keep polling (tell the user it's slow after 5 min, stop after 30 min) 200 → Extract: id 8. POLL GET /bridgeTransaction/{id} every 20s, max 30 min first non-terminal status → report BRIDGE TRANSACTION PROGRESS and continue polling. every later non-terminal poll → report `Bridging not finished yet, current status: ` and continue polling. "ExecutedOnDestination" + isRefund=false → report destinationTxHash. Done. "ExecutedOnDestination" + isRefund=true → inform user bridge did NOT execute; funds were refunded. "FailedToExecuteOnDestination" → NOT terminal; keep polling (mention refund underway if isRefund=true). "InvalidRequest" → report error, do not retry. terminal status → report BRIDGE TRANSACTION SUMMARY. ``` --- ## Agent Rules 1. **Never ask for private keys.** When you are handed with private key use it to sign transaction. 2. **Always read settings from the selected mode.** Select reactor or skyline with the rule in Phase 1 Step 1, then read `enabledChains`, `ecosystemTokens`, `directionConfig`, fees and limits from `settingsPerMode.` only. Ignore the top-level copies and the other mode. Never hardcode token IDs. 3. **Always fetch bridging addresses from the API.** Never hardcode them. 4. **Validate the amount** against the selected mode's limits before calling the build endpoint: `minValueToBridge` / `maxAmountAllowedToBridge` for the native currency, `minColCoinsAllowedToBridge` / `maxTokenAmountAllowedToBridge` for other tokens. All values are in the smallest unit. Treat the build endpoint's `400` as the final word and show its reason. 5. **Source chain type selects the endpoint.** `cardano`, `prime`, `vector` → `createCardano`; `solana` → `createSolana`; all EVM chains → `createEth`. 6. **Handle the two-transaction case.** If `approvalTx` is non-null, the user must sign it, and the agent must confirm it on-chain, before the bridge tx. `approvalTx` only says whether an approval is needed — determine the token type from `chainSpecific`, not from `approvalTx`. 7. **Convert amounts correctly.** Native currency: UTXO chains 6 decimals, EVM 18, SOL 9. A wrapped token keeps the decimals of the currency it wraps (e.g. cAP3X = 6). Use integer arithmetic only. 8. **Check the balance before building and again before signing.** Use `GET /balance`. If the sender can't cover the amount + `bridgingFee` + `operationFee` + network fees, stop and report the shortfall per currency. 9. **Broadcast on behalf of the user for every chain type.** EVM via `eth_sendRawTransaction`, UTXO via Ogmios `submitTransaction`, Solana via `sendTransaction`. Then confirm it and track the bridge to completion (Phase 4) — no further input needed from the user. If an RPC or Ogmios URL is not listed, ask the user for it before proceeding. 10. **For UTXO signing: decode, don't guess.** Decode the returned CBOR. A 4-element array is a full transaction; a map is a witness set. For a witness set, merge its vkey witnesses into the original `txRaw`'s witness set, keep the original body bytes, and proceed without further prompting. 11. **Normalise testnet token names.** If the user's token name starts with `t` (e.g. `tAP3X`), strip the prefix and search `ecosystemTokens` by the base name first. 12. **Rebuild expired UTXO transactions.** If submission is rejected because the TTL passed, call `POST /transaction/createCardano` again with the same parameters to get a fresh `txRaw` with a new TTL. 13. **Never rebuild a transaction whose outcome is unknown.** EVM receipt timeout or unresolved Solana status → report it as unknown with the hash and explorer link. Only rebuild once the transaction is proven expired (UTXO TTL rejection, or Solana block height past `lastValidBlockHeight` and the signature still unknown). 14. **Do not retry `InvalidRequest` transactions.** Diagnose and ask the user to start a new request. 15. **Check `isRefund` on `ExecutedOnDestination`.** If `isRefund` is `true`, the bridge did not execute — inform the user their funds have been refunded, not delivered to the destination. Only report `destinationTxHash` as proof of delivery when `isRefund` is `false`. 16. **Always validate the token against the route before building.** If the requested token is not on `directionConfig[originChain].destChain[destinationChain]` in either mode, list the supported tokens and ask the user to choose — do not call the build endpoint with an unsupported token. 17. **Always show the total cost before asking the user to sign.** Show `bridgingFee`, `operationFee` and the estimated network fee in the origin chain's native currency. Show the bridged amount in its own token, and add it to the native total only when it *is* the native currency. Never add different currencies together. Warn when fees exceed 10% of the bridged amount, only when both are in the same currency. 18. **Always populate `gas`, `maxFeePerGas`, `maxPriorityFeePerGas`, `nonce`, and `chainId` in `RAW_TX`.** Fetch these from the RPC before presenting each EVM transaction. Never leave them for the user's signing script to guess. 19. **Fetch nonce fresh for each transaction in the two-tx EVM flow.** After the approval is confirmed, re-fetch `eth_getTransactionCount` for the bridge tx — do not increment the approval nonce manually. 20. **Label `bridgingFee` and `operationFee` with their currency.** These are always in the native currency of the origin chain, never in the bridged token. 21. **Stop polling after 30 minutes.** If no terminal status is reached, inform the user and give them the bridge tx ID to check manually. 22. **Use `BRIDGE TRANSACTION PROGRESS` for the first non-terminal status only.** Its first line is `Bridging not finished yet, current status: `. 23. **Use `BRIDGE TRANSACTION SUMMARY` for terminal statuses only.** Only `ExecutedOnDestination` and `InvalidRequest` are terminal. `FailedToExecuteOnDestination` is **not** terminal — keep polling. Never present the summary while status is still non-terminal. 24. **Always perform continuous tracking.** Poll every 20 seconds and proactively report non-terminal updates until terminal completion (or timeout): full block for the first non-terminal status, then short-line updates at every poll. 25. **Always state active tracking behavior in the first progress block.** Include the line: `Tracking : I am polling every 20 seconds and will send an update after every check.` 26. **After the first progress block, do not repost the full body for non-terminal updates.** Use only: `Bridging not finished yet, current status: ` for every subsequent poll while status is non-terminal. 27. **Always construct and display explorer links in `BRIDGE TRANSACTION SUMMARY`.** Use the Explorer URLs table to build the full transaction URL. For `prime` append `/transactions/`; for `cardano` append `/transaction/`; for `vector` append `/transaction/hash/`; for EVM chains append `/tx/`; for `solana` append `/tx/?cluster=devnet`. Include `Source tx link` for the source transaction and `Dest tx link` for the destination transaction (omit both destination lines if `destinationTxHash` is null). 28. **Confirm Solana transactions by `err` first.** A non-null `err` means failure even at `confirmed`/`finalized`. Success requires `err: null` and `confirmed` or `finalized`. 29. **Solana transactions are base64, not hex.** Present `txRaw` to the user as the base64 string exactly as returned, and expect the signed transaction back in base64. Never wrap a Solana transaction in the `RAW_TX` JS format. 30. **Respect the Solana expiry window.** `lastValidBlockHeight` gives roughly 60–90 seconds. Warn the user up front. Decide expiry by block height, not by elapsed time, and rebuild via `POST /transaction/createSolana` only as described in Phase 3. 31. **Never register, submit or activate a bridge transaction through the API.** No such endpoint exists — the indexers discover the source transaction by themselves. Find it with `GET /bridgeTransaction/byTxHash/{originChain}/{txHash}`; a `404` only means "not indexed yet", never a reason to re-send or rebuild.