When can External Queries be called vs. External Messages

I have been looking through the Privacy Model here
https://build.scrt.network/dev/privacy-model-of-secret-contracts.html#external-query
and I have a point I wanted clarification on. It seems to me that external queries, read-only external contract executions, can be called at any time in my own smart contract. But external messages that perform data modifications like Transfer or mintNFT can only be called after the execution of my smart contract, like after all code in my contract is finished. Is that the case?

So we couldn’t, for instance, mint an NFT and then store its ID in a separate minter contract because the minter contract cannot execute code after sending an external message. We would have to make the mintNFT supply a random token id for the mint and store that?

external messages that perform data modifications like Transfer or mintNFT can only be called after the execution of my smart contract, like after all code in my contract is finished. Is that the case?

Yes, that is correct. The CosmWasm runtime (which is the smart contract runtime model we use for secret contracts) follows the “actors” model, so only one contract has mutable access to the storage at a time, and you can trigger callbacks (i.e. send messages) between contracts, effectively handing over control between different “actors”. You can also send out multiple callbacks after your contract finishes running, including a callback to yourself. These callbacks are processed in a depth-first fashion, so a contract A can issue two callbacks to B and C. B will be processed first, and can issue more callbacks that will be processed before C. Contracts can also make callbacks to each other, e.g. A->B->C->B->A->C etc, and this already happens on mainnet quite often. (but it requires some collaborative behavior from the contracts.)

So we couldn’t, for instance, mint an NFT and then store its ID in a separate minter contract because the minter contract cannot execute code after sending an external message. We would have to make the mintNFT supply a random token id for the mint and store that?

@baedrik can comment on the best way to do this with SNIP-721 (and you can always extend it if you need to) but from what i remember to mint an NFT you have to provide its ID in the first place. So your contract can pick a new ID, save it in its own state, then issue two callbacks:

  1. mint an NFT with the ID
  2. inform some other contract that the NFT was minted.
    If the ID doesn’t exist, the tx will fail at step 1, and revert the state of your contract to before the TX.
    If you want to avoid the revert, you can query the NFT contract first to see if the ID is available, and if it isn’t, not send the callback in the first place (and don’t return an error so it doesn’t get reverted)
2 Likes

Yes, most of the time, the contract that is calling MintNft will want to just supply the token_id to both the MintNft call and to the callback to the contract that wants to store the ID.

But that said, the token_id parameter for MintNft is optional, and if you do not specify it, the token contract will create a token_id for you. You can learn this token_id by doing a callback to either the contract that called MintNft, or if the recipient of the newly minted NFT is a contract, doing a callback to the recipient. Either of those would be able to do a TransactionHistory query and the first TX returned (which is the most recent) would be the mint transaction, so you could pull the token_id from there. If you go that route, the querying contract would also need to have already set its viewing key with the snip721 contract. If that is not the case, you would want the appropriate SetViewingKey callback done earlier in the callback chain than the callback that will perform the TransactionHistory query.

As you can see, it is MUCH more gas efficient to provide the token_id when minting, but there is a way around it if you REALLY don’t want to provide the ID for some reason.

2 Likes

You could also modify the reference implementation such that minting an NFT will trigger a ReceiveNft callback in the same way that SendNft does. That would be a more gas efficient way of learning a minted token_id that wasn’t specified than by using a query, and it would be a simple cut and paste modification.

2 Likes

I see, it seems like the simplest and cheapest way is just to have the minter provide an ID each time. Thanks for your work on the SNIP 721 and SNIP 20! I am learning a lot from the code and documentation.

You’re welcome! If you have any questions feel free to hit me up here, in discord, or via DM