Near Learn

The 1 yoctoNEAR guard

Why NEAR contracts require exactly 1 yoctoNEAR with assert_one_yocto(): it forces a full-access-key wallet signature for transfers and other sensitive calls.

Intermediate5 min read3-question check

When a user signs in to a NEAR dApp, the wallet often adds a function-call access key to their account and hands it to the website. That key can call your contract without any wallet prompt. Handy for game moves and likes — dangerous for moving assets.

The standard defence is tiny: require the call to attach exactly 1 yoctoNEAR (10⁻²⁴ NEAR). Function-call keys cannot attach deposits, so the transaction has to be signed with a full-access key — which normally means the user confirms it in their wallet.

The bug#

Vulnerable — a session key in the browser can move a user’s items
Rust
#[near]
impl Items {
    pub fn transfer_item(&mut self, item_id: u64, receiver_id: AccountId) {
        let owner = self.owners.get(&item_id).cloned()
            .unwrap_or_else(|| env::panic_str("no such item"));
        require!(owner == env::predecessor_account_id(), "not your item");
        // BUG: callable with a function-call key. An XSS bug or a malicious
        // frontend holding the user's session key can empty their inventory
        // without the wallet ever asking.
        self.owners.insert(item_id, receiver_id);
    }
}

The fix#

Fixed — #[payable] + assert_one_yocto()
Rust
use near_sdk::store::LookupMap;
use near_sdk::{assert_one_yocto, env, near, require, AccountId, PanicOnDefault};

#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Items {
    owners: LookupMap<u64, AccountId>,
}

#[near]
impl Items {
    #[payable] // otherwise near-sdk rejects ANY attached deposit
    pub fn transfer_item(&mut self, item_id: u64, receiver_id: AccountId) {
        assert_one_yocto(); // panics unless exactly 1 yoctoNEAR is attached
        let owner = self.owners.get(&item_id).cloned()
            .unwrap_or_else(|| env::panic_str("no such item"));
        require!(owner == env::predecessor_account_id(), "not your item");
        self.owners.insert(item_id, receiver_id);
    }
}

Check yourself

3 questions · progress saved in this browser

  1. 1.Why does requiring 1 yoctoNEAR force a wallet confirmation?
  2. 2.You add assert_one_yocto() to a method but forget #[payable]. What happens when a user attaches 1 yoctoNEAR?
  3. 3.Which NEP-141 method requires exactly 1 yoctoNEAR attached?