Documentation Index
Fetch the complete documentation index at: https://docs.algovoi.co.uk/llms.txt
Use this file to discover all available pages before exploring further.
The AlgoVoi Facilitator Service is a stateless on-chain transaction verifier. It occupies a single well-defined position in the platform trust boundary: given a transaction identifier, a chain, and a set of expected settlement parameters, it returns a deterministic verdict on whether that transaction settled as expected. It holds no tenant state, no checkout state, and no business logic. All compliance, audit, and delivery work happens upstream in the gateway.
The facilitator runs in an active-passive pair and is the only service in the platform that holds RPC credentials for external chain infrastructure.
Overview
| Property | Value |
|---|
| Role | Stateless on-chain transaction verifier |
| Chains supported | Algorand, VOI, Hedera, Stellar, Base, Solana, Tempo |
| State stored | None — fully stateless |
| Tenant awareness | None |
The facilitator is purpose-built for a single call pattern: the gateway sends a verification request and expects back either a settled verdict with chain-extracted facts or a rejection with a reason code. Nothing in the facilitator touches the payment database, the tenant table, the audit chain, or any webhook queue.
Active-passive failover
Two facilitator instances run in parallel, serving identical verification logic from the same codebase with the same RPC credentials. The gateway detects a primary failure (timeout, 5xx, or connection refused) and transparently retries the same request against the failover instance. Because both instances share the same underlying infrastructure, the fallback adds negligible latency.
No state replication. The two facilitator instances hold no state. A request that hits the primary and a retry that hits the failover are independently executed against the same chain; both return the same result because they read from the same blockchain.
Verification flow
A verification request proceeds through five stages inside the facilitator.
Stage 1 — Request ingestion.
The facilitator receives a structured request from the gateway. All fields are validated against the per-chain schema for the declared chain. Unknown chain values, malformed transaction ID formats, or missing required fields are rejected immediately with a 400 before any RPC call is made.
Stage 2 — Chain client selection.
The facilitator selects the appropriate chain client based on the chain field. Each chain has a dedicated client module that encapsulates the connection pool, authentication, retry logic, and response parsing specific to that chain’s RPC or REST interface.
Stage 3 — On-chain query.
The selected chain client queries the relevant RPC endpoint or indexer for the transaction identified by tx_id. The query strategy differs by chain — some chains use their native RPC, others use REST indexers or mirror nodes. The client applies exponential backoff on transient failures and distinguishes between “transaction not found” (which may be a timing issue) and “transaction found but does not match” (which is a definitive failure).
Stage 4 — Field extraction and comparison.
The raw chain response is parsed to extract: actual_amount, actual_asset, actual_sender, actual_receiver, block_height, and finality_status. Each extracted value is compared against the corresponding expected value from the request. The comparison is type-aware: amounts are compared as bigints in microunits; asset identifiers are normalised before comparison; addresses are normalised to canonical form for the relevant chain.
Stage 5 — Response construction.
The facilitator builds a structured response object carrying the extracted chain facts and the settled boolean. It does not add any gateway-level metadata (audit chain position, webhook status, compliance outcome). That enrichment happens exclusively in the gateway after the facilitator response is received.
Verification request schema
Sent by the gateway to the facilitator over the internal VPC channel.
| Field | Type | Required | Description |
|---|
| tx_id | string | Yes | Chain-native transaction identifier |
| chain | enum | Yes | One of: algorand, voi, hedera, stellar, base, solana, tempo |
| asset_id | string | Yes | CAIP-19 asset identifier for the expected payment asset |
| expected_amount_microunits | bigint | Yes | Expected settlement amount in millionths of the asset unit |
| expected_receiver | string | Yes | Merchant wallet address that should have received the payment |
| network_id | enum | No | mainnet (default) or testnet |
| sender_hint | string | No | Declared payer address; used as indexer query hint, not for verification |
| timeout_ms | integer | No | Maximum time to wait for RPC response; defaults to 8000ms |
Verification response schema
Returned by the facilitator to the gateway.
| Field | Type | Description |
|---|
| settled | boolean | True if the transaction was found, confirmed, and all values matched expectations |
| amount_microunits | bigint | Actual settled amount extracted from the chain, in microunits |
| asset_id | string | CAIP-19 asset identifier as extracted from the chain |
| sender_address | string | Payer wallet address as extracted from the chain |
| receiver_address | string | Receiver wallet address as confirmed on-chain |
| block_height | integer | Block or slot height at which the transaction was included |
| finality_status | enum | confirmed or pending_finality |
| verified_at | string | ISO-8601 timestamp of the facilitator-side verification |
| chain | enum | Echo of the input chain field |
| tx_id | string | Echo of the input transaction ID |
| rejection_reason | enum | Present only when settled is false; see rejection reason table |
Rejection reasons
| rejection_reason | Meaning |
|---|
| tx_not_found | Transaction ID not found on the chain at query time |
| amount_mismatch | Transaction found but settled amount does not match expected |
| asset_mismatch | Transaction found but asset does not match expected |
| receiver_mismatch | Transaction found but receiver does not match expected |
| finality_pending | Transaction found but has not yet reached required confirmation depth |
| rpc_error | Chain RPC returned an error or timed out |
| chain_unsupported | Declared chain is not supported by this facilitator instance |
| tx_malformed | Transaction ID format is invalid for the declared chain |
Per-chain verification approach
Each chain requires a distinct verification strategy because the data models, query interfaces, and finality semantics differ substantially across the seven supported chains.
| Chain | Query method | Address format | Asset representation | Finality criteria |
|---|
| Algorand | Algod + Indexer RPC | Base32 with checksum | ASA ID (integer) | 1 confirmed round |
| VOI | Algod-compatible RPC | Base32 with checksum | ARC-200 contract address | 1 confirmed round |
| Hedera | Mirror node REST API | 0.0.{num} format | HTS token ID | Mirror node inclusion |
| Stellar | Horizon REST API | Strkey (G…) | Asset code + issuer | Horizon ledger close |
| Base | Alchemy EVM JSON-RPC | EIP-55 checksummed hex | ERC-20 contract address | 6 block confirmations |
| Solana | RPC + token program | Base58 pubkey | SPL mint address | 32 slot confirmations |
| Tempo | EVM-compatible JSON-RPC | EIP-55 checksummed hex | ERC-20 contract address | 6 block confirmations |
Algorand
Algorand transaction verification uses the Algod REST API for recent transactions and the Indexer REST API for historical lookups. The facilitator looks for an axfer (asset transfer) transaction matching the declared ASA ID. For native ALGO payments, it looks for a pay transaction. The note field is extracted and decoded if present, providing the memo value for verification. Transaction finality is established on the first confirmed round — Algorand produces one block per approximately 3.9 seconds with immediate finality.
VOI
VOI uses an Algod-compatible API, making the verification path structurally identical to Algorand. The primary difference is in asset representation: VOI uses ARC-200, a smart-contract token standard, meaning asset transfers appear as application call transactions rather than axfer transactions. The facilitator inspects inner transactions and application state deltas to confirm the ARC-200 transfer. AVM compatibility means the same indexer query patterns apply.
Hedera
Hedera transaction verification queries the Hedera Mirror Node REST API. HTS (Hedera Token Service) transfers are returned as structured transfer records with explicit sender, receiver, token ID, and amount fields. Topic memo extraction is used for payment reference correlation. The mirror node does not have a configurable confirmation-depth concept; inclusion in the mirror node response is treated as the finality signal.
Stellar
Stellar transaction verification uses the Horizon REST API. The facilitator looks for a payment or path-payment operation with the expected asset code and issuer address. The canonical Stellar USDC issuer is GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVV. The memo hash field is extracted for payment reference correlation. Horizon ledger close is treated as finality — Stellar produces one ledger per approximately 5 seconds.
Base
Base transaction verification uses the Alchemy EVM JSON-RPC endpoint. The facilitator fetches the transaction receipt and scans the logs for an ERC-20 Transfer(address indexed from, address indexed to, uint256 value) event matching the expected USDC contract address. Amount comparison uses bigint arithmetic on the raw uint256 value. Six block confirmations are required before finality_status is set to confirmed; below that threshold the response carries finality_status: pending_finality.
Solana
Solana transaction verification uses the Solana JSON-RPC endpoint. The facilitator fetches the transaction by signature and parses the token program instructions to find an SPL token transfer matching the expected mint address. The memo program instruction is parsed separately to extract the payment reference. Thirty-two slot confirmations are required for finality_status: confirmed, matching Solana’s practical finality threshold for USDC transfers.
Tempo
Tempo uses an EVM-compatible RPC interface, making its verification path structurally identical to Base. The settlement asset is USDCe (an ERC-20 token). The facilitator applies the same log-scanning approach, the same six-block confirmation threshold, and the same bigint amount comparison. The only configuration difference is the chain ID and RPC endpoint.
Address normalisation
Address comparison is performed against normalised forms to avoid false negatives caused by checksum variants or encoding differences.
| Chain | Normalisation applied |
|---|
| Algorand | Base32 decoded and re-encoded; trailing checksum verified |
| VOI | Same as Algorand |
| Hedera | Entity ID parsed to {shard}.{realm}.{num} canonical form |
| Stellar | Strkey decoded and re-encoded to strip variant encodings |
| Base | toLowerCase() applied after EIP-55 checksum stripping |
| Solana | Base58 decoded and re-encoded |
| Tempo | Same as Base |
Amount representation
All amounts in the verification protocol are expressed in microunits — millionths of the base asset unit. This convention is applied uniformly across all chains regardless of the native on-chain decimal convention.
| Chain | Asset | On-chain decimals | Microunit scale |
|---|
| Algorand | USDC | 6 | 1:1 with on-chain value |
| VOI | USDC | 6 | 1:1 with on-chain value |
| Hedera | USDC | 6 | 1:1 with on-chain value |
| Stellar | USDC | 7 | Divide by 10 to get microunits |
| Base | USDC | 6 | 1:1 with on-chain value |
| Solana | USDC | 6 | 1:1 with on-chain value |
| Tempo | USDCe | 6 | 1:1 with on-chain value |
The gateway always submits expected_amount_microunits as a microunit value; the facilitator’s per-chain client performs any necessary scaling before comparison.
Finality handling
Finality semantics vary by chain and are handled explicitly rather than treating all transactions as immediately final.
finality_status: confirmed means the transaction has met the chain-specific confirmation threshold and is treated as irreversible under normal network conditions.
finality_status: pending_finality means the transaction exists on-chain and all field values match, but the confirmation depth has not yet been reached. The facilitator returns settled: false with rejection_reason: finality_pending in this case, not settled: true with finality_status: pending_finality — the gateway handles pending payments by re-queuing and re-polling.
The gateway queues pending-finality payments and re-polls the facilitator at a configurable interval until either the confirmation threshold is reached or the checkout expires. During this window the checkout status remains open from the merchant’s perspective.
Native-asset price oracle interaction. If a payment is made in a native asset (ALGO, VOI, etc.) rather than a stablecoin, the gateway must convert the verified microunit amount to USD cents using the price oracle before marking the checkout as paid. If the price oracle is unavailable at the moment of facilitator response, the payment is queued and the USD conversion is performed when the oracle recovers. This queuing does not affect the facilitator — the verification verdict is already stored and the facilitator is not re-queried for the oracle-pending case.
Trust boundary
The facilitator’s position in the trust hierarchy is deliberately narrow.
What the facilitator knows: transaction IDs, chain identifiers, asset identifiers, expected amounts, expected receivers. It queries external chain infrastructure and returns extracted facts.
What the facilitator does not know: tenant identities, checkout state, payment link metadata, mandate details, API keys, webhook endpoints, KYC status, trial balances.
What the facilitator cannot do: write to the payment database, trigger webhooks, update checkout status, insert into the audit chain, modify any gateway state. The facilitator is read-only against external chain infrastructure and returns a response. All state mutations happen in the gateway.
This separation means that a compromise of the facilitator exposes RPC credentials and the ability to query chain state, but not tenant data, payment history, API keys, or audit records. The gateway trusts the facilitator’s response but does not give the facilitator any write capability over platform state.
Operational characteristics
Stateless restart safety. Because no state is stored, facilitator processes can be restarted, replaced, or scaled without coordination. There are no pending in-memory queues to drain before restart. A request in flight at restart time will time out at the gateway; the gateway’s failover logic routes the retry to the standby instance.
Healthcheck endpoint. Both facilitator instances expose GET /health. The response carries a status field (ok or degraded) and per-chain RPC reachability flags. The gateway polls this endpoint to detect degraded state proactively.
No logging of tenant data. Facilitator access logs record transaction IDs, chain identifiers, and verdict outcomes. They do not record expected receiver addresses, API keys, tenant IDs, or any fields that would constitute personal or commercially sensitive data. Log retention is 30 days rolling.