diff --git a/.env.example b/.env.example index 0b8644d4..8dface4d 100644 --- a/.env.example +++ b/.env.example @@ -299,6 +299,17 @@ MODULE_litecoin-mweb_NODES[]=http://login:password@127.0.0.2:1234/ MODULE_litecoin-mweb_REQUESTER_TIMEOUT=60 MODULE_litecoin-mweb_REQUESTER_THREADS=12 +########################## +## Minimal Polkadot Module +########################## + +MODULES[]=polkadot-minimal +MODULE_polkadot-minimal_CLASS=PolkadotMinimalModule +MODULE_polkadot-minimal_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_polkadot-minimal_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_polkadot-minimal_REQUESTER_TIMEOUT=60 +MODULE_polkadot-minimal_REQUESTER_THREADS=12 + ###################### ## Main Polygon Module ###################### @@ -445,6 +456,7 @@ FRONT_bitcoin-cash_ECOSYSTEM_TITLE=Bitcoin Cash Ecosystem FRONT_polygon_ECOSYSTEM_TITLE=Polygon Ecosystem FRONT_zcash_ECOSYSTEM_TITLE=Zcash Ecosystem FRONT_cardano_ECOSYSTEM_TITLE=Cardano Ecosystem +FRONT_polkadot_ECOSYSTEM_TITLE=Polkadot Ecosystem FRONT_dash_ECOSYSTEM_TITLE=Dash Ecosystem FRONT_handshake_ECOSYSTEM_TITLE=Handshake Ecosystem FRONT_polygon-zkevm_ECOSYSTEM_TITLE=Polygon zkEVM Ecosystem @@ -459,6 +471,7 @@ FRONT_bitcoin-cash_ECOSYSTEM_DESCRIPTION=Includes Bitcoin Cash only FRONT_polygon_ECOSYSTEM_DESCRIPTION=Includes Polygon only FRONT_zcash_ECOSYSTEM_DESCRIPTION=Includes Zcash only FRONT_cardano_ECOSYSTEM_DESCRIPTION=Includes Cardano only +FRONT_polkadot_ECOSYSTEM_DESCRIPTION=Includes Polkadot only FRONT_dash_ECOSYSTEM_DESCRIPTION=Includes Dash only FRONT_handshake_ECOSYSTEM_DESCRIPTION=Includes Handshake only FRONT_polygon-zkevm_ECOSYSTEM_DESCRIPTION=Includes Polygon zkEVM only @@ -473,6 +486,7 @@ FRONT_bitcoin-cash_BLOCKCHAIN_TITLE=Bitcoin Cash FRONT_polygon_BLOCKCHAIN_TITLE=Polygon FRONT_zcash_BLOCKCHAIN_TITLE=Zcash FRONT_cardano_BLOCKCHAIN_TITLE=Cardano +FRONT_polkadot_BLOCKCHAIN_TITLE=Polkadot FRONT_dash_BLOCKCHAIN_TITLE=Dash FRONT_handshake_BLOCKCHAIN_TITLE=Handshake FRONT_polygon-zkevm_BLOCKCHAIN_TITLE=Polygon zkEVM @@ -487,6 +501,7 @@ FRONT_bitcoin-cash_BLOCKCHAIN_DESCRIPTION=The first Bitcoin fork FRONT_polygon_BLOCKCHAIN_DESCRIPTION=An EVM chain FRONT_zcash_BLOCKCHAIN_DESCRIPTION=Supports transparent, Sprout, Sapling, and Orchard shielded pools FRONT_cardano_BLOCKCHAIN_DESCRIPTION=Cardano +FRONT_polkadot_BLOCKCHAIN_DESCRIPTION=Polkadot FRONT_dash_BLOCKCHAIN_DESCRIPTION=Dash FRONT_handshake_BLOCKCHAIN_DESCRIPTION=Handshake FRONT_polygon-zkevm_BLOCKCHAIN_DESCRIPTION=Polygon zkEVM @@ -517,6 +532,7 @@ FRONT_polygon-erc-721_MODULE_TITLE=ERC-721 FRONT_polygon-erc-1155_MODULE_TITLE=ERC-1155 FRONT_zcash-main_MODULE_TITLE=Main FRONT_cardano-main_MODULE_TITLE=Main +FRONT_polkadot-minimal_MODULE_TITLE=Minimal FRONT_dash-main_MODULE_TITLE=Main FRONT_dash-dip-2_MODULE_TITLE=DIP-2 FRONT_handshake-main_MODULE_TITLE=Main @@ -552,6 +568,7 @@ FRONT_polygon-erc-721_MODULE_DESCRIPTION=ERC-721 token transfers FRONT_polygon-erc-1155_MODULE_DESCRIPTION=ERC-1155 token transfers FRONT_zcash-main_MODULE_DESCRIPTION=Supports transparent, Sprout, Sapling, and Orchard shielded pools FRONT_cardano-main_MODULE_DESCRIPTION=Main Cardano transfers (UTXO) +FRONT_polkadot-minimal_MODULE_DESCRIPTION=Polkadot transfers between accounts FRONT_dash-main_MODULE_DESCRIPTION=Main Dash transfers FRONT_dash-dip-2_MODULE_DESCRIPTION=DIP-2 special transfers FRONT_handshake-main_MODULE_DESCRIPTION=Main Handshake transfers diff --git a/Modules/Common/PolkadotLikeMinimalModule.php b/Modules/Common/PolkadotLikeMinimalModule.php new file mode 100644 index 00000000..2a7ab8fe --- /dev/null +++ b/Modules/Common/PolkadotLikeMinimalModule.php @@ -0,0 +1,243 @@ +version = 1; + } + + final public function post_post_initialize() + { + // + } + + final public function inquire_latest_block() + { + return (int)requester_single($this->select_node(), + endpoint: 'api/scan/metadata', + params: true, + result_in: 'data', + timeout: $this->timeout)['blockNum']; + } + + final public function ensure_block($block_id, $break_on_first = false) + { + $this->block_data = requester_single($this->select_node(), + endpoint: 'api/scan/block', + params: ['block_num' => $block_id], + result_in: 'data', + timeout: $this->timeout); + + $this->block_hash = $this->block_data['hash']; + $this->block_time = date('Y-m-d H:i:s', (int)$this->block_data['block_timestamp']); + } + + final public function pre_process_block($block_id) + { + if ($block_id === 0) + { + $this->set_return_events([]); + return; + } + + $extrinsics = $extrinsics_with_hashes = []; + + foreach ($this->block_data['extrinsics'] as $extrinsic) + if ($extrinsic['extrinsic_hash']) + $extrinsics_with_hashes[] = $extrinsic['extrinsic_hash']; + + if (!$extrinsics_with_hashes) + { + $this->set_return_events([]); + return; + } + + $events = []; + $multi_curl = []; + + foreach ($extrinsics_with_hashes as $extrinsic_hash) + $multi_curl[] = requester_multi_prepare($this->select_node(), + endpoint: 'api/scan/extrinsic', + params: ['hash' => $extrinsic_hash], + timeout: $this->timeout); + + $curl_results = requester_multi($multi_curl, limit: envm($this->module, 'REQUESTER_THREADS'), timeout: $this->timeout); + + foreach ($curl_results as $v) + { + $extrinsics[] = requester_multi_process($v, result_in: 'data'); + } + + $transfers = []; + + foreach ($extrinsics as $i) + { + $this_i = $fee = null; + + if (count($i) !== 1) + { + $found = false; + foreach ($i as $j => $k) + { + if ((int)$k['block_num'] === $block_id) + { + if ($found) + throw new ModuleError('Two extrinsics with the same hash in one block'); + + $found = true; + $this_i = $j; + } + } + } + else + { + $this_i = 0; + } + + if (!isset($this_i)) + continue; // Block 12831012 contains 0xa36adc0ddedb4b9f9f8de5ef59fa6c99bbb8ca8bbc44d189dd0bedf6bf61b92e + // which is present in 12857725, 12856380, 12855736, and many more, but not in 12831012 ¯\_(ツ)_/¯ + + if (!$i[$this_i]['transfers']) + continue; + + $initiating_address = $i[$this_i]['account_id']; + + if ($initiating_address === '') // This is a bit strange + $initiating_address = $i[$this_i]['transfers'][0]['from']; + + $fee_found = false; + $extrinsic_failed = $i[$this_i]['failed']; + + foreach ($i[$this_i]['event'] as $event) + { + if ($event['event_id'] === 'TransactionFeePaid') + { + if ($fee_found) + throw new ModuleError('Two fees'); + + $fee_data = json_decode($event['params'], true); + + if (count($fee_data) !== 3) + throw new ModuleError('Wrong fee data'); + if ($fee_data[1]['type_name'] !== 'BalanceOf') + throw new ModuleError('Wrong fee data'); + + $fee = $fee_data[1]['value']; + $fee_found = true; + $fee_idx = $event['event_idx']; + } + } + + if (!$fee_found) // Uh-oh + { + $fee = $i[$this_i]['fee']; + $fee_idx = $i[$this_i]['event'][0]['event_idx']; + } + + foreach ($i[$this_i]['transfers'] as $this_transfer) + { + if ((int)$this_transfer['block_num'] !== $block_id) + throw new ModuleError('Wrong block'); + + if ($extrinsic_failed && !$this_transfer['failed']) + throw new ModuleError('Not failed'); + + $transfers[] = + ['index' => $this_transfer['event_idx'], + 'hash' => $this_transfer['hash'], + 'from' => $this_transfer['from'], + 'to' => $this_transfer['to'], + 'amount' => $this_transfer['amount'], + 'failed' => $this_transfer['failed'], + 'type' => 'transfer' + ]; + } + + $transfers[] = + ['index' => $fee_idx, + 'hash' => $this_transfer['hash'], + 'from' => $initiating_address, + 'to' => 'the-void', + 'amount' => $fee, + 'failed' => false, + 'type' => 'fee' + ]; + } + + usort($transfers, function($a, $b) { + return [$a['index']] <=> [$b['index']]; + }); + + $sort_key = 0; + + foreach ($transfers as $transfer) + { + $events[] = [ + 'transaction' => $transfer['hash'], + 'address' => $transfer['from'], + 'sort_key' => $sort_key++, + 'effect' => '-' . $transfer['amount'], + 'failed' => $transfer['failed'], + 'extra' => ($transfer['type'] === 'fee') ? 'f' : null, + ]; + + $events[] = [ + 'transaction' => $transfer['hash'], + 'address' => $transfer['to'], + 'sort_key' => $sort_key++, + 'effect' => $transfer['amount'], + 'failed' => $transfer['failed'], + 'extra' => ($transfer['type'] === 'fee') ? 'f' : null, + ]; + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $this->set_return_events($events); + } +} diff --git a/Modules/PolkadotMinimalModule.php b/Modules/PolkadotMinimalModule.php new file mode 100644 index 00000000..30204223 --- /dev/null +++ b/Modules/PolkadotMinimalModule.php @@ -0,0 +1,22 @@ +blockchain = 'polkadot'; + $this->module = 'polkadot-minimal'; + $this->is_main = true; + $this->currency = 'polkadot'; + $this->currency_details = ['name' => 'DOT', 'symbol' => 'DOT', 'decimals' => 10, 'description' => null]; + $this->first_block_date = '2020-05-26'; + $this->first_block_id = 0; + } +}