🚀

[NEAR] ネイティブトークンの送付履歴

2022/09/25に公開

Ethereum (とその互換チェーン達)はアドレスに対するネイティブトークンの残高が eth_getBalance で簡単に取得できますね。しかし、その出入りの履歴となると途端に取得が難しくなります。

この辺りを後発の NEAR ではどうしてるのでしょうか?
調査してみました。

NEAR のドキュメントによると

Ethereum と同じように JSON-RPC で Transaction の Receipt が取れます。
https://docs.near.org/concepts/basics/transactions/overview

この Receipt の中にある Actions で実際の内部の動きを知ることができるようです。Etherscan での "Internal Txns" みたいですが、NEAR の Actions は純正の JSON-RPC で取得可能というところが違います。

ちなみにその Transaction で実際に使われたガス代、
すなわち evm でいうところの gasUsed x effectiveGasPrice は 
JSON-RPC の tx メソッドの response 内にある transaction_outcome.outcome.tokens_burnedreceipts_outcome.outcome.tokens_burned(複数)との合計です。

実際にコントラクトで試してみた

testnet にコントラクトをデプロイして動かしてみた時の Actions を見てみました。
実行者はいずれも fathens.testnet のアカウントで統一しています。

(やりとりが複雑なケースになるほどサンプルの Json も長く読み難くなるのですが、ご勘弁を〜

ケースA

ただの名前書き換えのメソッドを payable にしてみました。

    #[payable]
    pub fn something_b(&mut self, name: String) {
        self.name = name.clone();
        let account = env::signer_account_id();
        let amount = env::attached_deposit();
        log!("deposit: {} -> {}", account, amount);
    }

これを実行してコントラクトに 12345678901234567890123456 を deposit した結果を JSON-RPC の tx メソッドで見てみると、次のように something_b の呼び出しのパラメータの1つとして deposit の値がちゃんと出てきます。

request
{
  "jsonrpc": "2.0",
  "id": "a",
  "method": "tx",
  "params": ["C8LFDhVUmVv3eR51NPhvvQWD6AZLf41HKUfSYk6RtkQ1", "fathens.testnet"]
}
response
{
    "jsonrpc": "2.0",
    "result": {
    
        // ... 省略 ...
	
        "transaction": {
            "actions": [
                {
                    "FunctionCall": {
                        "args": "eyJuYW1lIjoic2F3YSJ9",
                        "deposit": "12345678901234567890123456",
                        "gas": 30000000000000,
                        "method_name": "something_b"
                    }
                }
            ],
            "hash": "C8LFDhVUmVv3eR51NPhvvQWD6AZLf41HKUfSYk6RtkQ1",
            "nonce": 100873970000017,
            "public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e",
            "receiver_id": "dev-1663991140396-89348653601121",
            "signature": "ed25519:2TR4BvhEYiRjn1yQRwAMz9xVqNRGp1kceYjEBhDSHjxvEyEDooknzvxu5CeQoJox7FdDAubrReSwXX5nczA3Kxm3",
            "signer_id": "fathens.testnet"
        }
	
        // ... 省略 ...
	
    },
    "id": "a"
}

ここで FunctionCall.args の eyJuYW1lIjoic2F3YSJ9 は Base64 された引数で、デコードするとこうなります。

"{\"name\":\"sawa\"}"

ケースB

さらにこのコントラクトに1割バックするコードを追加してみます。

    #[payable]
    pub fn something_b(&mut self, name: String) -> Promise {
        self.name = name.clone();
        let account = env::signer_account_id();
        let amount = env::attached_deposit();
        log!("deposit: {} -> {}", account, amount);

        let back = amount / 10;
        log!("cache back: {} <- {}", account, back);
        Promise::new(account).transfer(back)
    }

そしてこれを実行した結果を tx メソッドで取得してみました。

request
{
  "jsonrpc": "2.0",
  "id": "a",
  "method": "tx",
  "params": ["FUtpWNxJZjaA9Gi768TBxDvNq2JGioX3t9asNW8mEkr3", "fathens.testnet"]
}
response
{
    "jsonrpc": "2.0",
    "result": {
    
        // ... 省略 ...
	
        "transaction": {
            "actions": [
                {
                    "FunctionCall": {
                        "args": "eyJuYW1lIjoic2F3YSJ9",
                        "deposit": "12345678901234567890123456",
                        "gas": 30000000000000,
                        "method_name": "something_b"
                    }
                }
            ],
            "hash": "FUtpWNxJZjaA9Gi768TBxDvNq2JGioX3t9asNW8mEkr3",
            "nonce": 100873970000019,
            "public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e",
            "receiver_id": "dev-1663991140396-89348653601121",
            "signature": "ed25519:2LBSAA5TChFJiGS1zc8ZKPyvLN3azge4Y1qYJixtdCvR53kfUzYwb7ogAibFoC5q9FYpCEYWFJyLDZFUg7vLTiz1",
            "signer_id": "fathens.testnet"
        }
	
        // ... 省略 ...
	
    },
    "id": "a"
}

どうやら actions には出てこないようです。
もう一つのメソッド EXPERIMENTAL_tx_status を試してみます。
こちらも transactions 内の actions は同じですが、receipts の2つ目にちゃんと出てきました。

request
{
  "jsonrpc": "2.0",
  "id": "a",
  "method": "EXPERIMENTAL_tx_status",
  "params": ["FUtpWNxJZjaA9Gi768TBxDvNq2JGioX3t9asNW8mEkr3", "fathens.testnet"]
}
response
{
    "jsonrpc": "2.0",
    "result": {
        "receipts": [
            {
                "predecessor_id": "fathens.testnet",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "FunctionCall": {
                                    "args": "eyJuYW1lIjoic2F3YSJ9",
                                    "deposit": "12345678901234567890123456",
                                    "gas": 30000000000000,
                                    "method_name": "something_b"
                                }
                            }
                        ],
                        "gas_price": "122987387",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e"
                    }
                },
                "receipt_id": "8VBtrve82yze25CiUWZJz5MYtbohdT9kD8wf8sPLVAkf",
                "receiver_id": "dev-1663991140396-89348653601121"
            },
            {
                "predecessor_id": "dev-1663991140396-89348653601121",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "1234567890123456789012345"
                                }
                            }
                        ],
                        "gas_price": "122987387",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e"
                    }
                },
                "receipt_id": "57jmcoifU1cJeVmPEjRBKiYkhhMAMPPP1jHPiSLvDxgJ",
                "receiver_id": "fathens.testnet"
            },
            {
                "predecessor_id": "system",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "5130383935839187500"
                                }
                            }
                        ],
                        "gas_price": "0",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e"
                    }
                },
                "receipt_id": "D231avwW8Nb9YLB6kenbWQbKXxHFji8Rty3LRi4KwB5T",
                "receiver_id": "fathens.testnet"
            },
            {
                "predecessor_id": "system",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "3622447194802972088408"
                                }
                            }
                        ],
                        "gas_price": "0",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e"
                    }
                },
                "receipt_id": "BxFWxLRHCW5Wat7tmc182wBkuAUCUoM2WyuDGULytLHY",
                "receiver_id": "fathens.testnet"
            }
        ],
	
        // ... 省略 ...
	
	"transaction": {
            "actions": [
                {
                    "FunctionCall": {
                        "args": "eyJuYW1lIjoic2F3YSJ9",
                        "deposit": "12345678901234567890123456",
                        "gas": 30000000000000,
                        "method_name": "something_b"
                    }
                }
            ],
            "hash": "FUtpWNxJZjaA9Gi768TBxDvNq2JGioX3t9asNW8mEkr3",
            "nonce": 100873970000019,
            "public_key": "ed25519:477YvTgELd88jhxYW75Yvzj6aZHFkgkHQyXwmBgDsH6e",
            "receiver_id": "dev-1663991140396-89348653601121",
            "signature": "ed25519:2LBSAA5TChFJiGS1zc8ZKPyvLN3azge4Y1qYJixtdCvR53kfUzYwb7ogAibFoC5q9FYpCEYWFJyLDZFUg7vLTiz1",
            "signer_id": "fathens.testnet"
        },
	
        // ... 省略 ...
	
    },
    "id": "a"
}

ケースC

さらにこのコントラクトを別のコントラクトから呼び出すようにしてみました。
引数で指定された amount を(実行者の残高ではなく)呼び出し側のコントラクトの残高から送ります。
(今回は特に必要ないのですが結果のコールバックも付けています)

外部コントラクトの定義
#[ext_contract(donation)]
trait Donation {
  fn get_current_name(&self) -> String;
  fn something_b(&mut self, name: String);
}
呼び出し側
  pub fn change_name(&mut self, new_name: String, amount: Balance) -> Promise {
    let ex_account = self.excon.clone().unwrap();
    log!("calling: {}", ex_account);
    donation::ext(ex_account)
      .with_static_gas(Gas(5*TGAS))
      .with_attached_deposit(amount)
      .something_b(new_name)
    .then(
      Self::ext(env::current_account_id())
      .with_static_gas(Gas(5*TGAS))
      .change_name_callback()
    )
  }

  #[private]
  pub fn change_name_callback(&mut self, #[callback_result] call_result: Result<(), PromiseError>) -> bool {
    if call_result.is_err() {
      env::log_str("something_b failed...");
      return false;
    } else {
      env::log_str("something_b was successful!");
      return true;
    }
  }

やはり、 tx では取れなかったのですが、EXPERIMENTAL_tx_status だと以下のように取れました。

request
{
  "jsonrpc": "2.0",
  "id": "a",
  "method": "EXPERIMENTAL_tx_status",
  "params": ["CTxHYHk8Cdoin3Nx47eiuTbZDhTxwdKBaZeBP9AK3wPp", "fathens.testnet"]
}
response
{
    "jsonrpc": "2.0",
    "result": {
        "receipts": [
            {
                "predecessor_id": "fathens.testnet",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "FunctionCall": {
                                    "args": "eyJuZXdfbmFtZSI6InNhd2EiLCJhbW91bnQiOjEyMzR9",
                                    "deposit": "0",
                                    "gas": 30000000000000,
                                    "method_name": "change_name"
                                }
                            }
                        ],
                        "gas_price": "122987387",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "GzgCLgiYqmkG7Ykk27mQnhu5ck2oBoSrLHuCyVSdNovp",
                "receiver_id": "dev-1664005113153-67165495609473"
            },
            {
                "predecessor_id": "dev-1664005113153-67165495609473",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "FunctionCall": {
                                    "args": "eyJuYW1lIjoic2F3YSJ9",
                                    "deposit": "1234",
                                    "gas": 9754636221744,
                                    "method_name": "something_b"
                                }
                            }
                        ],
                        "gas_price": "122987387",
                        "input_data_ids": [],
                        "output_data_receivers": [
                            {
                                "data_id": "2fSGEqQ76GBvLN9TjRwGwaBo67fQfpkSTsPTwCMm72UW",
                                "receiver_id": "dev-1664005113153-67165495609473"
                            }
                        ],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "3cvtsf26sfZGktEfXETBbLafgGQnpWb7MFuLfwb6mbau",
                "receiver_id": "dev-1663991140396-89348653601121"
            },
            {
                "predecessor_id": "dev-1663991140396-89348653601121",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "123"
                                }
                            }
                        ],
                        "gas_price": "122987387",
                        "input_data_ids": [],
                        "output_data_receivers": [
                            {
                                "data_id": "2fSGEqQ76GBvLN9TjRwGwaBo67fQfpkSTsPTwCMm72UW",
                                "receiver_id": "dev-1664005113153-67165495609473"
                            }
                        ],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "9T9nKeHKQwhSPBLLDXtf56V9cmJ9huKusQkHLrCXkqs1",
                "receiver_id": "fathens.testnet"
            },
            {
                "predecessor_id": "system",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "5130383935839187500"
                                }
                            }
                        ],
                        "gas_price": "0",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "B1caQG1EJuhKcjHdMN2vtA3iSPM81oX47WU8sXuetX3H",
                "receiver_id": "fathens.testnet"
            },
            {
                "predecessor_id": "system",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "1134070184197619231336"
                                }
                            }
                        ],
                        "gas_price": "0",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "9SnWQDzCFWoUHkuacB51wZQaX8C93Su9a7FAJ6TUHzuP",
                "receiver_id": "fathens.testnet"
            },
            {
                "predecessor_id": "dev-1664005113153-67165495609473",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "FunctionCall": {
                                    "args": "",
                                    "deposit": "0",
                                    "gas": 9754636221745,
                                    "method_name": "change_name_callback"
                                }
                            }
                        ],
                        "gas_price": "122987387",
                        "input_data_ids": [
                            "2fSGEqQ76GBvLN9TjRwGwaBo67fQfpkSTsPTwCMm72UW"
                        ],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "EXNQT1tS5Lw8JrpqCwFSaeVzynYuWNP8T4AzsD63Xkdk",
                "receiver_id": "dev-1664005113153-67165495609473"
            },
            {
                "predecessor_id": "system",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "1190243525833600419475"
                                }
                            }
                        ],
                        "gas_price": "0",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "96mSfXhp1EERK9eFrDPqzBxVauEu1LQTaHM1yy4rLDAk",
                "receiver_id": "fathens.testnet"
            },
            {
                "predecessor_id": "system",
                "receipt": {
                    "Action": {
                        "actions": [
                            {
                                "Transfer": {
                                    "deposit": "185342751824095397841"
                                }
                            }
                        ],
                        "gas_price": "0",
                        "input_data_ids": [],
                        "output_data_receivers": [],
                        "signer_id": "fathens.testnet",
                        "signer_public_key": "ed25519:EKaAXHTd6kjWHHjg88NrvV2geEgdyPTc7R1KLbQssy46"
                    }
                },
                "receipt_id": "2qmRbtAK4pFcWVddZbVBV5M4tHN8hcoFJLuyF5ohkCvE",
                "receiver_id": "fathens.testnet"
            }
        ],
	
        // ... 省略 ...
	
    },
    "id": "a"
}

まとめ

このように NEAR ではコントラクト内部で明示的にイベントを発火しなくても JSON-RPC でネイティブトークンのやり取りが取得できることが分かりました。さすが後発なだけはあると思います。
JSON-RPC のメソッド名に EXPERIMENTAL_... と付いているのは気になりますが、この情報が無くなる方向は考え難いので、より便利に進化していくのでしょう。

Discussion