Hartii developer docs

QuaiRelay

Integrate

QuaiRelay has no HTTP API — every integration talks straight to the contract and to the public Quai RPC. All examples use quais, checksum every address before it reaches the RPC, and treat an unset gas price/balance as unknown rather than zero (the same rules the relay's own front ends follow).

Send QUAI through the relay

js
import { quais } from 'quais';

const RPC_URL = 'https://rpc.quai.network/cyprus1';
const RELAY_ADDRESS = '0x0031B09e63592B713768761cdDACC603974c10bD';
const RELAY_ABI = ['function sendTo(address payable to) external payable'];

// quais.getAddress() checksums — Quai's RPC rejects a lowercase address outright.
async function sendThroughRelay(signer, destination, valueWei) {
  const to = quais.getAddress(String(destination).toLowerCase());
  if (!to.startsWith('0x00')) {
    throw new Error('destination is not a Cyprus-1 address — QUAI sent there is unreachable');
  }

  const relay = new quais.Contract(RELAY_ADDRESS, RELAY_ABI, signer);
  const tx = await relay.sendTo(to, { value: valueWei });
  await tx.wait();
  return tx.hash;
}

Read balance + gas price to offer "send everything"

js
import { quais } from 'quais';

const provider = new quais.JsonRpcProvider('https://rpc.quai.network/cyprus1', undefined, {
  usePathing: false, // RPC_URL already ends in /cyprus1 — do not let the SDK append it again
});

async function maxSendable(accountAddress) {
  const address = quais.getAddress(String(accountAddress).toLowerCase());
  const [balance, feeData] = await Promise.all([
    provider.getBalance(address),
    provider.getFeeData(quais.Zone.Cyprus1), // getFeeData needs the zone on Quai
  ]);

  const gasLimit = 60_000n; // a sendTo call; re-estimated by the wallet when it signs
  const gasPrice = BigInt(feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n);
  const gasCost = (gasLimit * gasPrice * 125n) / 100n; // 25% headroom

  if (gasCost >= balance) return 0n; // balance can't cover its own fee
  return balance - gasCost;
}

Raw JSON-RPC (no wallet SDK)

Useful for a script that just wants to read the relay's bytecode or watch its events — no signing involved.

http
POST https://rpc.quai.network/cyprus1
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"quai_getCode","params":["0x0031B09e63592B713768761cdDACC603974c10bD","latest"]}

Returns the deployed runtime bytecode (non-0x response = the contract exists at that address).

http
POST https://rpc.quai.network/cyprus1
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"quai_getBalance","params":["0x<your-checksummed-address>","latest"]}

Calldata by hand

If you need to build the transaction yourself instead of using quais.Contract:

js
// selector for sendTo(address), keccak-256("sendTo(address)")[0..4]
const SELECTOR = '0xe6d25245';

function encodeSendTo(toChecksummed) {
  const body = toChecksummed.slice(2).toLowerCase().padStart(64, '0');
  return SELECTOR + body;
}

const tx = {
  from: accountAddress,
  to: '0x0031B09e63592B713768761cdDACC603974c10bD',
  value: '0x' + valueWei.toString(16),
  data: encodeSendTo(destinationChecksummed),
  gas: '0x' + (60_000n).toString(16),
};
// wallet.request({ method: 'quai_sendTransaction', params: [tx] })
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.