Docs/Reference/Relayers
Relayers
How independent relayers discover prepared reward actions, check customer USDC allowance, submit them, and earn the relayer fee.
Opportunities#
Relayers look for Created issuances and redemptions.
The coordination API exposes signed actions that have been created but not completed. A relayer can filter by kind, status, issuer, and program, check customer USDC allowance, then decide whether the relayer fee makes execution worthwhile.
Execution#
Relayers submit from their own wallet. If the transaction succeeds, the customer pays the USDC fee through transferFrom, and the submitting address receives the relayer portion.
import { createPublicClient, createWalletClient, http } from "viem";
import { base } from "viem/chains";
const page = await fetch(
"https://api.loyfin.com/operations?kind=issuance&status=pending&limit=1"
).then((response) => response.json());
const operation = page.items[0];
if (!operation) return;
const publicClient = createPublicClient({ chain: base, transport: http() });
const [feeToken, requiredFee] = await Promise.all([
publicClient.readContract({
address: operation.verifyingContract,
abi: loyfinAbi,
functionName: "feeToken"
}),
publicClient.readContract({
address: operation.verifyingContract,
abi: loyfinAbi,
functionName: "requiredFee"
})
]);
const customer = operation.holder;
const allowance = await publicClient.readContract({
address: feeToken,
abi: erc20Abi,
functionName: "allowance",
args: [customer, operation.verifyingContract]
});
if (allowance < requiredFee) {
throw new Error("Customer must approve USDC before relayer submission.");
}
const metadata = operation.metadata;
const hash = await walletClient.writeContract({
address: operation.verifyingContract,
abi: loyfinAbi,
functionName: operation.kind === "issuance" ? "issue" : "redeem",
args: operation.kind === "issuance"
? [{
issuer: operation.issuer,
to: operation.holder,
loyaltyId: operation.loyaltyId,
amount: BigInt(operation.amount),
expiresAt: BigInt(operation.expiresAt),
deadline: BigInt(operation.deadline),
nonce: operation.nonce,
chainId: BigInt(operation.chainId),
verifyingContract: operation.verifyingContract,
operationHash: operation.operationHash,
metadata: {
loyaltyId: metadata.loyaltyId,
name: metadata.name,
symbol: metadata.symbol,
media: metadata.media,
description: metadata.description ?? "",
contractURI: metadata.contractURI ?? "",
tokenURI: metadata.tokenURI ?? ""
},
data: operation.data
}, operation.signature]
: [{
issuer: operation.issuer,
from: operation.holder,
loyaltyId: operation.loyaltyId,
amount: BigInt(operation.amount),
expiresAt: BigInt(operation.expiresAt),
deadline: BigInt(operation.deadline),
nonce: operation.nonce,
chainId: BigInt(operation.chainId),
verifyingContract: operation.verifyingContract,
operationHash: operation.operationHash,
data: operation.data
}, operation.signature]
});
console.log("submitted", hash);