You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用payable将NEAR从predecessor账户存入合约的异常排查

问题描述

我使用如下方法时运行正常,NEAR余额会从调用者账户扣除并划入合约账户:

#[payable]
pub fn buy_tokens(&mut self, number_of_tokens: U128) {
    let number_of_tokens: u128 = number_of_tokens.into();
    assert!(self.on_crowdsale == true, "Crowdsale has stalled");
    let amount = env::attached_deposit();
    let mut required_deposit = number_of_tokens * self.token_price;
    assert!(
        amount >= required_deposit + self.ft.storage_balance_bounds().min.0,
        "Requires attached deposit {}",
        required_deposit + self.ft.storage_balance_bounds().min.0
    );
    let account_id = env::predecessor_account_id();
    if !self.ft.accounts.contains_key(&account_id) {
        // 未注册,若用户附加的NEAR足够则完成注册
        // 从用户余额中扣除注册费用
        self.ft.internal_register_account(&account_id);
        required_deposit = required_deposit + self.ft.storage_balance_bounds().min.0;
    }
    self.token_sold = (self.token_sold)
        .checked_add(number_of_tokens)
        .expect("Overflow");
    assert!(
        self.token_sold <= self.total_available_tokens,
        "No more tokens to sale"
    );
    assert!(
        self.token_sold <= self.phase_available_tokens,
        "No more tokens to sale"
    );
    self.mint_myft(&account_id, number_of_tokens);
    if amount - required_deposit > 0 {
        Promise::new(account_id).transfer(amount - required_deposit);
    }
}

但我使用如下方法时,合约虽提示执行成功,但调用者账户和合约地址的NEAR余额均无变化:

#[payable]
pub fn buy_nft(&mut self, token_id: U128) {
    let product_id: u128 = token_id.into();
    let token_owner_id = env::predecessor_account_id();
    let product = self.get_product(product_id);
    let price = self.get_nft_price(product_id);
    let amount = env::attached_deposit();
    assert!(amount == price, "Price should be equal to deposit");
    let product_owner_id: u128 = product.user_id;
    self.increment_owner_incentives(product_owner_id, amount);
    self.check_and_increment_nft_token_mint_count(product_id);
    let countname = self.get_nft_count_name(product_id);
    let names = [product_id.to_owned().to_string(), countname];
    let joined_name = names.join("_");
    log!("NFT token id {}", joined_name);
    log!("Amount {}: Price {}", amount, price);
    let token_metadata = TokenMetadata {
        title: Some(product.product_details_hash.into()),
        description: None,
        media: None,
        media_hash: None,
        copies: Some(1u64),
        issued_at: None,
        expires_at: None,
        starts_at: None,
        updated_at: None,
        extra: None,
        reference: None,
        reference_hash: None,
    };
    self.tokens.mint(
        joined_name,
        token_owner_id.try_into().unwrap(),
        Some(token_metadata),
    );
}

调用命令参考:near call dev-1625891373202-46622873112534 buy_nft '{"token_id":"5"}' --accountId amiyatulu.testnet --amount 10 --gas 300000000000000
请问我在第二个方法的实现中存在什么错误?


问题原因
  • 核心错误是未支付NFT mint所需的存储押金,导致交易隐式回滚。
    按照NEP-171 NFT标准要求,每个NFT的持有者需要占用对应的链上存储空间,必须支付对应存储押金才能完成mint操作。你在调用self.tokens.mint时没有附带任何NEAR作为存储押金,也没有提前为调用者注册NFT存储账户,因此mint操作直接失败,触发整个交易的状态回滚,用户支付的NEAR原路退回,所以双方余额都没有变化。你看到的"执行成功"只是当前合约方法执行完成的提示,没有捕获后续NFT mint操作的失败结果。
  • 若你调用的是外部NFT合约的mint接口,当前写法没有对跨合约Promise的执行结果做校验,也会出现方法执行提示成功、但实际状态全部回滚的情况。
  • 现有代码仅将收到的金额计入了owner_incentives的记账字段,没有做实际的资金划转逻辑,后续如果没有额外提取规则,资金会一直滞留在合约账户中。
修复建议
  1. 在mint NFT前检查调用者是否已经注册NFT存储账户,未注册则先从用户的attached_deposit中扣除存储押金完成注册,或者在调用mint接口时附带对应的存储押金。
  2. 如果是调用外部NFT合约,需要给mint的Promise添加回调函数,根据mint的成功/失败结果处理资金:如果mint成功则留存/划转资金,如果失败则全额退回用户的押金。
  3. 补充资金划转逻辑,在确认mint成功后,按照业务规则将收到的NEAR转给对应收益方。

内容的提问来源于stack exchange,提问作者Amiya Behera

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 12:54:00