Near Learn

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.

Declaring and emitting events
Solidity
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);
Rust
#[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();
Coming from EVM

#[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#

Four one-line additions
Rust
// 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();
true
In the receipt logs: a returning backer pledges 2 NEAR (no storage charge, so the full amount)
JSON
EVENT_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:

Rust
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

  1. 1.What prefix marks a NEP-297 event in a NEAR log line?
  2. 2.Why is the claimed event emitted in on_claim rather than in claim?
  3. 3.What does #[near(event_json(standard = "crowdfund"))] generate for the enum?