Search code examples
nearprotocol

do all NEAR blockchain transactions require a receiver account?


reading through some documentation here and saw that part of the definition of a transaction is that all actions are performed "on top of the receiver's account" and also that the receiver account is "the account towards which the transaction will be routed."

also in the nearlib SDK, the transactions interface includes a method called signTransaction that requires receiverId as a parameter

async function signTransaction(receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]> {

but looking over the list of transactions supported by nearcore I wonder why do some of these transaction require a receiver.

why would any transactions require a "receiver" except for maybe Transfer, AddKey, DeleteKey, and DeleteAccount?

amd I think of the idea of "receiver" too literally, as in "they receive the outcome or impact of the transaction"? and instead it's not the right way to think about it?

or is receiverId optional in some cases but the interface just requires a value to avoid validation cruft?

here's what I believe to be the full list of supported transactions

pub enum Action {
    CreateAccount(CreateAccountAction),
    DeployContract(DeployContractAction),
    FunctionCall(FunctionCallAction),
    Transfer(TransferAction),
    Stake(StakeAction),
    AddKey(AddKeyAction),
    DeleteKey(DeleteKeyAction),
    DeleteAccount(DeleteAccountAction),
}

Solution

  • Unfortunately, we don't have a good name to denote what we call "receiver". In some places in our code, we also call it "actor", because it actually refers to an account on which the action is performed opposed to an account which issued the action to perform (a.k.a "sender").

    DeployContract, Stake, AddKey, DeleteKey require receiver==sender, in other words only an account itself can add/delete keys, stake and deploy a contract on itself, no other account can do it for it.

    DeleteAccount is the same, it requires receiver==sender with one exception: If account is about to run out of balance due to storage rent and is below certain system-defined treshold any other account can delete it and claim the remaining balance.

    CreateAccount, FunctionCall, and Transfer do not require receiver==sender. In case of the CreateAccount receiver should not exist at the moment of execution and will actually be created.

    See the code that implements this logic: https://github.com/nearprotocol/nearcore/blob/ed43018851f8ec44f0a26b49fc9a863b71a1428f/runtime/runtime/src/actions.rs#L360