Events for indexers and frontends
Emit NEP-297 events from the crowdfunding contract with #[near(event_json)]: EVENT_JSON logs for campaign created, pledged, claimed and refunded.
Intermediate7 min read3-question check
A frontend can poll views, but anything that wants a history — an activity feed, an indexer, analytics, a bot that tweets new campaigns — needs events. In this milestone the contract emits a structured event for each of its four state changes, following the NEP-297 event format.
Logs, not topics#
NEAR has no LOG0–LOG4 opcodes, bloom filters or indexed topics. A contract writes plain strings with env::log_str, and they appear in the receipt outcome. NEP-297 gives those strings a shape: the prefix EVENT_JSON: followed by a JSON object with standard, version, event and data. Indexers look for the prefix and parse the rest.
event CampaignCreated(uint256 indexed id, address indexed creator, uint256 goal, uint64 deadline);
event Pledged(uint256 indexed id, address indexed backer, uint256 amount);
event Claimed(uint256 indexed id, address indexed creator, uint256 amount);
event Refunded(uint256 indexed id, address indexed backer, uint256 amount);
// ...
emit Pledged(id, msg.sender, msg.value);#[near(event_json(standard = "crowdfund"))]
pub enum CrowdfundEvent<'a> {
#[event_version("1.0.0")]
CampaignCreated { campaign_id: CampaignId, creator: &'a AccountId, goal: U128, deadline_ms: u64 },
#[event_version("1.0.0")]
Pledged { campaign_id: CampaignId, backer: &'a AccountId, amount: U128 },
#[event_version("1.0.0")]
Claimed { campaign_id: CampaignId, creator: &'a AccountId, amount: U128 },
#[event_version("1.0.0")]
Refunded { campaign_id: CampaignId, backer: &'a AccountId, amount: U128 },
}
// ...
CrowdfundEvent::Pledged { campaign_id, backer, amount: U128(amount) }.emit();#[near(event_json(...))] generates the emit() method and the NEP-297 envelope. Variant names become snake_case event names: CampaignCreated → "campaign_created".
Borrowing (&'a AccountId) avoids cloning account ids just to log them. Amounts are U128 so they serialize as strings, like everywhere else in our JSON.
Where to emit#
// create_campaign — just before `id` is returned
CrowdfundEvent::CampaignCreated { campaign_id: id, creator: &creator, goal, deadline_ms }.emit();
// add_pledge — covers NEAR pledges AND token pledges
CrowdfundEvent::Pledged { campaign_id, backer, amount: U128(amount) }.emit();
// on_claim — only on the success branch
let campaign = self.campaign_mut(campaign_id);
if result.is_err() {
campaign.claimed = false; // let the creator try again
return false;
}
CrowdfundEvent::Claimed {
campaign_id,
creator: &campaign.creator,
amount: U128(campaign.raised),
}
.emit();
true
// on_refund — only on the success branch
CrowdfundEvent::Refunded { campaign_id, backer: &backer, amount }.emit();
trueEVENT_JSON:{"standard":"crowdfund","version":"1.0.0","event":"pledged","data":{"campaign_id":0,"backer":"alice.testnet","amount":"2000000000000000000000000"}}MoreThe same event without the macro
The macro is convenience, not magic. This produces the same line, which is useful if you need a shape the macro cannot express:
use near_sdk::serde_json::json;
let event = json!({
"standard": "crowdfund",
"version": "1.0.0",
"event": "pledged",
"data": { "campaign_id": campaign_id, "backer": backer, "amount": U128(amount) },
});
env::log_str(&format!("EVENT_JSON:{event}"));Check yourself
3 questions · progress saved in this browser