Hartii developer docs

QuaiRelay

QuaiRelay

QuaiRelay solves one narrow problem: some in-app wallets can call a contract but cannot make a plain value transfer (on Quai this typically surfaces to the user as could not coalesce error). QUAI sitting in such a wallet is otherwise stuck. QuaiRelay turns a transfer into a contract call — the wallet signs sendTo(recipient) with value attached, and the relay forwards that exact value to recipient in the same transaction.

text
wallet ──sendTo(recipient) + value──▶ QuaiRelay ──same transaction──▶ recipient

Architecture

QuaiRelay is a contract, not a service. There is no backend, no database, no indexer and no owner/operator key. The two front ends that call it are:

  • quai-relay — the standalone page. index.html + app.js + relay-core.js, no build step, no dependencies, served statically (including from IPFS). Connects to the wallet's injected provider, reads balance/gas from the public Quai RPC, and submits one transaction.
  • /blipsend on HartiiLabs (hartii-labs, a separate product — see its own docs). A production page aimed at Blippay in-app-wallet users, offering the same plain-transfer-first / contract-fallback pattern, wired to the same relay contract. It is documented here only because it is QuaiRelay's main real-world caller — see How /blipsend uses the relay below.

Data flow for a relay send:

  1. The page reads the connected wallet's balance and the current gas price directly from https://rpc.quai.network/cyprus1 (quai_getBalance, quai_gasPrice).
  2. It validates the destination client-side (Cyprus-1 prefix, not a Qi-ledger address, checksum, not self, not the relay), takes the gas limit from a live quai_estimateGas of a send to a never-used recipient plus 20% (130,000 if the node does not answer), and computes the maximum sendable amount (balance minus that gas cost with 25% headroom).
  3. The wallet signs a transaction: to = the relay contract, value = the amount, data = ABI-encoded sendTo(recipient).
  4. The relay contract forwards msg.value to recipient in the same transaction, or reverts the whole thing — there is no partial-failure state.

Live contract

NetworkAddress
Quai mainnet, Cyprus-1 (chain 9) — v2, the only one to use0x003D2EC4C1Ff5e98898fEa6C6D1AAaA48FaDc11A
Retired v1 — no new sends0x0031B09e63592B713768761cdDACC603974c10bD

Verified live: quai_getCode against the v2 address on https://rpc.quai.network/cyprus1 returns 674 bytes of runtime bytecode, identical to the build. Deployed 2026-11-02, solc 0.8.20, EVM paris, after a rehearsal on Orchard.

Why v2. v1 (deployed 2026-09-19) checked the zone but not the ledger. A Qi-ledger address also starts 0x00, and on Quai a value call to one runs out of gas instead of reverting, so a send to it burned the payer's whole gas limit (nothing else was lost: the transaction reverted). v2 refuses such a recipient by name ("Recipient is a Qi address"), so the gas estimate fails with a reason before the wallet opens. Every Hartii page now sends through v2 only. HPAY's history and receipt tools still read v1's Relayed events, so payments made through it keep verifying.

Source. The contract, the standalone page and their tests are open source: github.com/IboTheSecond/quai-relay (MIT).

See contracts for the full interface and trust model, and integrate for runnable examples.

How /blipsend uses the relay

HartiiLabs' /blipsend page (hartii-labs/src/pages/BlipSend.jsx) offers two ways to move QUAI out of a stuck wallet, both to the same address the visitor typed:

  1. Plain transfer — an ordinary signer.sendTransaction({ to, value }). Works for any wallet that can send.
  2. Contract route (QuaiRelay) — for wallets that can't: contract.sendTo(destination, { value }) against the relay address above, using the ABI in src/abi/quaiRelay.js.

The page's own comment explains why it uses the general-purpose relay rather than a fixed-destination forwarder contract (hartii-labs also has one, QuaiForwarder, used for a single private payee elsewhere on the site):

In other words: sendTo's lack of a fixed destination isn't a limitation, it's the reason this contract is safe to expose on a public page at all.

PayLink — the shareable-payment-link feature on Gallery and Biome (where it is now called HPAY) — is the relay's other real-world caller, via the same sendTo(recipient) pattern /blipsend uses: a plain native transfer for wallets that support one (Pelagus), QuaiRelay for wallets that can't (Blip, and anything unrecognized). The shared, byte-identical payLinkPay module (Gallery's src/utils/paylink/payLinkPay.js, Biome's apps/biome/public/payLinkPay.mjs) picks the route with shouldUseRelay(kind) from detectWalletEnvironment, never guessing mid-payment.

Every PayLink payment carries an explicit gas limit and the live gas price, fixed 2026-09-30 after a funding bug: a transaction with no gas fields lets the wallet reserve its own default, and Blip's "Fund & continue" sheet asked a payer for a 75 QUAI "preparation reserve" on a 10 QUAI payment (roughly 1.3M gas at the live ~56k gwei, when the real cost is far smaller). Measured on mainnet: quai_estimateGas for QuaiRelay.sendTo to a never-used recipient is about 98,000 gas (98,233 on v2, 2026-11-02), to a recipient that already exists about 44,000; mined sends have used 32,722 to 73,616. A plain value transfer is 55,236.

estimatePaymentGas() fixes this:

  • Calls quai_estimateGas without a from — the estimate has to work while the payer's wallet is still unfunded, which is exactly the moment the top-up sheet appears, and a from on an empty wallet gets rejected with "insufficient funds."
  • Adds 20% headroom to the estimate, and reads the live quai_gasPrice alongside it.
  • Falls back to measured constants (130,000 gas for the relay path, 70,000 for a plain transfer) if the node is unreachable, so the payment still goes out on the first tap.

The Pay page shows the same number back to the payer as a "Network fee ≈ X QUAI" line under the Pay button, rounded up — so the funding sheet the wallet shows and the fee line the page shows agree, instead of the wallet inventing its own much larger number.

Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.