diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46cbb61..27ac04b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,8 @@ name: CI on: pull_request: # trigger on pull requests + branches: + - develop push: branches: # array of glob patterns matching against refs/heads. Optional; defaults to all - master # triggers on pushes that contain changes in master diff --git a/Cargo.lock b/Cargo.lock index 33360df..fab81f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,7 +508,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cota-aggregator" -version = "0.10.9" +version = "0.11.0" dependencies = [ "chrono", "ckb-jsonrpc-types", diff --git a/Cargo.toml b/Cargo.toml index 16b1c0d..cf0ee59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cota-aggregator" -version = "0.10.9" +version = "0.11.0" edition = "2018" [dependencies] diff --git a/README.md b/README.md index 2a8ebe2..8c4f018 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ https://cota.nervina.dev/aggregator - [get_mint_cota_nft](#get_mint_cota_nft) - [is_claimed](#is_claimed) - [get_cota_nft_sender](#get_cota_nft_sender) +- [get_cota_nft_owner](#get_cota_nft_owner) - [get_define_info](#get_define_info) - [get_issuer_info](#get_issuer_info) - [get_issuer_info_by_cota_id](#get_issuer_info_by_cota_id) @@ -939,7 +940,7 @@ claimed - true for claimed and false fot unclaimed ### get_cota_nft_sender -Get the sender lock hash of the CoTA NFT +Get the sender lock hash and address of the CoTA NFT #### Parameters @@ -985,6 +986,50 @@ sender_address - The sender ckb address of the NFT } ``` +### get_cota_nft_owner + +Get the owner address of the CoTA NFT + +#### Parameters + +``` +cota_id - CoTA NFT Class Unique ID +token_index - The index of the NFT Class (increment from zero) +``` + +```shell +echo '{ + "id":2, + "jsonrpc":"2.0", + "method":"get_cota_nft_owner", + "params":{ + "cota_id":"0x2dd97617e685c0cd44b87cba7e8756ea67a721cd", + "token_index":"0x00000000" + } +}' \ +| tr -d '\n' \ +| curl -H 'content-type: application/json' -d @- \ +http://localhost:3030 +``` + +#### Response + +``` +block_number - The latest block number of cota-syncer +owner_address - The owner ckb address of the NFT +``` + +```json +{ + "jsonrpc": "2.0", + "result": { + "block_number": 4397997, + "owner_address": "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2mvqpq923wn8tpkxalc0d095xv0wzfsqqzkfvyx" + }, + "id": 2 +} +``` + ### get_define_info Get define CoTA NFT Class information(name, description, image, total, issued, configure etc.) by the cota_id diff --git a/src/api.rs b/src/api.rs index 740a2e5..6cd6432 100644 --- a/src/api.rs +++ b/src/api.rs @@ -18,7 +18,9 @@ use crate::models::common::{ }; use crate::models::issuer::get_issuer_info_by_lock_hash; use crate::models::joyid::get_joyid_info_by_lock_hash; -use crate::models::withdrawal::nft::get_cota_info_by_cota_id_token_index; +use crate::models::withdrawal::nft::{ + get_cota_info_by_cota_id_token_index, get_receiver_lock_by_cota_id_and_token_index, +}; use crate::request::claim::{ClaimReq, ClaimUpdateReq, IsClaimedReq}; use crate::request::define::{DefineInfoReq, DefineReq}; use crate::request::extension::{ExtSocialReq, ExtSubkeysReq}; @@ -31,7 +33,7 @@ use crate::request::social::SocialUnlockReq; use crate::request::subkey::SubKeyUnlockReq; use crate::request::transfer::{TransferReq, TransferUpdateReq}; use crate::request::update::UpdateReq; -use crate::request::withdrawal::{SenderLockReq, WithdrawalReq}; +use crate::request::withdrawal::{OwnerLockReq, SenderLockReq, WithdrawalReq}; use crate::request::witness::WitnessReq; use crate::response::claim::{parse_claimed_response, parse_claimed_smt, parse_claimed_update_smt}; use crate::response::define::{parse_define_info, parse_define_smt}; @@ -47,7 +49,7 @@ use crate::response::transaction::{parse_cota_transactions, parse_history_transa use crate::response::transfer::{parse_transfer_smt, parse_transfer_update_smt}; use crate::response::update::parse_update_smt; use crate::response::withdrawal::{ - parse_sender_response, parse_withdrawal_response, parse_withdrawal_smt, + parse_owner_response, parse_sender_response, parse_withdrawal_response, parse_withdrawal_smt, }; use crate::response::witness::cota::parse_cota_witness; use crate::utils::error; @@ -235,6 +237,19 @@ pub async fn get_sender_account(params: Params) -> Result { parse_sender_response(sender_account, block_number).map_err(rpc_err) } +pub async fn get_owner_account(params: Params) -> Result { + info!("Get owner account request: {:?}", params); + let map: Map = Params::parse(params)?; + let OwnerLockReq { + cota_id, + token_index, + } = OwnerLockReq::from_map(&map).map_err(rpc_err)?; + let owner_lock = + get_receiver_lock_by_cota_id_and_token_index(cota_id, token_index).map_err(rpc_err)?; + let block_number = tip_number()?; + parse_owner_response(owner_lock, block_number).map_err(rpc_err) +} + pub async fn get_define_info(params: Params) -> Result { info!("Get define info request: {:?}", params); let map: Map = Params::parse(params)?; diff --git a/src/main.rs b/src/main.rs index eb79878..c334128 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,6 +66,7 @@ fn main() { io.add_method("get_mint_cota_nft", fetch_mint_rpc); io.add_method("is_claimed", is_claimed_rpc); io.add_method("get_cota_nft_sender", get_sender_account); + io.add_method("get_cota_nft_owner", get_owner_account); io.add_method("get_define_info", get_define_info); io.add_method("get_issuer_info", get_issuer_info); io.add_method("get_cota_nft_info", get_cota_nft_info); diff --git a/src/models/withdrawal/nft.rs b/src/models/withdrawal/nft.rs index ae84256..bd6ade2 100644 --- a/src/models/withdrawal/nft.rs +++ b/src/models/withdrawal/nft.rs @@ -256,6 +256,43 @@ pub fn get_sender_lock_by_script_id( } } +pub fn get_receiver_lock_by_cota_id_and_token_index( + cota_id_: [u8; 20], + token_index_: [u8; 4], +) -> Result, Error> { + let start_time = Local::now().timestamp_millis(); + let cota_id_hex = hex::encode(cota_id_); + let token_index_u32 = u32::from_be_bytes(token_index_); + let cota_id_crc_u32 = generate_crc(cota_id_hex.as_bytes()); + let receiver_lock_ids: Vec = withdraw_cota_nft_kv_pairs + .select(receiver_lock_script_id) + .filter(cota_id_crc.eq(cota_id_crc_u32)) + .filter(token_index.eq(token_index_u32)) + .filter(cota_id.eq(cota_id_hex)) + .order(updated_at.desc()) + .limit(1) + .load::(&get_conn()) + .map_err(|e| { + error!("Query withdraw error: {}", e.to_string()); + Error::DatabaseQueryError(e.to_string()) + })?; + diff_time( + start_time, + "SQL get_receiver_lock_by_cota_id_and_token_index", + ); + if let Some(receiver_lock_id) = receiver_lock_ids.first().cloned() { + let lock_opt = get_script_map_by_ids(vec![receiver_lock_id])? + .get(&receiver_lock_id) + .cloned(); + match lock_opt { + Some(lock) => Ok(lock), + None => Ok(vec![]), + } + } else { + Ok(vec![]) + } +} + fn parse_withdraw_db(withdrawals: Vec) -> DBResult { let block_height = get_syncer_tip_block_number()?; if withdrawals.is_empty() { diff --git a/src/request/withdrawal.rs b/src/request/withdrawal.rs index 16245c4..ba2c132 100644 --- a/src/request/withdrawal.rs +++ b/src/request/withdrawal.rs @@ -54,3 +54,18 @@ impl SenderLockReq { }) } } + +#[derive(Clone, Eq, PartialEq)] +pub struct OwnerLockReq { + pub cota_id: [u8; 20], + pub token_index: [u8; 4], +} + +impl OwnerLockReq { + pub fn from_map(map: &Map) -> Result { + Ok(OwnerLockReq { + cota_id: map.get_hex_bytes_filed::<20>("cota_id")?, + token_index: map.get_hex_bytes_filed::<4>("token_index")?, + }) + } +} diff --git a/src/response/withdrawal.rs b/src/response/withdrawal.rs index 2e77ea4..c7b3af9 100644 --- a/src/response/withdrawal.rs +++ b/src/response/withdrawal.rs @@ -79,3 +79,14 @@ pub fn parse_sender_response( map.insert_u64("block_number", block_number); Ok(Value::Object(map)) } + +pub fn parse_owner_response(owner_lock: Vec, block_number: u64) -> Result { + let mut map = Map::new(); + if owner_lock.is_empty() { + map.insert_null("owner_address"); + } else { + map.insert_str("owner_address", address_from_script(&owner_lock)?); + } + map.insert_u64("block_number", block_number); + Ok(Value::Object(map)) +}