relockeql
v5.3.1
Published
A GraphQL implementation for interacting with Relocke based blockchains.
Maintainers
Readme
RelockeQL
RelockeQL is a GraphQL client and server library that allows developers to interact with Relocke-based blockchains (EOSIO, WAX, Telos, XPR Network, Vaulta, and WIRE Network) using GraphQL. It provides a unified interface to communicate with different chains in the ecosystem, enabling developers and agentic tools to leverage the unique features and capabilities of each blockchain while still benefiting from a consistent development experience.
As a GraphQL client library, RelockeQL simplifies the process of building and executing GraphQL queries and mutations, handling errors, and signing transactions. As a server library, it provides a framework for building GraphQL APIs that can interact with Relocke-based blockchains and other data sources.
With RelockeQL, developers can focus on building the frontend and business logic of their DApps, while relying on the library to handle the complexities of interacting with multiple blockchains in the ecosystem.
Live working example can be found here.

Supported chains
RelockeQL supports the following chains:
- Jungle (
jungle) - WAX (
wax) - Vaulta (EOS) (
vaulta) - Telos (
telos) - XPR Network (
xpr) - WIRE Network (
wire)
This list is not an allowlist. Applications may configure any other compatible chain using their own GraphQL-safe chain name and explicit RPC and, where required, Hyperion endpoints.
Installation
For Node.js, to install RelockeQL and the peer dependency graphql run:
npm install relockeql graphqlExamples
See the examples folder on how to run RelockeQL as a Node.js endpoint.
Configure endpoints
RelockeQL does not choose network providers. Every RPC endpoint must be supplied by the application in the required chains option:
const endpoints = {
vaulta: "https://your-vaulta-rpc.example",
wax: "https://your-wax-rpc.example",
wire: "https://your-wire-rpc.example"
};Hyperion history endpoints are configured separately by prefixing the chain name with hyperion_. They do not create additional top-level GraphQL fields:
const endpoints = {
vaulta: "https://your-vaulta-rpc.example",
hyperion_vaulta: "https://your-vaulta-hyperion.example"
};This explicit configuration prevents the package from silently sending requests to infrastructure that the application owner did not select.
Run the example HTTP server
The example server accepts the complete multi-chain endpoint map as JSON. Every configured RPC chain is exposed by the same GraphQL server, while matching hyperion_<chain> entries enable that chain's history queries. It does not contain fallback providers:
Copy the environment template. It contains the tested ReLocke RPC providers and the EOSphere Vaulta Hyperion provider; you may replace or extend the map with providers you select:
cp .env.example .envThe .env file is ignored by Git. Once configured, start the server normally:
npm run serverYou can alternatively provide the same configuration inline:
RELOCKEQL_CHAINS='{
"vaulta": "https://eos.relocke.io",
"hyperion_vaulta": "https://vaulta-hyperion.eosphere.io",
"telos": "https://telos.relocke.io",
"xpr": "https://proton.relocke.io",
"wax": "https://wax.relocke.io",
"jungle": "https://jungle.relocke.io"
}' \
RELOCKEQL_CONTRACTS='{
"vaulta": ["eosio.token", "eosio"],
"telos": ["eosio.token"],
"xpr": ["eosio.token"],
"wax": ["eosio.token"],
"jungle": ["eosio.token"]
}' \
npm run serverRELOCKEQL_CHAINS is required and may contain any number of tested or custom GraphQL-safe chain names. The server loads .env automatically when it exists, but still refuses to start without an explicit chain map. RELOCKEQL_CONTRACTS is an optional JSON map of chain names to contract account arrays. PORT defaults to 3002. Send GraphQL requests as JSON over HTTP POST; browser GET requests return 405 without stopping the server.
Serialize a contract ABI
serialize_abi converts an EOSIO-compatible ABI JSON document into the hexadecimal bytes required by eosio::setabi. Its self-contained encoder supports UTF-8 Ricardian text and ordered ABI 1.1/1.2 binary extensions without requiring an ABI serialization dependency.
import { serialize_abi } from "relockeql";
const rawAbi = serialize_abi({
version: "eosio::abi/1.2",
types: [],
structs: [],
actions: [],
tables: [],
ricardian_clauses: [
{ id: "ui.contract", body: "---\nschema: relocke.ui/1\n---\nPurpose" }
],
error_messages: [],
abi_extensions: []
});Trailing binary-extension fields remain absent unless supplied. If action_results is present, variants must also be present because ABI binary extensions cannot skip an earlier field and then encode a later one.
Query a Blockchain Account Info
import { RelockeQL } from "relockeql";
import { sign_transaction } from "your-signing-library";
const query = /* GraphQL */ `
{
vaulta {
get_blockchain {
get_account(account_name: "relockeblock") {
core_liquid_balance
ram_quota
net_weight
cpu_weight
ram_usage
}
}
}
}
`;
const { data } = await RelockeQL(
{ query },
{ chains: { vaulta: "https://your-vaulta-rpc.example" } }
);
console.log(data);Logged output included an account infomation.
Query smart contract code and hashes
get_smart_contract resolves only the selected contract fields. For example, this query retrieves both deployment hashes with one raw-ABI RPC request and does not download the contract WASM:
const query = /* GraphQL */ `
{
vaulta {
get_blockchain {
get_smart_contract(account_name: "rloc") {
abi_hash
wasm_hash
}
}
}
}
`;
const { data } = await RelockeQL(
{ query },
{ chains: { vaulta: "https://your-vaulta-rpc.example" } }
);The same field can select wasm and abi as base64-encoded content. A wasm_hash-only selection uses the dedicated code-hash endpoint, while compatible selections share in-flight RPC requests within the GraphQL operation.
Query a Contract Table
Contract table fields return the table rows together with the blockchain pagination metadata.
const query = /* GraphQL */ `
{
vaulta {
eosio {
powup_order(arg: { scope: "" }) {
rows {
id
owner
cpu_weight
net_weight
}
more
next_key
}
}
}
}
`;
const { data } = await RelockeQL(
{ query },
{
chains: { vaulta: "https://your-vaulta-rpc.example" },
contracts: {
vaulta: ["eosio"]
}
}
);
const page = data.vaulta.eosio.powup_order;
if (page.more) {
console.log("Next lower bound:", page.next_key);
}Query a transaction through Hyperion
get_transaction_by_id returns a normalized transaction and all of its actions. The action data field is decoded JSON, so callers do not need to parse a JSON string.
const query = /* GraphQL */ `
{
vaulta {
get_blockchain {
get_transaction_by_id(
id: "690878b888dd70c339df268ae68019c097b1d4ded649a50c9b5723f734d84837"
) {
transaction_id
block_num
timestamp
executed
actions {
contract
action
actors
receivers
data
}
}
}
}
}
`;
const result = await RelockeQL(
{ query },
{
chains: {
vaulta: "https://your-vaulta-rpc.example",
hyperion_vaulta: "https://your-vaulta-hyperion.example"
}
}
);A Hyperion executed: false not-found response resolves to null. Missing, unavailable, timed-out, or malformed providers return GraphQL errors with specific extensions.code values.
List specific contract actions
get_actions requires a notified account, contract, and action name, which Hyperion combines as indexed account and contract:action filters. Omit both time boundaries to retrieve the latest matching actions. When after is supplied, the search window may span at most seven days. Searches request newest-first results, disable total-result counting, omit large binary data, default to 10 results, and return at most 25. They do not assume that a provider maintains Hyperion's optional hot-index alias. Offset, ascending scans, and arbitrary indexed-field filters are intentionally unavailable.
The optional after and before boundaries use the iso8601_datetime GraphQL scalar rather than unrestricted strings. It accepts YYYY-MM-DDTHH:mm:ss, optional fractional seconds, and an optional Z or ±HH:mm offset. For latest-action pagination, omit both boundaries on the first request and pass the oldest returned action timestamp as before on the next request. For explicit date-window pagination, keep after fixed and move before backward inside the seven-day window.
const latestQuery = /* GraphQL */ `
{
vaulta {
get_blockchain {
get_actions(
account: "alice"
contract: "eosio.token"
action: "transfer"
limit: 10
) {
actions {
transaction_id
block_num
timestamp
data
}
}
}
}
}
`;const query = /* GraphQL */ `
{
vaulta {
get_blockchain {
get_actions(
account: "alice"
contract: "eosio.token"
action: "transfer"
after: "2026-08-08T10:00:00Z"
before: "2026-08-15T10:00:00Z"
limit: 10
) {
query_time_ms
actions {
transaction_id
block_num
timestamp
data
}
}
}
}
}
`;
const result = await RelockeQL(
{ query },
{
chains: {
vaulta: "https://your-vaulta-rpc.example",
hyperion_vaulta: "https://your-vaulta-hyperion.example"
}
}
);Hyperion resource policy for block producers
ReLockeQL history queries are read-only HTTP requests. They do not submit blockchain transactions or consume on-chain CPU, NET, or RAM. They do consume the selected provider's API capacity, outbound bandwidth, and Hyperion/Elasticsearch CPU and I/O.
get_actions is intentionally narrower than the underlying Hyperion endpoint. A valid request produces one upstream request shaped like this:
GET /v2/history/get_actions
?account=<notified-account>
&filter=<contract>:<action>
&after=<optional-ISO-8601-lower-bound>
&before=<optional-ISO-8601-upper-bound>
&limit=<1-25>
&sort=desc
&track=false
&noBinary=trueThe library enforces the following provider protections before sending that request:
- Notified account, contract, and action are required.
- Omitting both time boundaries retrieves the latest matching actions.
- When a lower time boundary is supplied, a request may cover no more than seven days.
- The default result limit is 10 and the hard maximum is 25.
track=falseprevents total-result counting.noBinary=trueexcludes large binary action payloads.- Only newest-first ordering is available; ascending scans are not exposed.
- Offset pagination, arbitrary indexed-field filters, and unbounded result counts are not exposed.
- The selected Hyperion provider receives one request with an eight-second timeout and no fallback request to another producer.
- Returned actions are rejected unless they still match the requested notified account, contract, and action.
get_transaction_by_id makes one exact transaction-ID lookup against the selected provider. It does not perform a broad action search or fall back to another endpoint.
These controls bound the cost of an individual query; they cannot limit how many queries a public deployment receives. API operators should additionally apply per-user or per-credential rate limits, concurrency limits, short-lived deduplication for identical reads, and provider monitoring. Hyperion operators remain free to enforce stricter reverse-proxy limits and Elasticsearch query timeouts.
Cache transaction bodies and refresh headers
serialize_transaction only performs the work required by its selected output fields. A body-only selection serializes the actions without requesting current chain or reference-block data, so applications can cache the deterministic body while the actions, authorization, order, and contract ABI remain unchanged:
mutation CacheTransactionBody {
wax {
serialize_transaction(actions: [...]) {
transaction_body
}
}
}When the cached body needs a fresh expiration and reference block, submit the same action input but select only the live values. ReLockeQL skips action serialization for this request:
mutation RefreshTransactionHeader {
wax {
serialize_transaction(actions: [...]) {
chain_id
transaction_header
}
}
}Combine the refreshed chain_id and transaction_header with the cached transaction_body, then calculate a new signing hash. Selecting hash or non-empty required_keys automatically builds all of their required transaction components. ReLockeQL does not persist transaction bodies across GraphQL operations; applications control the cache lifetime and must invalidate entries when any serialized input or the contract ABI changes.
Transfer Tokens
import { RelockeQL } from "relockeql";
const query = /* GraphQL */ `
mutation {
jungle {
send_transaction(
actions: [
{
eosio_token: {
transfer: {
authorization: { actor: "relockeblock" }
to: "relockechain"
from: "relockeblock"
memo: ""
quantity: "0.0002 EOS"
}
}
}
]
) {
transaction_id
block_num
}
}
}
`;
const { data } = await RelockeQL(
{ query },
{
chains: { jungle: "https://your-jungle-rpc.example" },
contracts: {
// List your smart contract accounts for each chain.
jungle: ["eosio.token"]
},
signTransaction: async (hash) => {
const wif_private_key = "PVT_K1_…"; // your private key
const signature = await sign_transaction({ hash, wif_private_key });
return [signature]; // signatures must return array
}
}
);
console.log(data);Logged output includes transaction_id and block_num
Requirements
Supported runtime environments:
- Node.js versions
>=22.0.0.
