Who is the receiver in a nft_transfer_call?

Viewed 135

I have an NFT contract and a Market deployed for it. I've not been using nft_transfer_call for accepting a bid but adding it in now. In the case an NFT/Media owner accepting a bid the current implemented flow is:

  1. Call [nft-contract].accept_bid(token_id, bidder) which starts a cross contract call
  2. [market-contract].xcc_market_accept_bid(token_id, bidder, design.creator, design.owner_id, design.prev_owner)
  3. Market takes care of paying out shares, removing bid and calls back NFT
  4. [nft-contract].xcc_media_nft_transfer(token_id, receiver_id)
  5. Transfer moves NFT/Media to new owner and finale!

I understand this is not the correct way as I should use nft_transfer_call and based on the Standars this is how the above calls are supposed to be as I understood:

[nft-contract].nft_transfer_call({
  "receiver_id": ${market_address}, ? or ${bidder}
  "token_id": ${token_id},
  "msg": "${token_id} ${bidder} ${creator} ${owner_id} ${prev_owner}"
})

which should transfer token internally and then fires:

[market-contract].nft_on_transfer({
  "sender_id": ${sender_of_nft_transfer_call},
  "previous_owner_id": ${get_media_prev_owner_from_store},
  "token_id": ${token_id_passed_by_front_end},
  "msg": "${token_id} ${bidder} ${creator} ${owner_id} ${prev_owner}", // parameters for Market contract
})

that distributes payouts, updates new shares, and finally calls back:

[nft-contract].nft_resolve_transfer({
  "sender_id": ${sender_of_nft_transfer_call},
  "receiver_id":  ${market_address}, ? or ${bidder},
  "token_id": ${token_id},
})

The question is who is the receiver_id in this case (the market or the new owner)? I understand approval management Standard might be better solution here but I'm trying to keep it simple and solve it with the transfer call.


2 Answers

If I'm reading your code correctly, the bidder is the value for receiver_id in both functions. From the [docs][1]:

// * `receiver_id`: the valid NEAR account receiving the token
// Arguments:
// * `receiver_id`: the valid NEAR account receiving the token.
// * `token_id`: the token to send.
// * `approval_id`: expected approval ID. A number smaller than
//    2^53, and therefore representable as JSON. See Approval Management
//    standard for full explanation.
// * `memo` (optional): for use cases that may benefit from indexing or
//    providing information for a transfer.
// * `msg`: specifies information needed by the receiving contract in
//    order to properly handle the transfer. Can indicate both a function to
//    call and the parameters to pass to that function.
function nft_transfer_call(
  receiver_id: string,
  token_id: string,
  approval_id: number|null,
  memo: string|null,
  msg: string,
): Promise {}

Should match argument in nft_resolve_transfer()

// * `receiver_id`: the `receiver_id` argument given to `nft_transfer_call`
    // Arguments:
// * `sender_id`: the sender of `ft_transfer_call`
// * `receiver_id`: the `receiver_id` argument given to `nft_transfer_call`
// * `token_id`: the `token_id` argument given to `ft_transfer_call`
// * `approved_token_ids`: if using Approval Management, contract MUST provide
//   set of original approved accounts in this argument, and restore these
//   approved accounts in case of revert.
//
// Returns true if token was successfully transferred to `receiver_id`.
function nft_resolve_transfer(
  owner_id: string,
  receiver_id: string,
  token_id: string,
  approved_account_ids: null|string[],
): boolean {}


  [1]: https://nomicon.io/Standards/NonFungibleToken/Core.html

As BenTheHumanMan said, the receiver_id will be the same.

Here is a snippet from the Rust example:

fn nft_transfer_call(
    &mut self,
    receiver_id: AccountId,
    token_id: TokenId,
    approval_id: Option<u64>,
    memo: Option<String>,
    msg: String,
) -> PromiseOrValue<bool> {
    assert_one_yocto();
    let sender_id = env::predecessor_account_id();
    let (old_owner, old_approvals) =
        self.internal_transfer(&sender_id, &receiver_id, &token_id, approval_id, memo);
    // Initiating receiver's call and the callback
    ext_receiver::nft_on_transfer(
        sender_id.clone(),
        old_owner.clone(),
        token_id.clone(),
        msg,
        &receiver_id,
        NO_DEPOSIT,
        env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL,
    )
    .then(ext_self::nft_resolve_transfer(
        old_owner,
        receiver_id,
        token_id,
        old_approvals,
        &env::current_account_id(),
        NO_DEPOSIT,
        GAS_FOR_RESOLVE_TRANSFER,
    ))
    .into()
}

Note that we're using the receiver_id that's given to function initially and passing it to the callback (nft_resolve_transfer) after.

But perhaps to more directly answer your question… In the case where you're having folks bid on an NFT, using the transfer-and-call functionality might not be necessary. Without knowing much details about how bids work, I think you're right that approvals are a better fit.

There is an example in Nomicon that may help. Note that this links to fungible tokens instead of non-fungible tokens, as I believe this is easier to understand. The linked page goes over a scenario where:

Alice needs to issue 1 transaction, as opposed to 2 with a typical escrow workflow.

In short… pretend I want to pay 5 fungible tokens for a service, let's say an oracle. I pay 5 fungible tokens to an oracle contract, and in return they'll store my request, which presumably gets picked up and answered by off-chain services.

In order to:

  1. Send my request for an off-chain service
  2. Send fungible tokens

the typical workflow using an approvals system would be:

  1. I allow the oracle to take 5 fungible tokens from me.
  2. I sent a request, the oracle tries to take those 5 fungible tokens from me.
  3. If successful, the request is stored, if unsuccessful, it says, "you need to allow me to take 5 tokens from you"

This isn't great and takes multiple transactions. In this scenario I could use the transfer-and-call functionality to simplify this. It would essentially say, "hey Fungible Token Contract, transfer 5 tokens to the oracle and then send them from extra information I've given in the msg parameter." (Perhaps the msg contains info like what type of request I have, the request details, how long before I no longer need an off-chain response, etc.)


Pulling back out of the fungible token example, this same thing applies to non-fungible tokens. The use case for transfer-and-call is a little less straightforward and may not suit all needs.

Please see the NFT Approval Management standard spec for more details on that approach, which might be what you want for an auction.

Related