[NEAR] ネイティブトークンの送付履歴
Ethereum (とその互換チェーン達)はアドレスに対するネイティブトークンの残高が eth_getBalance
で簡単に取得できますね。しかし、その出入りの履歴となると途端に取得が難しくなります。
この辺りを後発の NEAR ではどうしてるのでしょうか?
調査してみました。
NEAR のドキュメントによると
Ethereum と同じように JSON-RPC で Transaction の Receipt が取れます。
この Receipt の中にある Actions で実際の内部の動きを知ることができるようです。Etherscan での "Internal Txns" みたいですが、NEAR の Actions は純正の JSON-RPC で取得可能というところが違います。
ちなみにその Transaction で実際に使われたガス代、
すなわち evm でいうところの gasUsed x effectiveGasPrice
は
JSON-RPC の tx
メソッドの response 内にある transaction_outcome.outcome.tokens_burned
と receipts_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 の値がちゃんと出てきます。
{
"jsonrpc": "2.0",
"id": "a",
"method": "tx",
"params": ["C8LFDhVUmVv3eR51NPhvvQWD6AZLf41HKUfSYk6RtkQ1", "fathens.testnet"]
}
{
"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
メソッドで取得してみました。
{
"jsonrpc": "2.0",
"id": "a",
"method": "tx",
"params": ["FUtpWNxJZjaA9Gi768TBxDvNq2JGioX3t9asNW8mEkr3", "fathens.testnet"]
}
{
"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つ目にちゃんと出てきました。
{
"jsonrpc": "2.0",
"id": "a",
"method": "EXPERIMENTAL_tx_status",
"params": ["FUtpWNxJZjaA9Gi768TBxDvNq2JGioX3t9asNW8mEkr3", "fathens.testnet"]
}
{
"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
だと以下のように取れました。
{
"jsonrpc": "2.0",
"id": "a",
"method": "EXPERIMENTAL_tx_status",
"params": ["CTxHYHk8Cdoin3Nx47eiuTbZDhTxwdKBaZeBP9AK3wPp", "fathens.testnet"]
}
{
"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