jasper
jasper

Reputation: 945

Handling accounts and transaction signatures from nearlib

I have a contract called exchange. A user, Bob, wants to spend near tokens to purchase positions in markets through the means of placing an Order. Order has a field called owner, it's important that the contract has proof that a certain order is owned by a certain address in this case Bob.

How transactions send to exchange are currently handled is a flow similar to:

const init = async () => {
  this.near = await window.nearlib.connect(Object.assign({ deps: { keyStore: new window.nearlib.keyStores.BrowserLocalStorageKeyStore() } }, window.nearConfig));
  this.walletAccount = new window.nearlib.WalletAccount(this.near);

  this.accountId = this.walletAccount.getAccountId();
  this.exchange = await this.near.loadContract(window.nearConfig.contractName, {
    viewMethods: [],
    changeMethods: ["place_new_order"],
    sender: this.accountId,
  });
}

await init();

// Bob logs into to near 
this.walletAccount.requestSignIn(
  window.nearConfig.contractName,
  window.nearConfig.contractName,
);

// Login process 
[...]

// Bob wants to place a new order
this.exchange.place_new_order(price, amount);

The exchange contract imports a struct called Order.

Order would look like:

use std::string::String;
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, BorshDeserialize, BorshSerialize, Clone)]
pub struct Order {
    pub owner: String,
    pub amount: u64,
    pub price: u64,
}


impl Order {
    pub fn new(owner: String, amount: u64, price: u64) -> Self {
        Order {
            owner,
            amount,
            price
        }
    }
}

Exchange is, in this case, the contract that implements the mod order. Exchange has the place_new_order method which is where I want to be able to make sure that Bob is the one who sent the transaction:

  use near_bindgen::{near_bindgen, env};

  [...]

  mod Order;

  [...]

  pub fn place_new_order(&mut self, amount: u64, price: u64) {
    // Stuff happens here
    let order = Order::new(env::current_account_id(), amount, price);
    // Stuff happens here
  }

Now the thing is that in case, using this nearlib code env::current_account_id() will always return exchange as the current_account_id. This makes sense because all the login does is create an access_key which allows exchange to do a couple of things in the name of Bob but it's still Exchange signing off on transactions.

The question here is: How do I ensure that that exchange knows that Bob is the one who initialized the transaction?

A way this could work that would make sense:

This would cause a Metamask-like UX problem where signing off on every tx is bad UX.

What I'd propose is the following:

Upvotes: 2

Views: 108

Answers (1)

Evgeny Kuzyakov
Evgeny Kuzyakov

Reputation: 1078

Short answer

You should use env::predecessor_account_id() to get the account ID of a user or a contract who called a method on exchange.

Details

In your case, even through the access key points towards the exchange it still signed by bob. Thesigner_idand thepredecessor_idduring the execution are both going to bebob`.

There are 3 different types of accounts during the execution:

  • env::current_account_id() returns the current account ID of a contract that is executing right now.
  • env::signer_account_id() returns the account ID of a user who signed the original transaction. Most of the time you should never rely on the signer account ID, cause the current call can be done from another contract.
  • env::predecessor_account_id() return the account ID of immediate predecessor who called the current contract. You should use it to verify the identity of a user who called you.

Upvotes: 2

Related Questions