@hazbase/relayer is the relayer SDK that provides gasless execution (meta-transactions) for the hazBase stack.
It is designed to be used together with @hazbase/kit’s ContractBuilder: build calldata & gas estimates with the builder, then sign and relay the request with this package.
- Recommended composition:
ContractBuilder(call assembly) +@hazbase/relayer(EIP‑712 signing & relay) +@hazbase/auth(JWT/client‑key/audit logging) - Typical use cases: frictionless first‑time UX, sponsored transactions for KYC‑verified users, better mobile UX
- Security: one‑time Auth Code +
ForwardRequest.noncefor replay protection, strict EIP‑712 domain separation
- Build calldata & (optionally) gas with ContractBuilder (
encode,estimate) - Build ForwardRequest (
buildForwardRequest) - Produce EIP‑712 signature (
signForwardRequest) - Obtain one‑time Auth Code (
generateRelayerAuthCode) - Send to relayer (
sendRelayerTransaction) → returns tx hash
Prefer
forwardCallto do all steps in one go (ABI/method/args → relay). When you also useContractBuilder, you can pre‑compute data/gas more precisely.
- Node.js >= 18 (ESM recommended)
- TypeScript >= 5
ethersv6@hazbase/auth(API endpoint & client key configuration, audit logging)@hazbase/kit(ContractBuilder)
npm i @hazbase/relayer @hazbase/kit @hazbase/auth ethers
# or
pnpm add @hazbase/relayer @hazbase/kit @hazbase/auth ethers// All comments in English per project rule
import { setClientKey } from '@hazbase/auth';
setClientKey(process.env.HAZBASE_CLIENT_KEY!); // required for validation & logging// Use ContractBuilder to centralize address/abi, then relay with forwardCall
import { ethers } from 'ethers';
import { signInWithWallet } from '@hazbase/auth';
import { ContractBuilder } from '@hazbase/kit';
import { forwardCall } from '@hazbase/relayer';
import erc20Abi from './abi/MyToken.json' assert { type: 'json' };
const CHAIN_ID = "11155111"; // repolia testnet
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
/** 1) Build a contract helper (address/abi binding) */
const tokenAddress = '0xToken...';
const signer = await provider.getSigner();
const { accessToken } = await signInWithWallet({ signer });
const contract = ContractBuilder
.create({ address: tokenAddress, abi: erc20Abi, chainId: CHAIN_ID, signer })
.withRelayer({ accessToken })
.build();
const decimals = await contract.decimals();
const amountWei = ethers.parseUnits(amount.toString(), decimals);
await contract.transfer(toAddress, amountWei);// Use ContractBuilder to encode calldata and estimate gas, then sign & relay
import { ethers } from 'ethers';
import { ContractBuilder } from '@hazbase/kit';
import {
buildForwardRequest, makeDomain, signForwardRequest,
generateRelayerAuthCode, sendRelayerTransaction
} from '@hazbase/relayer';
import erc20Abi from './abi/MyToken.json' assert { type: 'json' };
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const chainId = Number((await provider.getNetwork()).chainId);
/** 1) Prepare builder */
const tokenAddress = '0xToken...';
const builder = ContractBuilder
.create({ address: tokenAddress, abi: erc20Abi, chainId: CHAIN_ID, signer })
.build();
/** 2) Encode calldata via builder */
const to = '0xRecipient...';
const amount = 1_000n;
const data = builder.encode('transfer', [to, amount]); // or builder.interface.encodeFunctionData(...)
/** 3) Build ForwardRequest */
const req = await buildForwardRequest({
signer,
tokenAddress, // maps to request.to
chainId
});
/** 4) (Optional) refine gas with builder */
const gasEst = await builder.estimate('transfer', [to, amount]); // typical helper name
req.gas = BigInt(Math.ceil(Number(gasEst) * 1.2)); // add safety margin
/** 5) Inject calldata and sign typed data */
req.data = data;
const domain = makeDomain(chainId);
const signature = await signForwardRequest({ signer, domain, request: req });
/** 6) Obtain one-time Auth Code and send */
const code = await generateRelayerAuthCode({
accessToken: 'JWT-from-signInWithWallet',
chainId, contractAddress: tokenAddress
});
const txHash = await sendRelayerTransaction({ chainId, code, request: req, signature });
console.log('relayed tx =', txHash);The exact API names on
ContractBuilder(e.g.,encode,estimate) can vary by version. Please consult the type definitions (.d.ts) or your editor’s IntelliSense.
Purpose: from ABI/method/args, do ForwardRequest build → sign → get Auth Code → send, and return the tx hash.
Params
signer: ethers.JsonRpcSignerchainId: numberaccessToken: string— JWT obtained from@hazbase/auth.signInWithWalletcontractAddress: stringabi: ethers.InterfaceAbimethod: stringargs: unknown[]
Returns: string — transaction hash (relayer response)
Purpose: fetch a one‑time code required to execute via relayer.
Details
- Internally calls
ensureClientKeyActive(70)→POST /api/app/code/generate-code - On success, logs
createRequestTransaction(functionId=70, status='succeeded')
Params
accessToken: stringchainId: numbercontractAddress: stringtype?: string(default'relayer_auth')
Returns: string — one‑time code
Purpose: build a Minimal‑Forwarder compatible ForwardRequest (from,to,value,gas,nonce,data).
Details
- Reads
noncefrom the chain’s Forwarder (getForwarderAddress(chainId)) - Defaults:
value = 0,gasis a coarse estimate (override with builder’s estimate if available)
Params
signer: ethers.JsonRpcSignertokenAddress: string(mapped to requestto)chainId: number
Returns: ForwardRequest
Purpose: build the EIP‑712 domain (name, version, chainId, verifyingContract).
Note: verifyingContract is the chain’s Forwarder address.
Purpose: sign the ForwardRequest using EIP‑712 and return the signature.
Purpose: send the signed ForwardRequest to the relayer API and get the tx hash.
Returns: string — transaction hash (or relayer’s canonical identifier)
export interface ForwardRequest {
from: string;
to: string;
value: bigint; // usually 0n
gas: bigint; // estimation; override with builder's estimate
nonce: bigint; // forwarder.getNonce(from)
data: string; // calldata from ContractBuilder.encode(...)
}
export interface Domain {
name: string;
version: string;
chainId: number;
verifyingContract: string;
}
export interface ForwardCallParams {
signer: ethers.JsonRpcSigner;
chainId: number;
accessToken: string;
contractAddress: string;
abi: ethers.InterfaceAbi;
method: string;
args: unknown[];
}getRelayerUrl(chainId): string— resolve relayer API base URLgetForwarderAddress(chainId): string— resolve Minimal‑Forwarder addressbigintReplacer—JSON.stringifyreplacer to stringifybigint
The chain → forwarder mapping depends on your environment. See your
utils.ts/constants.ts.
- Normalize with the builder: centralize calldata generation & gas estimation in
ContractBuilder. - Domain separation: always derive domain via
makeDomain(); pinchainIdand forwarder per env. - Re‑issue Auth Code on retry: it’s one‑time; also re‑read
nonceon each retry.
Client key not set— call@hazbase/auth.setClientKey()first.Relayer error— checkresultin the response (expired Auth Code / forwarder mismatch / encoding issues).invalid signature— mismatch betweendomainandrequest. Ensurefrommatches the signer’s address.nonce too low/high— refreshforwarder.getNonce(from)and updaterequest.nonce.execution reverted— check roles/paused state on target contract, arguments, and balances.
Apache-2.0