Use this file to discover all available pages before exploring further.
ALX Protocol uses EIP-712 typed data signing for protocol requests. EIP-712 produces human-readable, structured signatures that bind a request to a specific chain, contract, and agent address. This prevents replay attacks across chains and contracts, and allows any party to recover the signer address and verify authenticity without a centralized authority.
Always pass verifyingContract explicitly. The zero-address default (0x000...000) is safe only for off-chain verification. Using it on-chain allows signatures from one context to be replayed against any other contract that also accepts the zero-address domain.
Construct the EIP-712 domain and the request object you want to sign. Choose a nonce that has not been used before for this agent, and set an appropriate expiry window.
import { buildSignedProtocolRequestDomain, SIGNED_PROTOCOL_REQUEST_TYPES, type SignedProtocolRequest,} from "@alx/protocol";const domain = buildSignedProtocolRequestDomain({ chainId: 8453, verifyingContract: "0xD1F216E872a9ed4b90E364825869c2F377155B29",});const request: SignedProtocolRequest = { kbId: "0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", query: "What is the JWT rotation policy?", agent: wallet.address, nonce: Date.now(), // unique per agent expiry: Math.floor(Date.now() / 1000) + 300, // valid for 5 minutes chainId: 8453,};
2
Sign with wallet.signTypedData
Pass the domain, types, and request to wallet.signTypedData. The SIGNED_PROTOCOL_REQUEST_TYPES constant from @alx/protocol provides the correct EIP-712 type definition.
import { ethers } from "ethers";import { SIGNED_PROTOCOL_REQUEST_TYPES } from "@alx/protocol";const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);const signature = await wallet.signTypedData( domain, SIGNED_PROTOCOL_REQUEST_TYPES, request,);// "0x..." — 65-byte ECDSA signature
3
Transmit the signed request
Send domain, request, and signature to the receiving service. The receiver uses these three values to verify the request.
const payload = { domain, request, signature };// Transmit to your service endpoint
4
Verify on the receiving side
Call verifySignedProtocolRequest with the domain, request, signature, current time, expected chain ID, and a NonceTracker instance. The function throws a SignedRequestValidationError on any failure and returns { ok: true, signer, request } on success.
import { verifySignedProtocolRequest, NonceTracker, SignedRequestValidationError,} from "@alx/protocol";// Create one NonceTracker per service instance; keep it in memory (or back it// with a persistent store) for the lifetime of nonce retention.const nonceTracker = new NonceTracker();try { const { signer, request: normalizedRequest } = verifySignedProtocolRequest({ domain, request, signature, now: Math.floor(Date.now() / 1000), // current Unix timestamp in seconds expectedChainId: 8453, nonceTracker, }); console.log("Verified signer:", signer); // signer === normalizedRequest.agent (checksummed address)} catch (err) { if (err instanceof SignedRequestValidationError) { console.error("Validation failed:", err.code, err.message); } throw err;}
NonceTracker tracks consumed (agent, nonce) pairs in memory. Call nonceTracker.consume(agent, nonce) during verification — it throws NONCE_REUSED if the pair has already been seen, and records it otherwise.
import { NonceTracker } from "@alx/protocol";const nonceTracker = new NonceTracker();// Check without consuming (e.g. for pre-validation)const alreadyUsed = nonceTracker.has(agentAddress, nonce);// Consume during verification (verifySignedProtocolRequest does this automatically// when you pass nonceTracker)nonceTracker.consume(agentAddress, nonce);
The nonce retention window should cover the maximum accepted request lifetime plus expected network delay. If you accept requests with a 5-minute expiry, keep nonces for at least 5–10 minutes. For a persistent deployment, back the tracker with Redis or a database so nonces survive process restarts.