Events
This page documents the events emitted by the Blockhead Recurring Payments Protocol. You can subscribe to these events to update dashboards, analytics, and user notifications in real time.
Notes
- Use indexed topics to filter efficiently (e.g., by
senderorrecipient).additionalInformationis an array of strings; keep it short and consistent if you plan to index it off-chain.- Timestamps and intervals are in seconds.
Event: RecurringPaymentCreated
Definition
event RecurringPaymentCreated(
uint256 accountNumber,
address indexed sender,
address indexed recipient,
uint256 amount,
address token,
uint256 timeIntervalSeconds,
address indexed paymentInterface,
string[] additionalInformation,
uint256 paymentDue,
bool canceled
);
When it fires
Emitted after a successful createRecurringPayment call.
How to use it
- Initialize subscription records in your database.
- Display confirmation to the user and show the first
paymentDuetime. - Attribute fee share by
paymentInterface.
ethers.js v6 listener
import { ethers } from "ethers";
const provider = new ethers.WebSocketProvider("wss://your_rpc");
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
// Listen for ALL creations
contract.on("RecurringPaymentCreated", (
accountNumber,
sender,
recipient,
amount,
token,
timeIntervalSeconds,
paymentInterface,
additionalInformation,
paymentDue,
canceled,
event
) => {
console.log("Created:", {
accountNumber: accountNumber.toString(),
sender, recipient, amount: amount.toString(), token,
timeIntervalSeconds: timeIntervalSeconds.toString(),
paymentInterface,
additionalInformation,
paymentDue: Number(paymentDue),
canceled
});
});
Filter by creator (sender)
// Build a topic filter using indexed params: sender, recipient, paymentInterface
const sender = "0xSenderAddress";
const filter = contract.filters.RecurringPaymentCreated(null, sender);
const logs = await contract.queryFilter(filter, -10_000); // last ~10k blocks
Event: RecurringPaymentCancelled
Definition
event RecurringPaymentCancelled(
uint256 indexed index,
address indexed sender,
address indexed recipient
);
When it fires
Emitted when a subscription is canceled by the payer, the merchant, or the contract owner.
How to use it
- Immediately reflect cancellation in UI (“Canceled” state).
- Revoke access to gated content/services.
- Update churn analytics.
ethers.js v6 listener
contract.on("RecurringPaymentCancelled", (index, sender, recipient, event) => {
console.log("Canceled:", {
accountNumber: index.toString(), sender, recipient
});
});
Filter by recipient (merchant)
const merchant = "0xMerchantAddress";
const filter = contract.filters.RecurringPaymentCancelled(null, null, merchant);
const logs = await contract.queryFilter(filter, 0, "latest");
Event: PaymentTransferred
Definition
event PaymentTransferred(uint256 indexed index);
When it fires
Emitted each time an automated payment execution occurs for a subscription.
How to use it
- Update billing history and invoice records.
- Notify users (email/Discord/Telegram) that a payment succeeded.
- Trigger fulfillment (extend access, ship goods, etc.).
ethers.js v6 listener
contract.on("PaymentTransferred", (index, event) => {
console.log("Payment executed for account:", index.toString());
});
Historical query for a single subscription
const accountId = 123n;
const filter = contract.filters.PaymentTransferred(accountId);
const executions = await contract.queryFilter(filter, 0, "latest");
console.log("Total executions:", executions.length);
ABI Fragments for Events
If you keep a trimmed ABI for listeners, include at least:
[
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "uint256", "name": "accountNumber", "type": "uint256" },
{ "indexed": true, "internalType": "address", "name": "sender", "type": "address" },
{ "indexed": true, "internalType": "address", "name": "recipient", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" },
{ "indexed": false, "internalType": "address", "name": "token", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "timeIntervalSeconds", "type": "uint256" },
{ "indexed": true, "internalType": "address", "name": "paymentInterface", "type": "address" },
{ "indexed": false, "internalType": "string[]", "name": "additionalInformation", "type": "string[]" },
{ "indexed": false, "internalType": "uint256", "name": "paymentDue", "type": "uint256" },
{ "indexed": false, "internalType": "bool", "name": "canceled", "type": "bool" }
],
"name": "RecurringPaymentCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "uint256", "name": "index", "type": "uint256" },
{ "indexed": true, "internalType": "address", "name": "sender", "type": "address" },
{ "indexed": true, "internalType": "address", "name": "recipient", "type": "address" }
],
"name": "RecurringPaymentCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "uint256", "name": "index", "type": "uint256" }
],
"name": "PaymentTransferred",
"type": "event"
}
]
Practical Tips
- Indexing strategy: Keep
additionalInformationconcise and stable if you plan to parse it; store heavy metadata off-chain and reference IDs in this array. - Backfilling history: Use
queryFilterwith appropriate block ranges to rebuild state in your database. - Rate limits: Prefer WebSocket providers for real-time listeners; fall back to HTTP polling if needed.