Pledging NEAR
Accept NEAR pledges with a #[payable] method: track per-backer totals with tuple-keyed LookupMaps and charge each new backer for the storage they add.
Beginner9 min read3-question check
A campaign without backers is just a title. In this milestone backers can pledge NEAR by attaching it to a pledge call. The contract records how much each backer has given, keeps a running total per campaign, and makes the backer — not the contract — pay for the new records.
The pledge method#
function pledge(uint256 id) external payable {
Campaign storage c = campaigns[id];
require(c.token == address(0), "token campaign");
require(block.timestamp < c.deadline, "ended");
if (pledges[id][msg.sender] == 0) {
backers[id][c.backerCount++] = msg.sender;
}
pledges[id][msg.sender] += msg.value;
c.raised += msg.value;
}#[payable]
pub fn pledge(&mut self, campaign_id: CampaignId) {
let backer = env::predecessor_account_id();
let campaign = self.campaign(campaign_id);
require!(campaign.token.is_none(), "this campaign takes a fungible token: use ft_transfer_call");
require!(env::block_timestamp_ms() < campaign.deadline_ms, "campaign has ended");
let storage_cost = self.ensure_backer(campaign_id, &backer);
let attached = env::attached_deposit().as_yoctonear();
require!(
attached > storage_cost,
format!("attach more than {storage_cost} yoctoNEAR (storage) to pledge")
);
self.add_pledge(campaign_id, &backer, attached - storage_cost);
}
pub fn get_pledge(&self, campaign_id: CampaignId, account_id: AccountId) -> U128 {
U128(self.pledges.get(&(campaign_id, account_id)).copied().unwrap_or(0))
}msg.sender ↔ env::predecessor_account_id(): the account that called this method directly. msg.value ↔ env::attached_deposit(), a NearToken (use .as_yoctonear() for the raw u128).
The Solidity version never thinks about storage cost. The NEAR version subtracts it from the deposit: the first pledge to a campaign pays a little for the new records; later pledges pay nothing extra.
Add both methods inside the #[near] impl Crowdfund block. Note the require! checks run before any write: if one fails, the call panics, the attached NEAR goes back to the caller and nothing is stored.
Internal helpers#
// internal helpers: a plain impl block, so none of these are exported
impl Crowdfund {
fn campaign(&self, id: CampaignId) -> &Campaign {
self.campaigns
.get(id)
.unwrap_or_else(|| env::panic_str("no such campaign"))
}
fn campaign_mut(&mut self, id: CampaignId) -> &mut Campaign {
self.campaigns
.get_mut(id)
.unwrap_or_else(|| env::panic_str("no such campaign"))
}
/// Creates the backer's records if they are new. Returns their storage cost in yoctoNEAR.
fn ensure_backer(&mut self, campaign_id: CampaignId, backer: &AccountId) -> u128 {
let key = (campaign_id, backer.clone());
if self.pledges.contains_key(&key) {
return 0;
}
let initial = env::storage_usage();
self.pledges.insert(key, 0);
let campaign = self.campaign_mut(campaign_id);
let index = campaign.backer_count;
campaign.backer_count += 1;
self.backers.insert((campaign_id, index), backer.clone());
self.pledges.flush();
self.backers.flush();
storage_cost_since(initial)
}
fn add_pledge(&mut self, campaign_id: CampaignId, backer: &AccountId, amount: u128) {
let key = (campaign_id, backer.clone());
let total = self.pledges.get(&key).copied().unwrap_or(0) + amount;
self.pledges.insert(key, total);
self.campaign_mut(campaign_id).raised += amount;
}
}- `ensure_backer` is the only place that grows state per backer: one
pledgesentry and onebackersslot. It measures exactly those bytes and returns their price. A returning backer costs0. - `add_pledge` only changes values in place (
u128stays 16 bytes), so it never needs a storage charge. We will reuse it for token pledges in lesson 6, where there is no NEAR deposit at all. - Separate impl block. Only methods inside
#[near] implbecome contract entry points. Helpers in a plainimplare ordinary Rust functions — the NEAR equivalent ofinternalin Solidity.
near contract call-function as-transaction crowdfund.you.testnet pledge \
json-args '{"campaign_id": 0}' \
prepaid-gas '30 Tgas' attached-deposit '2 NEAR' \
sign-as alice.testnet network-config testnet sign-with-keychain send
near contract call-function as-read-only crowdfund.you.testnet get_pledge \
json-args '{"campaign_id": 0, "account_id": "alice.testnet"}' \
network-config testnet now
# a yoctoNEAR string slightly below "2000000000000000000000000":
# 2 NEAR minus the storage for alice's two new recordsCheck yourself
3 questions · progress saved in this browser