Near Learn

Views & pagination

Design frontend-friendly NEAR view methods: a computed campaign status, paginated campaign and backer lists, and U128 strings for large amounts in JSON.

Intermediate9 min read3-question check

The contract is functionally complete. What it lacks is a good read API: the frontend needs to list campaigns, show each one’s status, and list who backed it. This milestone adds those views — free for callers, but still bounded, because a view that tries to return everything eventually stops working.

A status the frontend doesn’t have to compute#

Add a status enum and compute it in view_of
Rust
#[near(serializers = [json])]
#[derive(PartialEq, Debug)]
pub enum CampaignStatus {
    Open,
    Succeeded,
    Failed,
}

// add the field to CampaignView
//     pub status: CampaignStatus,

fn view_of(id: CampaignId, c: &Campaign) -> CampaignView {
    let status = if env::block_timestamp_ms() < c.deadline_ms {
        CampaignStatus::Open
    } else if c.raised >= c.goal {
        CampaignStatus::Succeeded
    } else {
        CampaignStatus::Failed
    };
    CampaignView {
        id,
        creator: c.creator.clone(),
        title: c.title.clone(),
        goal: U128(c.goal),
        raised: U128(c.raised),
        deadline_ms: c.deadline_ms,
        token: c.token.clone(),
        claimed: c.claimed,
        backer_count: c.backer_count,
        status,
    }
}

View calls run against a recent block, so env::block_timestamp_ms() works in them too. The status uses exactly the same comparisons as pledge, claim and refund, so the UI can never show “Failed” while the contract still accepts pledges. Unit variants serialize as strings: "Open", "Succeeded", "Failed".

Paginated lists#

Return a page, never the whole collection
Solidity
function getCampaigns(uint256 from, uint256 limit)
    external view returns (Campaign[] memory page)
{
    uint256 end = from + limit;
    if (end > campaigns.length) end = campaigns.length;
    if (from >= end) return new Campaign[](0);
    page = new Campaign[](end - from);
    for (uint256 i = from; i < end; i++) {
        page[i - from] = campaigns[i];
    }
}
Rust
const MAX_PAGE: u32 = 50;

pub fn get_campaigns(&self, from_index: Option<u32>, limit: Option<u32>) -> Vec<CampaignView> {
    let from = from_index.unwrap_or(0);
    let limit = limit.unwrap_or(20).min(MAX_PAGE);
    let end = from.saturating_add(limit).min(self.campaigns.len());
    (from..end)
        .filter_map(|id| self.campaigns.get(id).map(|c| view_of(id, c)))
        .collect()
}

pub fn get_backers(
    &self,
    campaign_id: CampaignId,
    from_index: Option<u32>,
    limit: Option<u32>,
) -> Vec<BackerView> {
    let count = self.campaigns.get(campaign_id).map(|c| c.backer_count).unwrap_or(0);
    let from = from_index.unwrap_or(0);
    let limit = limit.unwrap_or(20).min(MAX_PAGE);
    let end = from.saturating_add(limit).min(count);
    (from..end)
        .filter_map(|i| self.backers.get(&(campaign_id, i)))
        .map(|account_id| BackerView {
            account_id: account_id.clone(),
            amount: self.get_pledge(campaign_id, account_id.clone()),
        })
        .collect()
}

pub fn get_campaign_count(&self) -> u32 {
    self.campaigns.len()
}
Coming from EVM

Option<u32> arguments can be omitted from the JSON: {} means “first page, 20 items”. The MAX_PAGE cap means a client cannot ask for a page so large the view runs out of gas.

A range of indexes reads only the entries on the page. This is why lesson 2 picked a Vector for campaigns and the (campaign_id, index) map for backers: both page by index, directly.

The backer row type
Rust
#[near(serializers = [json])]
pub struct BackerView {
    pub account_id: AccountId,
    pub amount: U128,
}

Numbers in JSON#

ValueRust typeIn JSONWhy
Goal, raised, pledgesu128 → U128"5000000000000000000000000"JS numbers lose precision above 2^53; 5 NEAR is 5×10^24 yoctoNEAR
Deadlineu64 (milliseconds)1767225600000Milliseconds since 1970 stay far below 2^53
Block time in nanosecondsu64 → U64"1767225600000000000"Nanoseconds exceed 2^53, so they need a string
Ids, countsu327Always safe
Which Rust types are safe to return as JSON numbers
Try the views
Shell
near contract call-function as-read-only crowdfund.you.testnet get_campaigns \
  json-args '{"from_index": 0, "limit": 10}' network-config testnet now

near contract call-function as-read-only crowdfund.you.testnet get_backers \
  json-args '{"campaign_id": 0}' network-config testnet now

Check yourself

3 questions · progress saved in this browser

  1. 1.Why is raised returned as U128 instead of u128?
  2. 2.What does capping limit at MAX_PAGE protect against?
  3. 3.Is it safe to return deadline_ms: u64 as a plain JSON number?