diff --git a/.env.example b/.env.example index f41639e6..54797f73 100644 --- a/.env.example +++ b/.env.example @@ -744,6 +744,258 @@ MODULE_zcash-main_NODES[]=http://login:password@127.0.0.2:1234/ MODULE_zcash-main_REQUESTER_TIMEOUT=60 MODULE_zcash-main_REQUESTER_THREADS=12 +###################### +## Main CosmosHub Module +###################### + +MODULES[]=cosmos-hub-main +MODULE_cosmos-hub-main_CLASS=CosmosHubMainModule +MODULE_cosmos-hub-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cosmos-hub-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cosmos-hub-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_cosmos-hub-main_REQUESTER_TIMEOUT=60 +MODULE_cosmos-hub-main_REQUESTER_THREADS=12 + +###################### +## IBC CosmosHub Module +###################### + +MODULES[]=cosmos-hub-ibc +MODULE_cosmos-hub-ibc_CLASS=CosmosHubIBCModule +MODULE_cosmos-hub-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cosmos-hub-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cosmos-hub-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_cosmos-hub-ibc_REQUESTER_TIMEOUT=60 +MODULE_cosmos-hub-ibc_REQUESTER_THREADS=12 + +###################### +## Main Axelar Module +###################### + +MODULES[]=axelar-main +MODULE_axelar-main_CLASS=AxelarMainModule +MODULE_axelar-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_axelar-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_axelar-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_axelar-main_REQUESTER_TIMEOUT=60 +MODULE_axelar-main_REQUESTER_THREADS=12 + +###################### +## IBC Axelar Module +###################### + +MODULES[]=axelar-ibc +MODULE_axelar-ibc_CLASS=AxelarIBCModule +MODULE_axelar-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_axelar-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_axelar-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_axelar-ibc_REQUESTER_TIMEOUT=60 +MODULE_axelar-ibc_REQUESTER_THREADS=12 + +###################### +## Main Neutron Module +###################### + +MODULES[]=neutron-main +MODULE_neutron-main_CLASS=NeutronMainModule +MODULE_neutron-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_neutron-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_neutron-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_neutron-main_REQUESTER_TIMEOUT=60 +MODULE_neutron-main_REQUESTER_THREADS=12 + +###################### +## IBC Neutron Module +###################### + +MODULES[]=neutron-ibc +MODULE_neutron-ibc_CLASS=NeutronIBCModule +MODULE_neutron-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_neutron-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_neutron-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_neutron-ibc_REQUESTER_TIMEOUT=60 +MODULE_neutron-ibc_REQUESTER_THREADS=12 + +###################### +## Main Terra Module +###################### + +MODULES[]=terra-main +MODULE_terra-main_CLASS=TerraMainModule +MODULE_terra-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_terra-main_REQUESTER_TIMEOUT=60 +MODULE_terra-main_REQUESTER_THREADS=12 + +###################### +## CW20 Terra Module +###################### + +MODULES[]=terra-cw-20 +MODULE_terra-cw-20_CLASS=TerraCW20Module +MODULE_terra-cw-20_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-cw-20_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-cw-20_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_terra-cw-20_REQUESTER_TIMEOUT=60 +MODULE_terra-cw-20_REQUESTER_THREADS=12 + +###################### +## CW721 Terra Module +###################### + +MODULES[]=terra-cw-721 +MODULE_terra-cw-721_CLASS=TerraCW721Module +MODULE_terra-cw-721_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-cw-721_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-cw-721_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_terra-cw-721_REQUESTER_TIMEOUT=60 +MODULE_terra-cw-721_REQUESTER_THREADS=12 + +###################### +## IBC Terra Module +###################### + +MODULES[]=terra-ibc +MODULE_terra-ibc_CLASS=TerraIBCModule +MODULE_terra-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_terra-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_terra-ibc_REQUESTER_TIMEOUT=60 +MODULE_terra-ibc_REQUESTER_THREADS=12 + +###################### +## Main Osmosis Module +###################### + +MODULES[]=osmosis-main +MODULE_osmosis-main_CLASS=OsmosisMainModule +MODULE_osmosis-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_osmosis-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_osmosis-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_osmosis-main_REQUESTER_TIMEOUT=60 +MODULE_osmosis-main_REQUESTER_THREADS=12 + +###################### +## IBC Osmosis Module +###################### + +MODULES[]=osmosis-ibc +MODULE_osmosis-ibc_CLASS=OsmosisIBCModule +MODULE_osmosis-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_osmosis-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_osmosis-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_osmosis-ibc_REQUESTER_TIMEOUT=60 +MODULE_osmosis-ibc_REQUESTER_THREADS=12 + +###################### +## Main Sei Module +###################### + +MODULES[]=sei-main +MODULE_sei-main_CLASS=SeiMainModule +MODULE_sei-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_sei-main_REQUESTER_TIMEOUT=60 +MODULE_sei-main_REQUESTER_THREADS=12 + +###################### +## CW20 Sei Module +###################### + +MODULES[]=sei-cw-20 +MODULE_sei-cw-20_CLASS=SeiCW20Module +MODULE_sei-cw-20_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-cw-20_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-cw-20_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_sei-cw-20_REQUESTER_TIMEOUT=60 +MODULE_sei-cw-20_REQUESTER_THREADS=12 + +###################### +## CW721 Sei Module +###################### + +MODULES[]=sei-cw-721 +MODULE_sei-cw-721_CLASS=SeiCW721Module +MODULE_sei-cw-721_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-cw-721_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-cw-721_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_sei-cw-721_REQUESTER_TIMEOUT=60 +MODULE_sei-cw-721_REQUESTER_THREADS=12 + +###################### +## IBC Sei Module +###################### + +MODULES[]=sei-ibc +MODULE_sei-ibc_CLASS=SeiIBCModule +MODULE_sei-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_sei-ibc_REQUESTER_TIMEOUT=60 +MODULE_sei-ibc_REQUESTER_THREADS=12 + +###################### +## TokenFactory Sei Module +###################### + +MODULES[]=sei-token-factory +MODULE_sei-token-factory_CLASS=SeiTokenFactoryModule +MODULE_sei-token-factory_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-token-factory_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_sei-token-factory_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_sei-token-factory_REQUESTER_TIMEOUT=60 +MODULE_sei-token-factory_REQUESTER_THREADS=12 + +###################### +## Main Cronos POS Module +###################### + +MODULES[]=cronos-pos-main +MODULE_cronos-pos-main_CLASS=CronosPOSMainModule +MODULE_cronos-pos-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cronos-pos-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cronos-pos-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_cronos-pos-main_REQUESTER_TIMEOUT=60 +MODULE_cronos-pos-main_REQUESTER_THREADS=12 + +###################### +## IBC Cronos POS Module +###################### + +MODULES[]=cronos-pos-ibc +MODULE_cronos-pos-ibc_CLASS=CronosPOSIBCModule +MODULE_cronos-pos-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cronos-pos-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_cronos-pos-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_cronos-pos-ibc_REQUESTER_TIMEOUT=60 +MODULE_cronos-pos-ibc_REQUESTER_THREADS=12 + +###################### +## Main Celestia Module +###################### + +MODULES[]=celestia-main +MODULE_celestia-main_CLASS=CelestiaMainModule +MODULE_celestia-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_celestia-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_celestia-main_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_celestia-main_REQUESTER_TIMEOUT=60 +MODULE_celestia-main_REQUESTER_THREADS=12 + +###################### +## IBC Celestia Module +###################### + +MODULES[]=celestia-ibc +MODULE_celestia-ibc_CLASS=CelestiaIBCModule +MODULE_celestia-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_celestia-ibc_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_celestia-ibc_RPC_NODE=http://login:password@127.0.0.1:12345/ +MODULE_celestia-ibc_REQUESTER_TIMEOUT=60 +MODULE_celestia-ibc_REQUESTER_THREADS=12 + ############################ # Titles, descriptions, etc. ############################ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7c87c9e5..d4bb1bba 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,6 +5,8 @@ - Core, UTXO modules, EVM modules * [Yulian Volianskyi](https://github.com/jzethar) - Beacon Chain modules +* [Kirill Kuzminykh](https://github.com/Oskal174) + - Cosmos modules * [alexqrid](https://github.com/alexqrid) - TVM modules * [Oleg Makaussov](https://github.com/Lorgansar) diff --git a/Modules/AxelarIBCModule.php b/Modules/AxelarIBCModule.php new file mode 100644 index 00000000..83d40ddd --- /dev/null +++ b/Modules/AxelarIBCModule.php @@ -0,0 +1,23 @@ +blockchain = 'axelar'; + $this->module = 'axelar-ibc'; + $this->is_main = false; + $this->first_block_date = '2021-12-22'; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + $this->cosmos_coin_events_fork = 0; + } +} diff --git a/Modules/AxelarMainModule.php b/Modules/AxelarMainModule.php new file mode 100644 index 00000000..59360324 --- /dev/null +++ b/Modules/AxelarMainModule.php @@ -0,0 +1,29 @@ +blockchain = 'axelar'; + $this->module = 'axelar-main'; + $this->is_main = true; + $this->first_block_date = '2021-12-22'; + $this->currency = 'axelar'; + $this->currency_details = ['name' => 'Axelar', 'symbol' => 'AXL', 'decimals' => 6, 'description' => null]; + + // Cosmos-specific + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'axelar17xpfvakm2amg962yls6f84z3kell8c5l5h4gqu', + ]; + $this->cosmos_known_denoms = ['uaxl' => 0]; + $this->cosmos_coin_events_fork = 0; + } +} diff --git a/Modules/CelestiaIBCModule.php b/Modules/CelestiaIBCModule.php new file mode 100644 index 00000000..5eb38e95 --- /dev/null +++ b/Modules/CelestiaIBCModule.php @@ -0,0 +1,24 @@ +blockchain = 'celestia'; + $this->module = 'celestia-ibc'; + $this->is_main = false; + $this->first_block_id = 1; + $this->first_block_date = '2023-10-31'; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + $this->cosmos_coin_events_fork = 0; + } +} diff --git a/Modules/CelestiaMainModule.php b/Modules/CelestiaMainModule.php new file mode 100644 index 00000000..c7d9d1b2 --- /dev/null +++ b/Modules/CelestiaMainModule.php @@ -0,0 +1,35 @@ +blockchain = 'celestia'; + $this->module = 'celestia-main'; + $this->is_main = true; + $this->first_block_id = 1; + $this->first_block_date = '2023-10-31'; + $this->currency = 'celestia'; + $this->currency_details = ['name' => 'Celestia', 'symbol' => 'TIA', 'decimals' => 8, 'description' => null]; + + // Cosmos-specific + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'celestia17xpfvakm2amg962yls6f84z3kell8c5lpnjs3s' + ]; + $this->cosmos_known_denoms = ['utia' => 0]; + $this->cosmos_coin_events_fork = 0; + + $this->tests = [ + ['block' => 689399, 'transaction' => '275137ad9b3ee553fc28baa6c91102be8b137f59d84258cb82ca505d3689c2ad', 'result' => 'a:1:{s:6:"events";a:4:{i:0;a:8:{s:11:"transaction";s:64:"275137ad9b3ee553fc28baa6c91102be8b137f59d84258cb82ca505d3689c2ad";s:8:"sort_key";i:0;s:7:"address";s:47:"celestia1tv85ggjuxnxden0v44assh854k88t9pca3ardk";s:6:"effect";s:5:"-2536";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:689399;s:4:"time";s:19:"2024-02-01 22:45:13";}i:1;a:8:{s:11:"transaction";s:64:"275137ad9b3ee553fc28baa6c91102be8b137f59d84258cb82ca505d3689c2ad";s:8:"sort_key";i:1;s:7:"address";s:47:"celestia17xpfvakm2amg962yls6f84z3kell8c5lpnjs3s";s:6:"effect";s:4:"2536";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:689399;s:4:"time";s:19:"2024-02-01 22:45:13";}i:2;a:8:{s:11:"transaction";s:64:"275137ad9b3ee553fc28baa6c91102be8b137f59d84258cb82ca505d3689c2ad";s:8:"sort_key";i:2;s:7:"address";s:47:"celestia1tv85ggjuxnxden0v44assh854k88t9pca3ardk";s:6:"effect";s:9:"-11600000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:689399;s:4:"time";s:19:"2024-02-01 22:45:13";}i:3;a:8:{s:11:"transaction";s:64:"275137ad9b3ee553fc28baa6c91102be8b137f59d84258cb82ca505d3689c2ad";s:8:"sort_key";i:3;s:7:"address";s:47:"celestia17sael2kcmm8npe2pmkxj3un90xfg60vv9l4aes";s:6:"effect";s:8:"11600000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:689399;s:4:"time";s:19:"2024-02-01 22:45:13";}}}'], + ['block' => 9956, 'result' => 'a:2:{s:6:"events";a:12:{i:0;a:8:{s:11:"transaction";s:64:"5994338cb722b6a012dfea98dbc10d09aadb66d9c40872f937309d7b65476a9f";s:8:"sort_key";i:0;s:7:"address";s:47:"celestia16m48j88mlw2smhc8nyurznt4jl9nqgyqegz3da";s:6:"effect";s:7:"-136491";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:1;a:8:{s:11:"transaction";s:64:"5994338cb722b6a012dfea98dbc10d09aadb66d9c40872f937309d7b65476a9f";s:8:"sort_key";i:1;s:7:"address";s:47:"celestia17xpfvakm2amg962yls6f84z3kell8c5lpnjs3s";s:6:"effect";s:6:"136491";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:2;a:8:{s:11:"transaction";s:64:"90b27c6570cbb7a3c59cb43a43efe7ddf4fa82edf50f235b67fc39ffe9eba34f";s:8:"sort_key";i:2;s:7:"address";s:47:"celestia1zpaqkahypxx680p0hnsnw5707zpg5q8mcgvygs";s:6:"effect";s:6:"-37818";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:3;a:8:{s:11:"transaction";s:64:"90b27c6570cbb7a3c59cb43a43efe7ddf4fa82edf50f235b67fc39ffe9eba34f";s:8:"sort_key";i:3;s:7:"address";s:47:"celestia17xpfvakm2amg962yls6f84z3kell8c5lpnjs3s";s:6:"effect";s:5:"37818";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:4;a:8:{s:11:"transaction";s:64:"90b27c6570cbb7a3c59cb43a43efe7ddf4fa82edf50f235b67fc39ffe9eba34f";s:8:"sort_key";i:4;s:7:"address";s:47:"celestia1zpaqkahypxx680p0hnsnw5707zpg5q8mcgvygs";s:6:"effect";s:9:"-81270886";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:5;a:8:{s:11:"transaction";s:64:"90b27c6570cbb7a3c59cb43a43efe7ddf4fa82edf50f235b67fc39ffe9eba34f";s:8:"sort_key";i:5;s:7:"address";s:47:"celestia1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3y3clr6";s:6:"effect";s:8:"81270886";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:6;a:8:{s:11:"transaction";N;s:8:"sort_key";i:6;s:7:"address";s:8:"the-void";s:6:"effect";s:9:"-29937517";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:7;a:8:{s:11:"transaction";N;s:8:"sort_key";i:7;s:7:"address";s:47:"celestia1m3h30wlvsf8llruxtpukdvsy0km2kum8emkgad";s:6:"effect";s:8:"29937517";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:8;a:8:{s:11:"transaction";N;s:8:"sort_key";i:8;s:7:"address";s:47:"celestia1m3h30wlvsf8llruxtpukdvsy0km2kum8emkgad";s:6:"effect";s:9:"-29937517";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:9;a:8:{s:11:"transaction";N;s:8:"sort_key";i:9;s:7:"address";s:47:"celestia17xpfvakm2amg962yls6f84z3kell8c5lpnjs3s";s:6:"effect";s:8:"29937517";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:10;a:8:{s:11:"transaction";N;s:8:"sort_key";i:10;s:7:"address";s:47:"celestia17xpfvakm2amg962yls6f84z3kell8c5lpnjs3s";s:6:"effect";s:9:"-30087517";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}i:11;a:8:{s:11:"transaction";N;s:8:"sort_key";i:11;s:7:"address";s:47:"celestia1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8k44vnj";s:6:"effect";s:8:"30087517";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:9956;s:4:"time";s:19:"2023-11-01 22:53:18";}}s:10:"currencies";N;}'], + ]; + } +} diff --git a/Modules/Common/CosmosCW20Module.php b/Modules/Common/CosmosCW20Module.php new file mode 100644 index 00000000..38c477a1 --- /dev/null +++ b/Modules/Common/CosmosCW20Module.php @@ -0,0 +1,192 @@ +value => 'Mint', + CosmosSpecialTransactions::Burn->value => 'Burn', + ]; + + public ?bool $should_return_events = true; + public ?bool $allow_empty_return_events = true; + public ?bool $should_return_currencies = true; + public ?bool $allow_empty_return_currencies = true; + + public ?bool $mempool_implemented = false; + public ?bool $forking_implemented = false; + + // Cosmos-specific + public ?string $rpc_node = null; + + public array $extra_features = []; + + final public function pre_initialize() + { + $this->version = 1; + } + + final public function post_post_initialize() + { + $this->rpc_node = envm( + $this->module, + 'RPC_NODE', + new DeveloperError('RPC_NODE not set in the config') + ); + } + + final public function pre_process_block($block_id) + { + $block_data = requester_single($this->select_node(), endpoint: "block?height={$block_id}", timeout: $this->timeout); + $block_data = $block_data['result'] ?? $block_data; + $block_results = requester_single($this->select_node(), endpoint: "block_results?height={$block_id}", timeout: $this->timeout); + $block_results = $block_results['result'] ?? $block_results; + + if (($tx_count = count($block_data['block']['data']['txs'] ?? [])) !== count($block_results['txs_results'] ?? [])) + throw new ModuleException("TXs count and TXs results count mismatch!"); + + $events = []; + $currencies = []; + $currencies_to_process = []; + $sort_key = 0; + + // Process each transaction results. + for ($i = 0; $i < $tx_count; $i++) + { + $tx_hash = $this->get_tx_hash($block_data['block']['data']['txs'][$i]); + $tx_result = $block_results['txs_results'][$i]; + + if (in_array(CosmosSpecialFeatures::HasNotCodeField, $this->extra_features)) + $failed = (int)isset($tx_result['code']) ? true : false; + else + $failed = (int)$tx_result['code'] === 0 ? false : true; + + foreach ($tx_result['events'] ?? [] as $tx_event) + { + switch ($tx_event['type']) + { + case 'wasm': + $info = $this->parse_wasm_cw20_event($tx_event['attributes']); + if (is_null($info)) + break; + + $currencies_to_process[] = $info['currency']; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $info['from'], + 'currency' => $info['currency'], + 'effect' => '-' . $info['amount'], + 'failed' => $failed, + 'extra' => $info['extra'], + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $info['to'], + 'currency' => $info['currency'], + 'effect' => $info['amount'], + 'failed' => $failed, + 'extra' => $info['extra'], + ]; + + break; + } + } + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $currencies_to_process = array_unique($currencies_to_process); + $currencies_to_process = check_existing_currencies($currencies_to_process, $this->currency_format); + + foreach ($currencies_to_process as $currency) + { + $request = base64_encode('{"token_info":{}}'); + $token_info = requester_single($this->rpc_node, endpoint: "cosmwasm/wasm/v1/contract/{$currency}/smart/{$request}", timeout: $this->timeout, result_in: 'data'); + $currencies[] = [ + 'id' => $currency, + 'name' => $token_info['name'] ?? '', + 'symbol' => $token_info['symbol'] ?? '', + 'decimals' => $token_info['decimals'] ?? '', + ]; + } + + $this->set_return_events($events); + $this->set_return_currencies($currencies); + } + + // Getting balances from the node + public function api_get_balance(string $address, array $currencies): array + { + // Input currencies should be in format like this: `{module}/ibc_E92E07E68705FAD13305EE9C73684B30A7B66A52F54C9890327E0A4C0F1D22E3` + $denoms_to_find = []; + foreach ($currencies as $currency) + { + $denoms_to_find[] = explode('/', $currency)[1]; + } + + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}", timeout: $this->timeout); + + $balances_from_node = []; + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + + // Check pagination + while (!is_null($data['pagination']['next_key'])) + { + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}?pagination.key={$data['pagination']['next_key']}", timeout: $this->timeout); + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + } + + $return = []; + foreach ($denoms_to_find as $denom) + { + $return[] = $balances_from_node[$denom] ?? '0'; + } + + return $return; + } +} \ No newline at end of file diff --git a/Modules/Common/CosmosCW721Module.php b/Modules/Common/CosmosCW721Module.php new file mode 100644 index 00000000..553c5702 --- /dev/null +++ b/Modules/Common/CosmosCW721Module.php @@ -0,0 +1,187 @@ +version = 1; + } + + final public function post_post_initialize() + { + $this->rpc_node = envm( + $this->module, + 'RPC_NODE', + new DeveloperError('RPC_NODE not set in the config') + ); + } + + final public function pre_process_block($block_id) + { + $block_data = requester_single($this->select_node(), endpoint: "block?height={$block_id}", timeout: $this->timeout); + $block_data = $block_data['result'] ?? $block_data; + $block_results = requester_single($this->select_node(), endpoint: "block_results?height={$block_id}", timeout: $this->timeout); + $block_results = $block_results['result'] ?? $block_results; + + if (($tx_count = count($block_data['block']['data']['txs'] ?? [])) !== count($block_results['txs_results'] ?? [])) + throw new ModuleException("TXs count and TXs results count mismatch!"); + + $events = []; + $currencies = []; + $currencies_to_process = []; + $sort_key = 0; + + // Process each transaction results. + for ($i = 0; $i < $tx_count; $i++) + { + $tx_hash = $this->get_tx_hash($block_data['block']['data']['txs'][$i]); + $tx_result = $block_results['txs_results'][$i]; + + if (in_array(CosmosSpecialFeatures::HasNotCodeField, $this->extra_features)) + $failed = (int)isset($tx_result['code']) ? true : false; + else + $failed = (int)$tx_result['code'] === 0 ? false : true; + + foreach ($tx_result['events'] ?? [] as $tx_event) + { + switch ($tx_event['type']) + { + case 'wasm': + $info = $this->parse_wasm_cw721_event($tx_event['attributes']); + if (is_null($info)) + break; + + $currencies_to_process[] = $info['currency']; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $info['from'], + 'currency' => $info['currency'], + 'effect' => '-1', + 'failed' => $failed, + 'extra' => $info['extra'], + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $info['to'], + 'currency' => $info['currency'], + 'effect' => '1', + 'failed' => $failed, + 'extra' => $info['extra'], + ]; + + break; + } + } + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $currencies_to_process = array_unique($currencies_to_process); + $currencies_to_process = check_existing_currencies($currencies_to_process, $this->currency_format); + + foreach ($currencies_to_process as $currency) + { + $request = base64_encode('{"contract_info":{}}'); + $token_info = requester_single($this->rpc_node, endpoint: "cosmwasm/wasm/v1/contract/{$currency}/smart/{$request}", timeout: $this->timeout, result_in: 'data'); + $currencies[] = [ + 'id' => $currency, + 'name' => $token_info['name'] ?? '', + 'symbol' => $token_info['symbol'] ?? '', + ]; + } + + $this->set_return_events($events); + $this->set_return_currencies($currencies); + } + + // Getting balances from the node + public function api_get_balance(string $address, array $currencies): array + { + // Input currencies should be in format like this: `{module}/ibc_E92E07E68705FAD13305EE9C73684B30A7B66A52F54C9890327E0A4C0F1D22E3` + $denoms_to_find = []; + foreach ($currencies as $currency) + { + $denoms_to_find[] = explode('/', $currency)[1]; + } + + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}", timeout: $this->timeout); + + $balances_from_node = []; + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + + // Check pagination + while (!is_null($data['pagination']['next_key'])) + { + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}?pagination.key={$data['pagination']['next_key']}", timeout: $this->timeout); + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + } + + $return = []; + foreach ($denoms_to_find as $denom) + { + $return[] = $balances_from_node[$denom] ?? '0'; + } + + return $return; + } +} \ No newline at end of file diff --git a/Modules/Common/CosmosIBCModule.php b/Modules/Common/CosmosIBCModule.php new file mode 100644 index 00000000..7030c343 --- /dev/null +++ b/Modules/Common/CosmosIBCModule.php @@ -0,0 +1,690 @@ +version = 1; + } + + final public function post_post_initialize() + { + if (is_null($this->cosmos_special_addresses)) + throw new DeveloperError("`cosmos_special_addresses` is not set (deleloper error)"); + + if (is_null($this->cosmos_coin_events_fork)) + throw new DeveloperError("`cosmos_coin_events_fork` is not set (deleloper error)"); + + $this->rpc_node = envm( + $this->module, + 'RPC_NODE', + new DeveloperError('RPC_NODE not set in the config') + ); + } + + final public function pre_process_block($block_id) + { + $block_data = requester_single($this->select_node(), endpoint: "block?height={$block_id}", timeout: $this->timeout); + $block_data = $block_data['result'] ?? $block_data; + $block_results = requester_single($this->select_node(), endpoint: "block_results?height={$block_id}", timeout: $this->timeout); + $block_results = $block_results['result'] ?? $block_results; + + if (($tx_count = count($block_data['block']['data']['txs'] ?? [])) !== count($block_results['txs_results'] ?? [])) + throw new ModuleException("TXs count and TXs results count mismatch!"); + + $events = []; + $currencies = []; + $currencies_to_process = []; + $sort_key = 0; + + // Process each transaction results. + for ($i = 0; $i < $tx_count; $i++) + { + $tx_hash = $this->get_tx_hash($block_data['block']['data']['txs'][$i]); + $tx_result = $block_results['txs_results'][$i]; + + if (in_array(CosmosSpecialFeatures::HasNotCodeField, $this->extra_features)) + $failed = (int)isset($tx_result['code']) ? true : false; + else + $failed = (int)$tx_result['code'] === 0 ? false : true; + + // Need to collect fee and fee_payer before parsing events. + $fee_event_detected = ['from' => false, 'to' => false]; // To avoid double extra + $fee_info = $this->try_find_ibc_fee_info($tx_result['events'] ?? []); + + if (in_array(CosmosSpecialFeatures::HasDoublesTxEvents, $this->extra_features)) + { + $this->erase_double_fee_events($tx_result['events']); + } + + $sub = []; + $add = []; + foreach ($tx_result['events'] ?? [] as $tx_event) + { + switch ($tx_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($tx_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $extra = null; + if (!is_null($fee_info) && !$fee_event_detected['from']) + { + if ($coin_spent_data['from'] === $fee_info['fee_payer'] && + $ibc_amount['amount'] === $fee_info['fee']) + { + $extra = 'f'; + $fee_event_detected['from'] = true; + } + } + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + 'extra' => $extra, + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($tx_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $extra = null; + if (!is_null($fee_info) && !$fee_event_detected['to']) + { + if ($ibc_amount['amount'] === $fee_info['fee']) + { + $extra = 'f'; + $fee_event_detected['to'] = true; + } + } + + $add[] = [ + 'address' => $coin_received_data['to'], + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + 'extra' => $extra, + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($tx_event['attributes']); + if (is_null($coinbase_data)) + break; + + $ibc_amount = $this->denom_amount_to_ibc_amount($coinbase_data['amount']); + if (is_null($ibc_amount)) + break; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $sub[] = [ + 'address' => 'the-ibc-channel', + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + 'extra' => null, + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($tx_event['attributes']); + if (is_null($burn_data)) + break; + + $ibc_amount = $this->denom_amount_to_ibc_amount($burn_data['amount']); + if (is_null($ibc_amount)) + break; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $add[] = [ + 'address' => 'the-ibc-channel', + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + 'extra' => null, + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($tx_event['attributes']); + if (is_null($transfer_data)) + break; + + foreach ($transfer_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'currency' => $ibc_amount['currency'], + 'effect' => '-' . $ibc_amount['amount'], + 'failed' => $failed, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'currency' => $ibc_amount['currency'], + 'effect' => $ibc_amount['amount'], + 'failed' => $failed, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (tx: {$tx_hash}."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['currency'] !== $add[$event_i]['currency']) + throw new ModuleException("Sub and add currency missmatch."); + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'currency' => $sub[$event_i]['currency'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => $failed, + 'extra' => $sub[$event_i]['extra'], + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'currency' => $add[$event_i]['currency'], + 'effect' => $add[$event_i]['amount'], + 'failed' => $failed, + 'extra' => $add[$event_i]['extra'], + ]; + } + } + + // Block header events parsing + + $sub = []; + $add = []; + foreach ($block_results['begin_block_events'] ?? [] as $bb_event) + { + switch ($bb_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($bb_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($bb_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $add[] = [ + 'address' => $coin_received_data['to'], + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($bb_event['attributes']); + if (is_null($coinbase_data)) + break; + + $ibc_amount = $this->denom_amount_to_ibc_amount($coinbase_data['amount']); + if (is_null($ibc_amount)) + break; + + $currencies_to_process[] = $ibc_amount['currency']; + + $sub[] = [ + 'address' => 'the-void', + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($bb_event['attributes']); + if (is_null($burn_data)) + break; + + $ibc_amount = $this->denom_amount_to_ibc_amount($burn_data['amount']); + if (is_null($ibc_amount)) + break; + + $currencies_to_process[] = $ibc_amount['currency']; + + $add[] = [ + 'address' => 'the-void', + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($bb_event['attributes']); + if (is_null($transfer_data)) + break; + + foreach ($transfer_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'currency' => $ibc_amount['currency'], + 'effect' => '-' . $ibc_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'currency' => $ibc_amount['currency'], + 'effect' => $ibc_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (begin block events)."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['currency'] !== $add[$event_i]['currency']) + throw new ModuleException("Sub and add currency missmatch."); + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'currency' => $sub[$event_i]['currency'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'currency' => $add[$event_i]['currency'], + 'effect' => $add[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + $sub = []; + $add = []; + $swap_detected = $swap_detected = $this->detect_swap_events($block_results['end_block_events'] ?? []); + foreach ($block_results['end_block_events'] ?? [] as $eb_event) + { + switch($eb_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($eb_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($eb_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $add[] = [ + 'address' => $coin_received_data['to'], + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($eb_event['attributes']); + if (is_null($coinbase_data)) + break; + + $ibc_amount = $this->denom_amount_to_ibc_amount($coinbase_data['amount']); + if (is_null($ibc_amount)) + break; + + $currencies_to_process[] = $ibc_amount['currency']; + + $sub[] = [ + 'address' => 'the-void', + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($eb_event['attributes']); + if (is_null($burn_data)) + break; + + $ibc_amount = $this->denom_amount_to_ibc_amount($burn_data['amount']); + if (is_null($ibc_amount)) + break; + + $currencies_to_process[] = $ibc_amount['currency']; + + $add[] = [ + 'address' => 'the-void', + 'currency' => $ibc_amount['currency'], + 'amount' => $ibc_amount['amount'], + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($eb_event['attributes']); + if (is_null($transfer_data)) + break; + + if ($swap_detected && is_null($transfer_data['from'])) + $transfer_data['from'] = 'swap-pool'; + + foreach ($transfer_data['amount'] as $amount) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($amount); + if (is_null($ibc_amount)) + continue; // Skip none ibc amounts + + $currencies_to_process[] = $ibc_amount['currency']; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'currency' => $ibc_amount['currency'], + 'effect' => '-' . $ibc_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'currency' => $ibc_amount['currency'], + 'effect' => $ibc_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (end block events)."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['currency'] !== $add[$event_i]['currency']) + throw new ModuleException("Sub and add currency missmatch."); + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'currency' => $sub[$event_i]['currency'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'currency' => $add[$event_i]['currency'], + 'effect' => $add[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $currencies_to_process = array_unique($currencies_to_process); + $currencies_to_process = check_existing_currencies($currencies_to_process, $this->currency_format); + + foreach ($currencies_to_process as $currency) + { + // Not possible to get IBC token info from Tendermint API. + $ibc_hash = explode("_", $currency)[1]; + $denom_trace = requester_single($this->rpc_node, endpoint: "ibc/apps/transfer/v1/denom_traces/{$ibc_hash}", timeout: $this->timeout); + $currencies[] = [ + 'id' => $currency, + 'name' => $denom_trace['denom_trace']['base_denom'] ?? '', + 'description' => $denom_trace['denom_trace']['path'] ?? '', + 'decimals' => 6, // IBC tokens == native tokens from other cosmos sdk chains + ]; + } + + $this->set_return_events($events); + $this->set_return_currencies($currencies); + } + + // Getting balances from the node + public function api_get_balance(string $address, array $currencies): array + { + // Input currencies should be in format like this: `{module}/ibc_E92E07E68705FAD13305EE9C73684B30A7B66A52F54C9890327E0A4C0F1D22E3` + $denoms_to_find = []; + foreach ($currencies as $currency) + { + $denoms_to_find[] = explode('/', $currency)[1]; + } + + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}", timeout: $this->timeout); + + $balances_from_node = []; + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + + // Check pagination + while (!is_null($data['pagination']['next_key'])) + { + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}?pagination.key={$data['pagination']['next_key']}", timeout: $this->timeout); + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + } + + $return = []; + foreach ($denoms_to_find as $denom) + { + $return[] = $balances_from_node[$denom] ?? '0'; + } + + return $return; + } +} diff --git a/Modules/Common/CosmosMainModule.php b/Modules/Common/CosmosMainModule.php new file mode 100644 index 00000000..9be62f24 --- /dev/null +++ b/Modules/Common/CosmosMainModule.php @@ -0,0 +1,628 @@ + exponent] ex. [uatom => 0] means 123uatom = 123 * 10^0 ATOM + public ?array $cosmos_known_denoms = null; + + // Since this block appeared coin_spent/coin_received events in x/bank module + // value 0 if there is no such fork + public ?int $cosmos_coin_events_fork = null; + + public array $extra_features = []; + + final public function pre_initialize() + { + $this->version = 1; + } + + final public function post_post_initialize() + { + if (is_null($this->cosmos_special_addresses)) + throw new DeveloperError("`cosmos_special_addresses` is not set (developer error)"); + if (!array_key_exists('fee_collector', $this->cosmos_special_addresses)) + $this->cosmos_special_addresses['fee_collector'] = ''; + + if (is_null($this->cosmos_known_denoms)) + throw new DeveloperError("`cosmos_known_denoms` is not set (developer error)"); + + if (is_null($this->cosmos_coin_events_fork)) + throw new DeveloperError("`cosmos_coin_events_fork` is not set (deleloper error)"); + + $this->rpc_node = envm( + $this->module, + 'RPC_NODE', + new DeveloperError('RPC_NODE not set in the config') + ); + } + + final public function pre_process_block($block_id) + { + $block_data = requester_single($this->select_node(), endpoint: "block?height={$block_id}", timeout: $this->timeout); + $block_data = $block_data['result'] ?? $block_data; + $block_results = requester_single($this->select_node(), endpoint: "block_results?height={$block_id}", timeout: $this->timeout); + $block_results = $block_results['result'] ?? $block_results; + + if (($tx_count = count($block_data['block']['data']['txs'] ?? [])) !== count($block_results['txs_results'] ?? [])) + throw new ModuleException("TXs count and TXs results count mismatch!"); + + $events = []; + $sort_key = 0; + + // Parsing coin_spent/coin_received events because it is guarantees to find all monetary transactions. + + // Process each transaction results. + for ($i = 0; $i < $tx_count; $i++) + { + $tx_hash = $this->get_tx_hash($block_data['block']['data']['txs'][$i]); + $tx_result = $block_results['txs_results'][$i]; + + if (in_array(CosmosSpecialFeatures::HasNotCodeField, $this->extra_features)) + $failed = (int)isset($tx_result['code']) ? (int)$tx_result['code'] !== 0 : false; + else + $failed = (int)$tx_result['code'] === 0 ? false : true; + + // Need to collect fee and fee_payer before parsing events. + $fee_event_detected = ['from' => false, 'to' => false]; // To avoid double extra + $fee_info = $this->try_find_fee_info($tx_result['events'] ?? []); + + if (in_array(CosmosSpecialFeatures::HasDoublesTxEvents, $this->extra_features)) + { + $this->erase_double_fee_events($tx_result['events']); + } + + foreach ($tx_result['events'] ?? [] as $tx_event) + { + switch ($tx_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($tx_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $extra = null; + if (!is_null($fee_info) && !$fee_event_detected['from']) + { + if ($coin_spent_data['from'] === $fee_info['fee_payer'] && + $amount === $fee_info['fee']) + { + $extra = 'f'; + $fee_event_detected['from'] = true; + } + } + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $coin_spent_data['from'], + 'effect' => '-' . $amount, + 'failed' => $failed, + 'extra' => $extra, + ]; + + // Additional event for fee_collector + if (!is_null($fee_info) && in_array(CosmosSpecialFeatures::HasNotFeeCollectorRecvEvent, $this->extra_features) && $fee_event_detected['to'] == false) + { + $fee_event_detected['to'] = true; + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $this->cosmos_special_addresses['fee_collector'], + 'effect' => $amount, + 'failed' => $failed, + 'extra' => $extra, + ]; + } + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($tx_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $extra = null; + if (!is_null($fee_info) && !$fee_event_detected['to']) + { + if ($coin_received_data['to'] === $this->cosmos_special_addresses['fee_collector'] && + $amount === $fee_info['fee']) + { + $extra = 'f'; + $fee_event_detected['to'] = true; + } + } + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $coin_received_data['to'], + 'effect' => $amount, + 'failed' => $failed, + 'extra' => $extra, + ]; + } + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($tx_event['attributes']); + if (is_null($burn_data)) + break; + + $amount = $this->denom_amount_to_amount($burn_data['amount']); + if (is_null($amount)) + break; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => 'the-void', + 'effect' => $amount, + 'failed' => $failed, + 'extra' => null, + ]; + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($tx_event['attributes']); + if (is_null($coinbase_data)) + break; + + $amount = $this->denom_amount_to_amount($coinbase_data['amount']); + if (is_null($amount)) + break; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => 'the-void', + 'effect' => '-' . $amount, + 'failed' => $failed, + 'extra' => null, + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($tx_event['attributes']); + if (is_null($transfer_data)) + break; + + foreach ($transfer_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $extra = null; + if ($transfer_data['to'] === $this->cosmos_special_addresses['fee_collector']) + { + $extra = 'f'; + $fee_info['fee_payer'] = $transfer_data['from']; + $fee_event_detected['from'] = true; + $fee_event_detected['to'] = true; + } + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'] ?? $fee_info['fee_payer'], // fee_payer for multi transfers + 'effect' => '-' . $amount, + 'failed' => $failed, + 'extra' => $extra, + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'effect' => $amount, + 'failed' => $failed, + 'extra' => $extra, + ]; + } + + break; + } + } + + // Additional events for zero fee transactions + if (!$fee_event_detected['from'] && !$fee_event_detected['to']) + { + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $fee_info['fee_payer'] ?? 'the-void', + 'effect' => '-0', + 'failed' => $failed, + 'extra' => 'f', + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $this->cosmos_special_addresses['fee_collector'], + 'effect' => '0', + 'failed' => $failed, + 'extra' => 'f', + ]; + } + } + + // Block header events parsing + + // For header events may be not correct order for coin_spent/received events + $sub = []; + $add = []; + foreach ($block_results['begin_block_events'] ?? [] as $bb_event) + { + switch ($bb_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($bb_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'amount' => $amount, + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($bb_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $add[] = [ + 'address' => $coin_received_data['to'], + 'amount' => $amount, + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($bb_event['attributes']); + if (is_null($coinbase_data)) + break; + + $amount = $this->denom_amount_to_amount($coinbase_data['amount']); + if (is_null($amount)) + break; + + $sub[] = [ + 'address' => 'the-void', + 'amount' => $amount, + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($bb_event['attributes']); + if (is_null($burn_data)) + break; + + $amount = $this->denom_amount_to_amount($burn_data['amount']); + if (is_null($amount)) + break; + + $add[] = [ + 'address' => 'the-void', + 'amount' => $amount, + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($bb_event['attributes']); + if (is_null($transfer_data)) + break; + + foreach ($transfer_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'effect' => '-' . $amount, + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'effect' => $amount, + 'failed' => false, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (end block events)."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'effect' => $add[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + $sub = []; + $add = []; + $swap_detected = $this->detect_swap_events($block_results['end_block_events'] ?? []); + foreach ($block_results['end_block_events'] ?? [] as $eb_event) + { + switch($eb_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($eb_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'amount' => $amount, + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($eb_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $add[] = [ + 'address' => $coin_received_data['to'], + 'amount' => $amount, + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($eb_event['attributes']); + if (is_null($coinbase_data)) + break; + + $amount = $this->denom_amount_to_amount($coinbase_data['amount']); + if (is_null($amount)) + break; + + $sub[] = [ + 'address' => 'the-void', + 'amount' => $amount, + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($eb_event['attributes']); + if (is_null($burn_data)) + break; + + $amount = $this->denom_amount_to_amount($burn_data['amount']); + if (is_null($amount)) + break; + + $add[] = [ + 'address' => 'the-void', + 'amount' => $amount, + ]; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($eb_event['attributes']); + if (is_null($transfer_data)) + break; + + if ($swap_detected && is_null($transfer_data['from'])) + $transfer_data['from'] = 'swap-pool'; + + foreach ($transfer_data['amount'] as $amount) + { + $amount = $this->denom_amount_to_amount($amount); + if (is_null($amount)) + continue; // In main module skip unknown denoms + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'effect' => '-' . $amount, + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'effect' => $amount, + 'failed' => false, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (end block events)."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'effect' => $add[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $this->set_return_events($events); + } + + // Getting balances from the node + public function api_get_balance(string $address): string + { + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}", timeout: $this->timeout); + foreach ($data['balances'] as $balance_data) + { + if ($balance_data['denom'] === 'uatom') + return $balance_data['amount']; + } + + // Check pagination + while (!is_null($data['pagination']['next_key'])) + { + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}?pagination.key={$data['pagination']['next_key']}", timeout: $this->timeout); + foreach ($data['balances'] as $balance_data) + { + if ($balance_data['denom'] === 'uatom') + return $balance_data['amount']; + } + } + + return '0'; + } +} diff --git a/Modules/Common/CosmosTokenFactoryModule.php b/Modules/Common/CosmosTokenFactoryModule.php new file mode 100644 index 00000000..5f3c5778 --- /dev/null +++ b/Modules/Common/CosmosTokenFactoryModule.php @@ -0,0 +1,655 @@ +version = 1; + } + + final public function post_post_initialize() + { + if (is_null($this->cosmos_coin_events_fork)) + throw new DeveloperError("`cosmos_coin_events_fork` is not set (deleloper error)"); + + $this->rpc_node = envm( + $this->module, + 'RPC_NODE', + new DeveloperError('RPC_NODE not set in the config') + ); + } + + final public function pre_process_block($block_id) + { + $block_data = requester_single($this->select_node(), endpoint: "block?height={$block_id}", timeout: $this->timeout); + $block_data = $block_data['result'] ?? $block_data; + $block_results = requester_single($this->select_node(), endpoint: "block_results?height={$block_id}", timeout: $this->timeout); + $block_results = $block_results['result'] ?? $block_results; + + if (($tx_count = count($block_data['block']['data']['txs'] ?? [])) !== count($block_results['txs_results'] ?? [])) + throw new ModuleException("TXs count and TXs results count mismatch!"); + + $events = []; + $currencies = []; + $currencies_to_process = []; + $sort_key = 0; + + // Process each transaction results. + for ($i = 0; $i < $tx_count; $i++) + { + $tx_hash = $this->get_tx_hash($block_data['block']['data']['txs'][$i]); + $tx_result = $block_results['txs_results'][$i]; + + if (in_array(CosmosSpecialFeatures::HasNotCodeField, $this->extra_features)) + $failed = (int)isset($tx_result['code']) ? true : false; + else + $failed = (int)$tx_result['code'] === 0 ? false : true; + + $sub = []; + $add = []; + foreach ($tx_result['events'] ?? [] as $tx_event) + { + switch ($tx_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($tx_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + 'extra' => null, + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($tx_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $add[] = [ + 'address' => $coin_received_data['to'], + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + 'extra' => null, + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($tx_event['attributes']); + if (is_null($coinbase_data)) + break; + + $tf_amount = $this->denom_amount_to_token_factory_amount($coinbase_data['amount']); + if (is_null($tf_amount)) + break; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $sub[] = [ + 'address' => 'the-void', + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + 'extra' => null, + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($tx_event['attributes']); + if (is_null($burn_data)) + break; + if (is_null($burn_data['from'])) // In case Sei duplicates 'burn' events + break; + + $tf_amount = $this->denom_amount_to_token_factory_amount($burn_data['amount']); + if (is_null($tf_amount)) + break; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $add[] = [ + 'address' => 'the-void', + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + 'extra' => null, + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($tx_event['attributes']); + if (is_null($transfer_data)) + break; + + foreach ($transfer_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'currency' => $tf_amount['currency'], + 'effect' => '-' . $tf_amount['amount'], + 'failed' => $failed, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'currency' => $tf_amount['currency'], + 'effect' => $tf_amount['amount'], + 'failed' => $failed, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (tx: {$tx_hash}."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['currency'] !== $add[$event_i]['currency']) + throw new ModuleException("Sub and add currency missmatch."); + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'currency' => $sub[$event_i]['currency'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => $failed, + 'extra' => $sub[$event_i]['extra'], + ]; + + $events[] = [ + 'transaction' => $tx_hash, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'currency' => $add[$event_i]['currency'], + 'effect' => $add[$event_i]['amount'], + 'failed' => $failed, + 'extra' => $add[$event_i]['extra'], + ]; + } + } + + // Block header events parsing + + $sub = []; + $add = []; + foreach ($block_results['begin_block_events'] ?? [] as $bb_event) + { + switch ($bb_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($bb_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($bb_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $add[] = [ + 'address' => $coin_received_data['to'], + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($bb_event['attributes']); + if (is_null($coinbase_data)) + break; + + $tf_amount = $this->denom_amount_to_token_factory_amount($coinbase_data['amount']); + if (is_null($tf_amount)) + break; + + $currencies_to_process[] = $tf_amount['currency']; + + $sub[] = [ + 'address' => 'the-void', + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($bb_event['attributes']); + if (is_null($burn_data)) + break; + + $tf_amount = $this->denom_amount_to_token_factory_amount($burn_data['amount']); + if (is_null($tf_amount)) + break; + + $currencies_to_process[] = $tf_amount['currency']; + + $add[] = [ + 'address' => 'the-void', + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($bb_event['attributes']); + if (is_null($transfer_data)) + break; + + foreach ($transfer_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'currency' => $tf_amount['currency'], + 'effect' => '-' . $tf_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'currency' => $tf_amount['currency'], + 'effect' => $tf_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (begin block events)."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['currency'] !== $add[$event_i]['currency']) + throw new ModuleException("Sub and add currency missmatch."); + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'currency' => $sub[$event_i]['currency'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'currency' => $add[$event_i]['currency'], + 'effect' => $add[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + $sub = []; + $add = []; + $swap_detected = $swap_detected = $this->detect_swap_events($block_results['end_block_events'] ?? []); + foreach ($block_results['end_block_events'] ?? [] as $eb_event) + { + switch($eb_event['type']) + { + case 'coin_spent': + $coin_spent_data = $this->parse_coin_spent_event($eb_event['attributes']); + if (is_null($coin_spent_data)) + break; + + foreach ($coin_spent_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $sub[] = [ + 'address' => $coin_spent_data['from'], + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + } + + break; + + case 'coin_received': + $coin_received_data = $this->parse_coin_received_event($eb_event['attributes']); + if (is_null($coin_received_data)) + break; + + foreach ($coin_received_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $add[] = [ + 'address' => $coin_received_data['to'], + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + } + + break; + + case 'coinbase': + $coinbase_data = $this->parse_coinbase_event($eb_event['attributes']); + if (is_null($coinbase_data)) + break; + + $tf_amount = $this->denom_amount_to_token_factory_amount($coinbase_data['amount']); + if (is_null($tf_amount)) + break; + + $currencies_to_process[] = $tf_amount['currency']; + + $sub[] = [ + 'address' => 'the-void', + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + + break; + + case 'burn': + $burn_data = $this->parse_burn_event($eb_event['attributes']); + if (is_null($burn_data)) + break; + + $tf_amount = $this->denom_amount_to_token_factory_amount($burn_data['amount']); + if (is_null($tf_amount)) + break; + + $currencies_to_process[] = $tf_amount['currency']; + + $add[] = [ + 'address' => 'the-void', + 'currency' => $tf_amount['currency'], + 'amount' => $tf_amount['amount'], + ]; + + break; + + case 'transfer': + if ($block_id >= $this->cosmos_coin_events_fork) + break; + + $transfer_data = $this->parse_transfer_event($eb_event['attributes']); + if (is_null($transfer_data)) + break; + + if ($swap_detected && is_null($transfer_data['from'])) + $transfer_data['from'] = 'swap-pool'; + + foreach ($transfer_data['amount'] as $amount) + { + $tf_amount = $this->denom_amount_to_token_factory_amount($amount); + if (is_null($tf_amount)) + continue; // Skip none token factory amounts + + $currencies_to_process[] = $tf_amount['currency']; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['from'], + 'currency' => $tf_amount['currency'], + 'effect' => '-' . $tf_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $transfer_data['to'], + 'currency' => $tf_amount['currency'], + 'effect' => $tf_amount['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + break; + } + } + + // To keep right events order + if (count($sub) !== count($add)) + throw new ModuleException("Deposits and withdrawals counts missmatch (end block events)."); + + for ($event_i = 0; $event_i < count($sub); $event_i++) + { + if ($sub[$event_i]['currency'] !== $add[$event_i]['currency']) + throw new ModuleException("Sub and add currency missmatch."); + if ($sub[$event_i]['amount'] !== $add[$event_i]['amount']) + throw new ModuleException("Sub and add amount missmatch."); + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $sub[$event_i]['address'], + 'currency' => $sub[$event_i]['currency'], + 'effect' => '-' . $sub[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + + $events[] = [ + 'transaction' => null, + 'sort_key' => $sort_key++, + 'address' => $add[$event_i]['address'], + 'currency' => $add[$event_i]['currency'], + 'effect' => $add[$event_i]['amount'], + 'failed' => false, + 'extra' => null, + ]; + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $currencies_to_process = array_unique($currencies_to_process); + $currencies_to_process = check_existing_currencies($currencies_to_process, $this->currency_format); + + foreach ($currencies_to_process as $currency) + { + $currencies[] = [ + 'id' => $currency, + 'name' => explode('_', $currency)[2], + 'decimals' => 6, // TokenFactory tokens have same decimals with native tokens + ]; + } + + $this->set_return_events($events); + $this->set_return_currencies($currencies); + } + + // Getting balances from the node + public function api_get_balance(string $address, array $currencies): array + { + // Input currencies should be in format like this: `{module}/factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei` + $denoms_to_find = []; + foreach ($currencies as $currency) + { + $denoms_to_find[] = explode('/', $currency)[1]; + } + + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}", timeout: $this->timeout); + + $balances_from_node = []; + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + + // Check pagination + while (!is_null($data['pagination']['next_key'])) + { + $data = requester_single($this->rpc_node, endpoint: "cosmos/bank/v1beta1/balances/{$address}?pagination.key={$data['pagination']['next_key']}", timeout: $this->timeout); + foreach ($data['balances'] as $balance_data) + { + $denom = str_replace('/', '_', $balance_data['denom']); + $balances_from_node[$denom] = $balance_data['amount']; + } + } + + $return = []; + foreach ($denoms_to_find as $denom) + { + $return[] = $balances_from_node[$denom] ?? '0'; + } + + return $return; + } +} diff --git a/Modules/Common/CosmosTraits.php b/Modules/Common/CosmosTraits.php new file mode 100644 index 00000000..31863b7b --- /dev/null +++ b/Modules/Common/CosmosTraits.php @@ -0,0 +1,625 @@ +select_node(), endpoint: 'status', timeout: $this->timeout); + $response = $response['result'] ?? $response; + return (int) $response['sync_info']['latest_block_height']; + } + + public function ensure_block($block_id, $break_on_first = false) + { + $multi_curl = []; + foreach ($this->nodes as $node) + { + $multi_curl[] = requester_multi_prepare($node, endpoint: "block?height={$block_id}", timeout: $this->timeout); + } + + try + { + $curl_results = requester_multi($multi_curl, limit: count($this->nodes), timeout: $this->timeout); + } + catch (RequesterException $e) + { + throw new RequesterException("ensure_block(block_id: {$block_id}): no connection, previously: " . $e->getMessage()); + } + + if (count($curl_results) !== 0) + { + $result = requester_multi_process($curl_results[0]); + $result = $result['result'] ?? $result; + $this->block_hash = $result['block_id']['hash']; + $this->block_time = date('Y-m-d H:i:s', strtotime($result['block']['header']['time'])); + foreach ($curl_results as $curl_result) + { + $result1 = requester_multi_process($curl_result); + $result1 = $result1['result'] ?? $result1; + if ($result1['block_id']['hash'] !== $this->block_hash) + { + throw new ConsensusException("ensure_block(block_id: {$block_id}): no consensus"); + } + } + } + } + + // Converts tx_data from /block api into tx_hash + function get_tx_hash(?string $tx_data): ?string + { + if (is_null($tx_data)) + return null; + return hash('sha256', base64_decode($tx_data)); + } + + // Looking for fee info in tx events (fee in native coins) + function try_find_fee_info(?array $tx_events): ?array + { + $fee_info = ['fee' => null, 'fee_payer' => null]; + if (count($tx_events) === 0) + return $fee_info; + + foreach ($tx_events as $tx_event) + { + if ($tx_event['type'] === 'use_feegrant') + { + foreach ($tx_event['attributes'] as $attr) + { + switch ($attr['key']) + { + case 'granter': + case 'Z3JhbnRlcg==': // granter + $fee_info['fee_payer'] = $this->try_base64_decode($attr['value']); + break; + } + } + } + + if ($tx_event['type'] === 'tx') + { + foreach ($tx_event['attributes'] as $attr) + { + switch ($attr['key']) + { + case 'fee': + case 'ZmVl': // fee + if (is_null($attr['value'])) // zero fee for tx + $fee_info['fee'] = '0'; + else + $fee_info['fee'] = $this->denom_amount_to_amount($this->try_base64_decode($attr['value'])); + break; + + case 'fee_payer': + case 'ZmVlX3BheWVy': // fee_payer + if (is_null($fee_info['fee_payer'])) + $fee_info['fee_payer'] = $this->try_base64_decode($attr['value']); + break; + + case 'acc_seq': + case 'YWNjX3NlcQ==': // acc_seq in format {addr}/{num} + if (is_null($fee_info['fee_payer'])) + $fee_info['fee_payer'] = explode('/', $this->try_base64_decode($attr['value']))[0]; + break; + } + } + } + + if (!is_null($fee_info['fee']) && !is_null($fee_info['fee_payer'])) + return $fee_info; + } + + // Cannot find fee_info for none empty tx + return null; + } + + // Looking for fee info in tx events (in case fee is not in native coins) + function try_find_ibc_fee_info(?array $tx_events): ?array + { + $fee_info = ['fee' => null, 'fee_payer' => null, 'fee_currency' => null]; + if (count($tx_events) === 0) + return $fee_info; + + foreach ($tx_events as $tx_event) + { + if ($tx_event['type'] === 'use_feegrant') + { + foreach ($tx_event['attributes'] as $attr) + { + switch ($attr['key']) + { + case 'granter': + case 'Z3JhbnRlcg==': // granter + $fee_info['fee_payer'] = $this->try_base64_decode($attr['value']); + break; + } + } + } + + if ($tx_event['type'] === 'tx') + { + foreach ($tx_event['attributes'] as $attr) + { + switch ($attr['key']) { + case 'fee': + case 'ZmVl': // fee + if (!is_null($attr['value'])) + { + $ibc_amount = $this->denom_amount_to_ibc_amount($this->try_base64_decode($attr['value'])); + if (!is_null($ibc_amount)) + { + $fee_info['fee'] = $ibc_amount['amount']; + $fee_info['fee_currency'] = $ibc_amount['currency']; + } + } + break; + + case 'fee_payer': + case 'ZmVlX3BheWVy': // fee_payer + if (is_null($fee_info['fee_payer'])) + $fee_info['fee_payer'] = $this->try_base64_decode($attr['value']); + break; + + case 'acc_seq': + case 'YWNjX3NlcQ==': // acc_seq in format {addr}/{num} + if (is_null($fee_info['fee_payer'])) + $fee_info['fee_payer'] = explode('/', $this->try_base64_decode($attr['value']))[0]; + break; + } + } + } + + if (!is_null($fee_info['fee']) && !is_null($fee_info['fee_payer'])) + return $fee_info; + } + + // Cannot find fee_info for none empty tx + return null; + } + + // Returns null|array('from' => addr, 'amount' => string_array) + function parse_coin_spent_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for coin_spent parsing (is null)!'); + + $result = ['from' => null, 'amount' => null]; + foreach ($attributes as $attr) + { + if (is_null($attr['value'])) + return null; + + switch ($attr['key']) + { + case 'spender': + case 'c3BlbmRlcg==': // spender + $result['from'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $result['amount'] = explode(',', $this->try_base64_decode($attr['value'])); + break; + } + } + + return $result; + } + + // Returns null|array('to' => addr, 'amount' => string_array) + function parse_coin_received_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for coin_received parsing (is null)!'); + + $result = ['to' => null, 'amount' => null]; + foreach ($attributes as $attr) + { + if (is_null($attr['value'])) + return null; + + switch ($attr['key']) + { + case 'receiver': + case 'cmVjZWl2ZXI=': // receiver + $result['to'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $result['amount'] = explode(',', $this->try_base64_decode($attr['value'])); + break; + } + } + + return $result; + } + + // Returns null|array('from' => addr, 'amount' => string_array) + function parse_coinbase_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for coinbase parsing (is null)!'); + + $result = ['from' => null, 'amount' => null]; + foreach ($attributes as $attr) + { + switch ($attr['key']) + { + case 'minter': + case 'bWludGVy': // minter + $result['from'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $result['amount'] = $this->try_base64_decode($attr['value']); + break; + } + } + + return $result; + } + + // Returns null|array('from' => addr, 'amount' => string_array) + function parse_burn_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for burn parsing (is null)!'); + + $return = ['from' => null, 'amount' => null, 'extra' => null]; + foreach ($attributes as $attr) + { + switch ($attr['key']) + { + case 'burner': + case 'YnVybmVy': // burner + $return['from'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $return['amount'] = $this->try_base64_decode($attr['value']); + break; + } + } + + return $return; + } + + // Returns null|array('from' => addr, 'to' => addr, 'amount' => string_array) + function parse_transfer_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for coin_received parsing (is null)!'); + + $result = ['from' => null, 'to' => null, 'amount' => null]; + foreach ($attributes as $attr) + { + if (is_null($attr['value'])) + return null; + + switch ($attr['key']) + { + case 'sender': + case 'c2VuZGVy': // sender + $result['from'] = $this->try_base64_decode($attr['value']); + break; + + case 'recipient': + case 'cmVjaXBpZW50': // recipient + $result['to'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $result['amount'] = explode(',', $this->try_base64_decode($attr['value'])); + break; + } + } + + return $result; + } + + // Returns null|array('from' => addr, 'to' => addr, 'amount' => string, 'currency' => addr, 'extra' => string) + function parse_wasm_cw20_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for wasm parsing (is null)!'); + + $result = ['from' => null, 'to' => null, 'amount' => null, 'currency' => null, 'extra' => null]; + $action = null; + foreach ($attributes as $attr) + { + switch ($attr['key']) + { + case '_contract_address': + case 'X2NvbnRyYWN0X2FkZHJlc3M=': // _contract_address + $result['currency'] = $this->try_base64_decode($attr['value']); + break; + + case 'action': + case 'YWN0aW9u': // action + $action = $this->try_base64_decode($attr['value']); + switch ($action) + { + case 'transfer': + case 'send': + case 'transfer_from': + break; + + case 'mint': + $result['extra'] = CosmosSpecialTransactions::Mint->value; + $result['from'] = 'the-void'; + break; + + case 'burn': + case 'burn_from': + $result['extra'] = CosmosSpecialTransactions::Burn->value; + $result['to'] = 'the-void'; + break; + + default: + return null; // Skips not cw20 actions + } + + break; + + case 'from': + case 'ZnJvbQ==': // from + $result['from'] = $this->try_base64_decode($attr['value']); + break; + + case 'to': + case 'dG8=': // to + $result['to'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $result['amount'] = $this->try_base64_decode($attr['value']); + break; + } + } + + // 'action' is necessary field + if (is_null($action)) + return null; + + // Check for cw721 mint/burn events and skip + if ($result['from'] === 'the-void' && is_null($result['amount'])) + return null; + if ($result['to'] === 'the-void' && is_null($result['amount'])) + return null; + + // Additional checks for not cw20 events + if (is_null($result['from']) || is_null($result['to']) || is_null($result['currency'])) + return null; + + return $result; + } + + // Returns null|array('from' => addr, 'to' => addr, 'currency' => addr, 'extra' => token_id_string) + function parse_wasm_cw721_event(?array $attributes): ?array + { + if (is_null($attributes)) + throw new ModuleException('Invalid `attributes` for wasm parsing (is null)!'); + + $result = ['from' => null, 'to' => null, 'currency' => null, 'extra' => null]; + $action = null; + $minter = null; + $owner = null; + $amount_presents = false; + foreach ($attributes as $attr) + { + switch ($attr['key']) + { + case '_contract_address': + case 'X2NvbnRyYWN0X2FkZHJlc3M=': // _contract_address + $result['currency'] = $this->try_base64_decode($attr['value']); + break; + + case 'action': + case 'YWN0aW9u': // action + $action = $this->try_base64_decode($attr['value']); + switch ($action) + { + case 'transfer_nft': + case 'send_nft': + break; + + case 'mint': + $result['from'] = 'the-void'; + break; + + case 'burn': + $result['to'] = 'the-void'; + break; + + default: + return null; // Skips not cw721 actions + } + + break; + + case 'minter': + case 'bWludGVy': // minter + $minter = $this->try_base64_decode($attr['value']); + break; + + case 'owner': + case 'b3duZXI=': // owner + $owner = $this->try_base64_decode($attr['value']); + break; + + case 'sender': + case 'c2VuZGVy': // sender + $result['from'] = $this->try_base64_decode($attr['value']); + break; + + case 'recipient': + case 'cmVjaXBpZW50': // recipient + $result['to'] = $this->try_base64_decode($attr['value']); + break; + + case 'token_id': + case 'dG9rZW5faWQ=': // token_id + $result['extra'] = $this->try_base64_decode($attr['value']); + break; + + case 'amount': + case 'YW1vdW50': // amount + $amount_presents = true; + break; + } + } + + // 'action' is necessary field + if (is_null($action)) + return null; + + // Check for cw20 mint/burn events and skip + if ($result['from'] === 'the-void' && $amount_presents) + return null; + if ($result['to'] === 'the-void' && $amount_presents) + return null; + + // Clarifies 'to' for mint event + if ($result['from'] === 'the-void') + $result['to'] = is_null($owner) ? $minter : $owner; + + // Additional checks for not documented cw721 event + if (is_null($result['from']) || is_null($result['to']) || is_null($result['currency']) || is_null($result['extra'])) + return null; + + return $result; + } + + // Detects swap operation in early blocks to set up special addresses. + function detect_swap_events(?array $tx_events): bool + { + foreach ($tx_events as $tx_event) + { + switch ($tx_event['type']) + { + case 'swap_transacted': + case 'deposit_to_pool': + case 'withdraw_from_pool': + return true; + } + } + + return false; + } + + // Parse the knowning denoms from $this->cosmos_known_denoms + // denom_amount format: {amount}{denom} (ex. 1234uatom) + function denom_amount_to_amount(?string $denom_amount): ?string + { + if ($denom_amount === '') + return '0'; + + if (str_contains($denom_amount, ',')) + throw new ModuleException("Expected single denom amount, array detected."); + + $parts = preg_split('/(?<=[0-9])(?=[a-z]+)/i', $denom_amount, limit: 2); + if (!is_numeric($parts[0])) + throw new ModuleException("Invalid denom amount format (not numeric): {$denom_amount}"); + + if (!array_key_exists($parts[1], $this->cosmos_known_denoms)) + return null; + + return $parts[0] . str_repeat('0', $this->cosmos_known_denoms[$parts[1]]); + } + + // Parse {amount}ibc/{denom} to null|array('amount' => string, 'currency' => string) + function denom_amount_to_ibc_amount(?string $denom_amount): ?array + { + if ($denom_amount === '') + return null; + + if (str_contains($denom_amount, ',')) + throw new ModuleException("Expected single denom amount, array detected."); + + $parts = preg_split('/(?<=[0-9])(?=[a-z]+)/i', $denom_amount, limit: 2); + if (!is_numeric($parts[0])) + throw new ModuleException("Invalid denom amount format (not numeric): {$denom_amount}"); + + if (!str_contains($parts[1], 'ibc/')) + return null; + + // Replace ibc/ to ibc_ + return ['amount' => $parts[0], 'currency' => str_replace('/', '_', $parts[1])]; + } + + // Parse {amount}factory/{creator}/{denom} to null|array('amount' => string, 'currency' => string, 'name' => string) + function denom_amount_to_token_factory_amount(?string $denom_amount): ?array + { + if ($denom_amount === '') + return null; + + if (str_contains($denom_amount, ',')) + throw new ModuleException("Expected single denom amount, array detected."); + + $parts = preg_split('/(?<=[0-9])(?=[a-z]+)/i', $denom_amount, limit: 2); + + if (!is_numeric($parts[0])) + throw new ModuleException("Invalid denom amount format (not numeric): {$denom_amount}"); + + if (!str_contains($parts[1], 'factory/')) + return null; + + return ['amount' => $parts[0], 'currency' => str_replace('/', '_', $parts[1])]; + } + + // In some chains may be doubled events for fee paying. + function erase_double_fee_events(array &$events) { + if (empty($events)) + return; + + // Check 'tx' events in the end of the list exists + if ($events[count($events) - 1]['type'] !== 'tx') + return; + + $erase_index = null; + for ($i = count($events) - 1; $i >= 0; $i--) + { + if ($events[$i]['type'] === 'coin_spent') + { + $erase_index = count($events) - $i; + break; + } + } + + // Erase the exact events from end of list + $events = array_slice($events, 0, -$erase_index); + } + + function try_base64_decode($data): string + { + return in_array(CosmosSpecialFeatures::HasDecodedValues, $this->extra_features) ? $data : base64_decode($data); + } +} diff --git a/Modules/CosmosHubIBCModule.php b/Modules/CosmosHubIBCModule.php new file mode 100644 index 00000000..d5061cdc --- /dev/null +++ b/Modules/CosmosHubIBCModule.php @@ -0,0 +1,26 @@ +blockchain = 'cosmos-hub'; + $this->module = 'cosmos-hub-ibc'; + $this->is_main = false; + $this->first_block_date = '2019-12-11'; + // cosmoshub-4 started with 5200791 + $this->first_block_id = 5200791; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + // https://github.com/cosmos/cosmos-sdk/pull/8656 + $this->cosmos_coin_events_fork = 8695000; + } +} diff --git a/Modules/CosmosHubMainModule.php b/Modules/CosmosHubMainModule.php new file mode 100644 index 00000000..2b919ac7 --- /dev/null +++ b/Modules/CosmosHubMainModule.php @@ -0,0 +1,32 @@ +blockchain = 'cosmos-hub'; + $this->module = 'cosmos-hub-main'; + $this->is_main = true; + $this->first_block_date = '2019-12-11'; + // cosmoshub-4 started with 5200791 + $this->first_block_id = 5200791; + $this->currency = 'atom'; + $this->currency_details = ['name' => 'Atom', 'symbol' => 'ATOM', 'decimals' => 6, 'description' => null]; + + // Cosmos-specific + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta' + ]; + $this->cosmos_known_denoms = ['uatom' => 0]; + // https://github.com/cosmos/cosmos-sdk/pull/8656 + $this->cosmos_coin_events_fork = 8695000; + } +} diff --git a/Modules/CronosPOSIBCModule.php b/Modules/CronosPOSIBCModule.php new file mode 100644 index 00000000..10cf9f56 --- /dev/null +++ b/Modules/CronosPOSIBCModule.php @@ -0,0 +1,26 @@ +blockchain = 'cronos-pos'; + $this->module = 'cronos-pos-ibc'; + $this->is_main = false; + $this->first_block_id = 1; + $this->first_block_date = '2021-03-25'; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + // since: https://github.com/crypto-org-chain/chain-main/releases/tag/v3.0.0 + // Cronos POS supports SDK 0.43.0 + $this->cosmos_coin_events_fork = 3526800; + } +} diff --git a/Modules/CronosPOSMainModule.php b/Modules/CronosPOSMainModule.php new file mode 100644 index 00000000..bf131950 --- /dev/null +++ b/Modules/CronosPOSMainModule.php @@ -0,0 +1,39 @@ +blockchain = 'cronos-pos'; + $this->module = 'cronos-pos-main'; + $this->is_main = true; + $this->first_block_id = 1; + $this->first_block_date = '2021-03-25'; + $this->currency = 'cronos'; + $this->currency_details = ['name' => 'Cronos', 'symbol' => 'CRO', 'decimals' => 8, 'description' => null]; + + // Cosmos-specific + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv' + ]; + $this->cosmos_known_denoms = ['basecro' => 0]; + // since: https://github.com/crypto-org-chain/chain-main/releases/tag/v3.0.0 + // Cronos POS supports SDK 0.43.0 + $this->cosmos_coin_events_fork = 3526800; + + $this->tests = [ + // Block after Bank fork + ['block' => 15722786, 'result' => 'a:2:{s:6:"events";a:12:{i:0;a:8:{s:11:"transaction";s:64:"51b7fd65dd262c1577cc9815dfbea14578ac51380bc757931be79fa2a715260f";s:8:"sort_key";i:0;s:7:"address";s:42:"cro1y9mgq4htwehpv9cvs6ydp9uwz93dj0zqwklvau";s:6:"effect";s:6:"-20000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:1;a:8:{s:11:"transaction";s:64:"51b7fd65dd262c1577cc9815dfbea14578ac51380bc757931be79fa2a715260f";s:8:"sort_key";i:1;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:5:"20000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:2;a:8:{s:11:"transaction";s:64:"51b7fd65dd262c1577cc9815dfbea14578ac51380bc757931be79fa2a715260f";s:8:"sort_key";i:2;s:7:"address";s:42:"cro1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8lyv94w";s:6:"effect";s:11:"-1852327311";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:3;a:8:{s:11:"transaction";s:64:"51b7fd65dd262c1577cc9815dfbea14578ac51380bc757931be79fa2a715260f";s:8:"sort_key";i:3;s:7:"address";s:42:"cro1y9mgq4htwehpv9cvs6ydp9uwz93dj0zqwklvau";s:6:"effect";s:10:"1852327311";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:4;a:8:{s:11:"transaction";s:64:"51b7fd65dd262c1577cc9815dfbea14578ac51380bc757931be79fa2a715260f";s:8:"sort_key";i:4;s:7:"address";s:42:"cro1y9mgq4htwehpv9cvs6ydp9uwz93dj0zqwklvau";s:6:"effect";s:11:"-1852323393";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:5;a:8:{s:11:"transaction";s:64:"51b7fd65dd262c1577cc9815dfbea14578ac51380bc757931be79fa2a715260f";s:8:"sort_key";i:5;s:7:"address";s:42:"cro1fl48vsnmsdzcv85q5d2q4z5ajdha8yu3dqpk9x";s:6:"effect";s:10:"1852323393";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:6;a:8:{s:11:"transaction";N;s:8:"sort_key";i:6;s:7:"address";s:8:"the-void";s:6:"effect";s:12:"-10486209896";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:7;a:8:{s:11:"transaction";N;s:8:"sort_key";i:7;s:7:"address";s:42:"cro1m3h30wlvsf8llruxtpukdvsy0km2kum8s20pm3";s:6:"effect";s:11:"10486209896";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:8;a:8:{s:11:"transaction";N;s:8:"sort_key";i:8;s:7:"address";s:42:"cro1m3h30wlvsf8llruxtpukdvsy0km2kum8s20pm3";s:6:"effect";s:12:"-10486209896";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:9;a:8:{s:11:"transaction";N;s:8:"sort_key";i:9;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:11:"10486209896";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:10;a:8:{s:11:"transaction";N;s:8:"sort_key";i:10;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:12:"-10486242396";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}i:11;a:8:{s:11:"transaction";N;s:8:"sort_key";i:11;s:7:"address";s:42:"cro1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8lyv94w";s:6:"effect";s:11:"10486242396";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:15722786;s:4:"time";s:19:"2024-01-26 06:11:44";}}s:10:"currencies";N;}'], + // Block before Bank fork + ['block' => 3526797, 'result' => 'a:2:{s:6:"events";a:14:{i:0;a:8:{s:11:"transaction";s:64:"ff3bd86a7686fd722e45dcc2032bdb9c88ce4596bfe36ad95783ecbbf9dcb772";s:8:"sort_key";i:0;s:7:"address";s:42:"cro1lvk850fd64lga5yue6er893pwret2ttev893t4";s:6:"effect";s:6:"-20000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:1;a:8:{s:11:"transaction";s:64:"ff3bd86a7686fd722e45dcc2032bdb9c88ce4596bfe36ad95783ecbbf9dcb772";s:8:"sort_key";i:1;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:5:"20000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:2;a:8:{s:11:"transaction";s:64:"ff3bd86a7686fd722e45dcc2032bdb9c88ce4596bfe36ad95783ecbbf9dcb772";s:8:"sort_key";i:2;s:7:"address";s:42:"cro1lvk850fd64lga5yue6er893pwret2ttev893t4";s:6:"effect";s:13:"-821200000000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:3;a:8:{s:11:"transaction";s:64:"ff3bd86a7686fd722e45dcc2032bdb9c88ce4596bfe36ad95783ecbbf9dcb772";s:8:"sort_key";i:3;s:7:"address";s:42:"cro1hcea3h0ykw4jqyjhj3tnydsk22s06adhd4ul3z";s:6:"effect";s:12:"821200000000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:4;a:8:{s:11:"transaction";s:64:"a7485296b6fa762b468a25730b6549dfb0eb49cee0cf2545ab1ca34dde4a7cc3";s:8:"sort_key";i:4;s:7:"address";s:42:"cro1q040rm026jmpfmxdsj6q9phm9tdceepnsau6me";s:6:"effect";s:5:"-4152";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:5;a:8:{s:11:"transaction";s:64:"a7485296b6fa762b468a25730b6549dfb0eb49cee0cf2545ab1ca34dde4a7cc3";s:8:"sort_key";i:5;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:4:"4152";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:6;a:8:{s:11:"transaction";s:64:"3f4794ad0ed029c3bd1ff4852b4c8c3e5aaf3c74f44f2c79db4241a763814e24";s:8:"sort_key";i:6;s:7:"address";s:42:"cro1ghd7rv6zg7pqhu8v2e4s9j0vy0x4edcgul6lmk";s:6:"effect";s:6:"-20000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:7;a:8:{s:11:"transaction";s:64:"3f4794ad0ed029c3bd1ff4852b4c8c3e5aaf3c74f44f2c79db4241a763814e24";s:8:"sort_key";i:7;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:5:"20000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:8;a:8:{s:11:"transaction";s:64:"3f4794ad0ed029c3bd1ff4852b4c8c3e5aaf3c74f44f2c79db4241a763814e24";s:8:"sort_key";i:8;s:7:"address";s:42:"cro1ghd7rv6zg7pqhu8v2e4s9j0vy0x4edcgul6lmk";s:6:"effect";s:10:"-400000000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:9;a:8:{s:11:"transaction";s:64:"3f4794ad0ed029c3bd1ff4852b4c8c3e5aaf3c74f44f2c79db4241a763814e24";s:8:"sort_key";i:9;s:7:"address";s:42:"cro1hcea3h0ykw4jqyjhj3tnydsk22s06adhd4ul3z";s:6:"effect";s:9:"400000000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:10;a:8:{s:11:"transaction";N;s:8:"sort_key";i:10;s:7:"address";s:42:"cro1m3h30wlvsf8llruxtpukdvsy0km2kum8s20pm3";s:6:"effect";s:11:"-8767439799";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:11;a:8:{s:11:"transaction";N;s:8:"sort_key";i:11;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:10:"8767439799";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:12;a:8:{s:11:"transaction";N;s:8:"sort_key";i:12;s:7:"address";s:42:"cro17xpfvakm2amg962yls6f84z3kell8c5lgztehv";s:6:"effect";s:11:"-8767547664";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}i:13;a:8:{s:11:"transaction";N;s:8:"sort_key";i:13;s:7:"address";s:42:"cro1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8lyv94w";s:6:"effect";s:10:"8767547664";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:3526797;s:4:"time";s:19:"2021-12-07 01:24:07";}}s:10:"currencies";N;}'], + ]; + } +} diff --git a/Modules/NeutronIBCModule.php b/Modules/NeutronIBCModule.php new file mode 100644 index 00000000..2b8bd0f4 --- /dev/null +++ b/Modules/NeutronIBCModule.php @@ -0,0 +1,25 @@ +blockchain = 'neutron'; + $this->module = 'neutron-ibc'; + $this->is_main = false; + $this->first_block_date = '2019-11-12'; // TODO: need to check date on archive node + + // Cosmos-specific + $this->cosmos_special_addresses = []; + // TODO: need to check early blocks on archive node + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasDecodedValues]; + } +} \ No newline at end of file diff --git a/Modules/NeutronMainModule.php b/Modules/NeutronMainModule.php new file mode 100644 index 00000000..04d7fbd6 --- /dev/null +++ b/Modules/NeutronMainModule.php @@ -0,0 +1,30 @@ +blockchain = 'neutron'; + $this->module = 'neutron-main'; + $this->is_main = true; + $this->first_block_date = '2023-10-11'; + $this->currency = 'neutron'; + $this->currency_details = ['name' => 'Neutron', 'symbol' => 'NTRN', 'decimals' => 6, 'description' => null]; + + // Cosmos-specific + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'neutron17xpfvakm2amg962yls6f84z3kell8c5l5x2z36', + ]; + $this->cosmos_known_denoms = ['untrn' => 0]; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasDecodedValues]; + } +} \ No newline at end of file diff --git a/Modules/OsmosisIBCModule.php b/Modules/OsmosisIBCModule.php new file mode 100644 index 00000000..fb841732 --- /dev/null +++ b/Modules/OsmosisIBCModule.php @@ -0,0 +1,24 @@ +blockchain = 'osmosis'; + $this->module = 'osmosis-ibc'; + $this->is_main = false; + $this->first_block_date = '2021-06-18'; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasDoublesTxEvents, CosmosSpecialFeatures::HasDecodedValues]; + } +} diff --git a/Modules/OsmosisMainModule.php b/Modules/OsmosisMainModule.php new file mode 100644 index 00000000..737ad4a0 --- /dev/null +++ b/Modules/OsmosisMainModule.php @@ -0,0 +1,31 @@ +blockchain = 'osmosis'; + $this->module = 'osmosis-main'; + $this->is_main = true; + $this->first_block_date = '2021-06-18'; + $this->currency = 'osmosis'; + $this->currency_details = ['name' => 'Osmosis', 'symbol' => 'OSMO', 'decimals' => 6, 'description' => null]; + + // Cosmos-specific + // Bench32 converted cosmos addresses + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'osmo17xpfvakm2amg962yls6f84z3kell8c5lczssa0', + ]; + $this->cosmos_known_denoms = ['uosmo' => 0]; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasDoublesTxEvents, CosmosSpecialFeatures::HasDecodedValues]; + } +} diff --git a/Modules/SeiCW20Module.php b/Modules/SeiCW20Module.php new file mode 100644 index 00000000..32909a20 --- /dev/null +++ b/Modules/SeiCW20Module.php @@ -0,0 +1,27 @@ +blockchain = 'sei'; + $this->module = 'sei-cw-20'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->extra_features = [CosmosSpecialFeatures::HasNotCodeField]; + + $this->tests = [ + // Transfer + ['block' => 53278048, 'result' => 'a:2:{s:6:"events";a:2:{i:0;a:9:{s:11:"transaction";s:64:"42357306c1fb246eec9e56af422e5bd955c37db423e0a422493119d6f8867733";s:8:"sort_key";i:0;s:7:"address";s:42:"sei1es66ct72nnlfrez3w27ymhdy9cvx2s4amtttgx";s:8:"currency";s:62:"sei1nm5l32c209nfswsj4u07h38jrrk9cmx9sy36cq6rsxk2cqczjktq4h8cvc";s:6:"effect";s:14:"-6963203246398";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53278048;s:4:"time";s:19:"2024-01-22 12:11:18";}i:1;a:9:{s:11:"transaction";s:64:"42357306c1fb246eec9e56af422e5bd955c37db423e0a422493119d6f8867733";s:8:"sort_key";i:1;s:7:"address";s:42:"sei15x5289awv4ecc6k8u6k0kuqgdaej243g8vkqqt";s:8:"currency";s:62:"sei1nm5l32c209nfswsj4u07h38jrrk9cmx9sy36cq6rsxk2cqczjktq4h8cvc";s:6:"effect";s:13:"6963203246398";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53278048;s:4:"time";s:19:"2024-01-22 12:11:18";}}s:10:"currencies";a:1:{i:0;a:4:{s:2:"id";s:62:"sei1nm5l32c209nfswsj4u07h38jrrk9cmx9sy36cq6rsxk2cqczjktq4h8cvc";s:4:"name";s:14:"Tensa Zangetsu";s:6:"symbol";s:5:"TENSA";s:8:"decimals";s:1:"6";}}}'] + ]; + } +} diff --git a/Modules/SeiCW721Module.php b/Modules/SeiCW721Module.php new file mode 100644 index 00000000..22cc090d --- /dev/null +++ b/Modules/SeiCW721Module.php @@ -0,0 +1,27 @@ +blockchain = 'sei'; + $this->module = 'sei-cw-721'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->extra_features = [CosmosSpecialFeatures::HasNotCodeField]; + + $this->tests = [ + // Transfer + ['block' => 53278074, 'result' => 'a:2:{s:6:"events";a:2:{i:0;a:9:{s:11:"transaction";s:64:"e9be09575e6410ee47c4681f2830b852529d1e6cf2456ea2bc9698bf3c13c810";s:8:"sort_key";i:0;s:7:"address";s:62:"sei1pdwlx9h8nc3fp6073mweug654wfkxjaelgkum0a9wtsktwuydw5sduczvz";s:8:"currency";s:62:"sei12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs5m2xaj";s:6:"effect";s:2:"-1";s:6:"failed";s:1:"f";s:5:"extra";s:19:"C98B68nM9qDNMKRd6Pu";s:5:"block";i:53278074;s:4:"time";s:19:"2024-01-22 12:11:29";}i:1;a:9:{s:11:"transaction";s:64:"e9be09575e6410ee47c4681f2830b852529d1e6cf2456ea2bc9698bf3c13c810";s:8:"sort_key";i:1;s:7:"address";s:42:"sei1np5c83mjqn5fhqcdsrt9gztakt2mkwdhzdp95h";s:8:"currency";s:62:"sei12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs5m2xaj";s:6:"effect";s:1:"1";s:6:"failed";s:1:"f";s:5:"extra";s:19:"C98B68nM9qDNMKRd6Pu";s:5:"block";i:53278074;s:4:"time";s:19:"2024-01-22 12:11:29";}}s:10:"currencies";a:1:{i:0;a:3:{s:2:"id";s:62:"sei12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs5m2xaj";s:4:"name";s:13:"Dagora Minter";s:6:"symbol";s:2:"DM";}}}'] + ]; + } +} diff --git a/Modules/SeiIBCModule.php b/Modules/SeiIBCModule.php new file mode 100644 index 00000000..2115aeb6 --- /dev/null +++ b/Modules/SeiIBCModule.php @@ -0,0 +1,29 @@ +blockchain = 'sei'; + $this->module = 'sei-ibc'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasNotCodeField]; + + $this->tests = [ + // Transfer + ['block' => 53274044, 'result' => 'a:2:{s:6:"events";a:4:{i:0;a:9:{s:11:"transaction";s:64:"c919178e60f0e6ad7224691b229e29ef6126c7e69dfb4a39ee8031ccc6bca335";s:8:"sort_key";i:0;s:7:"address";s:42:"sei16wmlr9tuyf4p2mr8ftxffdtp85vcpq8wq9e46d";s:8:"currency";s:68:"ibc_F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349";s:6:"effect";s:10:"-600318370";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53274044;s:4:"time";s:19:"2024-01-22 11:43:06";}i:1;a:9:{s:11:"transaction";s:64:"c919178e60f0e6ad7224691b229e29ef6126c7e69dfb4a39ee8031ccc6bca335";s:8:"sort_key";i:1;s:7:"address";s:62:"sei1d2r4s2q8kumpmvx6dyj77klhgm5e6fs9njmmz6ye7ukqa77ddtdsu72dc3";s:8:"currency";s:68:"ibc_F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349";s:6:"effect";s:9:"600318370";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53274044;s:4:"time";s:19:"2024-01-22 11:43:06";}i:2;a:9:{s:11:"transaction";s:64:"c919178e60f0e6ad7224691b229e29ef6126c7e69dfb4a39ee8031ccc6bca335";s:8:"sort_key";i:2;s:7:"address";s:62:"sei1d2r4s2q8kumpmvx6dyj77klhgm5e6fs9njmmz6ye7ukqa77ddtdsu72dc3";s:8:"currency";s:68:"ibc_F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349";s:6:"effect";s:10:"-600318370";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53274044;s:4:"time";s:19:"2024-01-22 11:43:06";}i:3;a:9:{s:11:"transaction";s:64:"c919178e60f0e6ad7224691b229e29ef6126c7e69dfb4a39ee8031ccc6bca335";s:8:"sort_key";i:3;s:7:"address";s:62:"sei1prqsm5y7tumchkugx5wcz4y0fya7cta5ch9h366em806frq4jtnqlrazgt";s:8:"currency";s:68:"ibc_F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349";s:6:"effect";s:9:"600318370";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53274044;s:4:"time";s:19:"2024-01-22 11:43:06";}}s:10:"currencies";a:1:{i:0;a:4:{s:2:"id";s:68:"ibc_F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349";s:4:"name";s:5:"uusdc";s:11:"description";s:18:"transfer/channel-2";s:8:"decimals";i:6;}}}'] + ]; + } +} diff --git a/Modules/SeiMainModule.php b/Modules/SeiMainModule.php new file mode 100644 index 00000000..8ebf5bef --- /dev/null +++ b/Modules/SeiMainModule.php @@ -0,0 +1,36 @@ +blockchain = 'sei'; + $this->module = 'sei-main'; + $this->is_main = true; + $this->first_block_date = '2022-05-28'; + $this->currency = 'sei'; + $this->currency_details = ['name' => 'Sei', 'symbol' => 'SEI', 'decimals' => 6, 'description' => null]; + + // Cosmos-specific + // Bench32 converted cosmos addresses + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu', + ]; + $this->cosmos_known_denoms = ['usei' => 0]; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasNotCodeField, CosmosSpecialFeatures::HasNotFeeCollectorRecvEvent]; + + $this->tests = [ + // Random block + ['block' => 53277469, 'result' => 'a:2:{s:6:"events";a:10:{i:0;a:8:{s:11:"transaction";s:64:"2948d04ff5b31e9785b34491acdfc9226d2bd163bc477649101f44fb66ccb243";s:8:"sort_key";i:0;s:7:"address";s:42:"sei1umsz72jtj9n30hkehahhq9mfj5k53apv8s6hsy";s:6:"effect";s:2:"-0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:1;a:8:{s:11:"transaction";s:64:"2948d04ff5b31e9785b34491acdfc9226d2bd163bc477649101f44fb66ccb243";s:8:"sort_key";i:1;s:7:"address";s:42:"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu";s:6:"effect";s:1:"0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:2;a:8:{s:11:"transaction";s:64:"bcaf2ba8609b9625cf443d917ae585c0dee79239fb39f47c6fc34199db89e20d";s:8:"sort_key";i:2;s:7:"address";s:42:"sei14n9fhykwk8rk7zln7rzd6uyhm2gzntuw2pv0e9";s:6:"effect";s:2:"-0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:3;a:8:{s:11:"transaction";s:64:"bcaf2ba8609b9625cf443d917ae585c0dee79239fb39f47c6fc34199db89e20d";s:8:"sort_key";i:3;s:7:"address";s:42:"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu";s:6:"effect";s:1:"0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:4;a:8:{s:11:"transaction";s:64:"a1cfcfcb86880cafc7a601ed3fbfc3dd7808758654006906a7f28e61b3597a32";s:8:"sort_key";i:4;s:7:"address";s:42:"sei14gcl2gvj4y4zey5j2kpek25wg0f4ns7fpcd8c6";s:6:"effect";s:2:"-0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:5;a:8:{s:11:"transaction";s:64:"a1cfcfcb86880cafc7a601ed3fbfc3dd7808758654006906a7f28e61b3597a32";s:8:"sort_key";i:5;s:7:"address";s:42:"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu";s:6:"effect";s:1:"0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:6;a:8:{s:11:"transaction";s:64:"fa47b43d3293bef00c9f78d84c0e3fdcb4f332e03607019a1f1cb8bc08bfff75";s:8:"sort_key";i:6;s:7:"address";s:42:"sei10qa0g0hsne0gk0lnc3y90cql4535cxa2yn9fcm";s:6:"effect";s:2:"-0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:7;a:8:{s:11:"transaction";s:64:"fa47b43d3293bef00c9f78d84c0e3fdcb4f332e03607019a1f1cb8bc08bfff75";s:8:"sort_key";i:7;s:7:"address";s:42:"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu";s:6:"effect";s:1:"0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:8;a:8:{s:11:"transaction";s:64:"feae5f13fccc9eb275c48a1350d0f9341f139a3d7a12cb51b25114a02c0bf309";s:8:"sort_key";i:8;s:7:"address";s:42:"sei1ghjz5yxx0s73afsjnpyjas7l437h6fg0fazddn";s:6:"effect";s:2:"-0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}i:9;a:8:{s:11:"transaction";s:64:"feae5f13fccc9eb275c48a1350d0f9341f139a3d7a12cb51b25114a02c0bf309";s:8:"sort_key";i:9;s:7:"address";s:42:"sei17xpfvakm2amg962yls6f84z3kell8c5la4jkdu";s:6:"effect";s:1:"0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:53277469;s:4:"time";s:19:"2024-01-22 12:07:14";}}s:10:"currencies";N;}'] + ]; + } +} diff --git a/Modules/SeiTokenFactoryModule.php b/Modules/SeiTokenFactoryModule.php new file mode 100644 index 00000000..bcc73ae6 --- /dev/null +++ b/Modules/SeiTokenFactoryModule.php @@ -0,0 +1,27 @@ +blockchain = 'sei'; + $this->module = 'sei-token-factory'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasNotCodeField]; + + $this->tests = [ + ['block' => 53277885, 'result' => 'a:2:{s:6:"events";a:6:{i:0;a:9:{s:11:"transaction";s:64:"8983f3d421d5ae995bad5ebab3ba3ad135cd82e8992c44504ea62e8b729f566d";s:8:"sort_key";i:0;s:7:"address";s:8:"the-void";s:8:"currency";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:6:"effect";s:9:"-10978280";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53277885;s:4:"time";s:19:"2024-01-22 12:10:09";}i:1;a:9:{s:11:"transaction";s:64:"8983f3d421d5ae995bad5ebab3ba3ad135cd82e8992c44504ea62e8b729f566d";s:8:"sort_key";i:1;s:7:"address";s:42:"sei19ejy8n9qsectrf4semdp9cpknflld0j6svvmtq";s:8:"currency";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:6:"effect";s:8:"10978280";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53277885;s:4:"time";s:19:"2024-01-22 12:10:09";}i:2;a:9:{s:11:"transaction";s:64:"8983f3d421d5ae995bad5ebab3ba3ad135cd82e8992c44504ea62e8b729f566d";s:8:"sort_key";i:2;s:7:"address";s:42:"sei19ejy8n9qsectrf4semdp9cpknflld0j6svvmtq";s:8:"currency";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:6:"effect";s:9:"-10978280";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53277885;s:4:"time";s:19:"2024-01-22 12:10:09";}i:3;a:9:{s:11:"transaction";s:64:"8983f3d421d5ae995bad5ebab3ba3ad135cd82e8992c44504ea62e8b729f566d";s:8:"sort_key";i:3;s:7:"address";s:62:"sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc";s:8:"currency";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:6:"effect";s:8:"10978280";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53277885;s:4:"time";s:19:"2024-01-22 12:10:09";}i:4;a:9:{s:11:"transaction";s:64:"8983f3d421d5ae995bad5ebab3ba3ad135cd82e8992c44504ea62e8b729f566d";s:8:"sort_key";i:4;s:7:"address";s:62:"sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc";s:8:"currency";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:6:"effect";s:9:"-10978280";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53277885;s:4:"time";s:19:"2024-01-22 12:10:09";}i:5;a:9:{s:11:"transaction";s:64:"8983f3d421d5ae995bad5ebab3ba3ad135cd82e8992c44504ea62e8b729f566d";s:8:"sort_key";i:5;s:7:"address";s:42:"sei1tr5x3q04prra3tu0fsan9da7v98ve8eqqwv2tn";s:8:"currency";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:6:"effect";s:8:"10978280";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:53277885;s:4:"time";s:19:"2024-01-22 12:10:09";}}s:10:"currencies";a:1:{i:0;a:3:{s:2:"id";s:75:"factory_sei1e3gttzq5e5k49f9f5gzvrl0rltlav65xu6p9xc0aj7e84lantdjqp7cncc_isei";s:4:"name";s:4:"isei";s:8:"decimals";i:6;}}}'] + ]; + } +} diff --git a/Modules/TerraCW20Module.php b/Modules/TerraCW20Module.php new file mode 100644 index 00000000..f358c699 --- /dev/null +++ b/Modules/TerraCW20Module.php @@ -0,0 +1,22 @@ +blockchain = 'terra'; + $this->module = 'terra-cw-20'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->extra_features = [CosmosSpecialFeatures::HasDecodedValues]; + } +} diff --git a/Modules/TerraCW721Module.php b/Modules/TerraCW721Module.php new file mode 100644 index 00000000..eaf0197b --- /dev/null +++ b/Modules/TerraCW721Module.php @@ -0,0 +1,22 @@ +blockchain = 'terra'; + $this->module = 'terra-cw-721'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->extra_features = [CosmosSpecialFeatures::HasDecodedValues]; + } +} diff --git a/Modules/TerraIBCModule.php b/Modules/TerraIBCModule.php new file mode 100644 index 00000000..c7839338 --- /dev/null +++ b/Modules/TerraIBCModule.php @@ -0,0 +1,24 @@ +blockchain = 'terra'; + $this->module = 'terra-ibc'; + $this->is_main = false; + $this->first_block_date = '2022-05-28'; + + // Cosmos-specific + $this->cosmos_special_addresses = []; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasDecodedValues]; + } +} diff --git a/Modules/TerraMainModule.php b/Modules/TerraMainModule.php new file mode 100644 index 00000000..14679bb3 --- /dev/null +++ b/Modules/TerraMainModule.php @@ -0,0 +1,31 @@ +blockchain = 'terra'; + $this->module = 'terra-main'; + $this->is_main = true; + $this->first_block_date = '2022-05-28'; + $this->currency = 'luna'; + $this->currency_details = ['name' => 'Luna', 'symbol' => 'LUNA', 'decimals' => 6, 'description' => null]; + + // Cosmos-specific + // Bench32 converted cosmos addresses + $this->cosmos_special_addresses = [ + // At each block, all fees received are transferred to fee_collector. + 'fee_collector' => 'terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa', + ]; + $this->cosmos_known_denoms = ['uluna' => 0]; + $this->cosmos_coin_events_fork = 0; + $this->extra_features = [CosmosSpecialFeatures::HasDecodedValues]; + } +}