diff --git a/.env.example b/.env.example index 9e419f0c..c8a5ac6a 100644 --- a/.env.example +++ b/.env.example @@ -744,6 +744,61 @@ 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 Filecoin Module +###################### + +MODULES[]=filecoin-main +MODULE_filecoin-main_CLASS=FilecoinMainModule +MODULE_filecoin-main_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-main_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-main_REQUESTER_TIMEOUT=60 +MODULE_filecoin-main_REQUESTER_THREADS=12 + +###################### +## Trace Filecoin Module +###################### + +MODULES[]=filecoin-trace +MODULE_filecoin-trace_CLASS=FilecoinTraceModule +MODULE_filecoin-trace_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-trace_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-trace_REQUESTER_TIMEOUT=60 +MODULE_filecoin-trace_REQUESTER_THREADS=12 + +###################### +## ERC20 EVM Filecoin Module +###################### + +MODULES[]=filecoin-evm-erc-20 +MODULE_filecoin-evm-erc-20_CLASS=FilecoinEVMERC20Module +MODULE_filecoin-evm-erc-20_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-evm-erc-20_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-evm-erc-20_REQUESTER_TIMEOUT=60 +MODULE_filecoin-evm-erc-20_REQUESTER_THREADS=12 + +###################### +## ERC721 EVM Filecoin Module +###################### + +MODULES[]=filecoin-evm-erc-721 +MODULE_filecoin-evm-erc-721_CLASS=FilecoinEVMERC721Module +MODULE_filecoin-evm-erc-721_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-evm-erc-721_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-evm-erc-721_REQUESTER_TIMEOUT=60 +MODULE_filecoin-evm-erc-721_REQUESTER_THREADS=12 + +###################### +## ERC1155 EVM Filecoin Module +###################### + +MODULES[]=filecoin-evm-erc-1155 +MODULE_filecoin-evm-erc-1155_CLASS=FilecoinEVMERC1155Module +MODULE_filecoin-evm-erc-1155_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-evm-erc-1155_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_filecoin-evm-erc-1155_REQUESTER_TIMEOUT=60 +MODULE_filecoin-evm-erc-1155_REQUESTER_THREADS=12 + ############################ # Titles, descriptions, etc. ############################ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7c87c9e5..1b7eb04e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,5 +7,7 @@ - Beacon Chain modules * [alexqrid](https://github.com/alexqrid) - TVM modules +* [Kirill Kuzminykh](https://github.com/Oskal174) + - Filecoin modules * [Oleg Makaussov](https://github.com/Lorgansar) - Cardano Tokens modules diff --git a/Engine/Requester.php b/Engine/Requester.php index 500c3f89..5fb4a966 100644 --- a/Engine/Requester.php +++ b/Engine/Requester.php @@ -7,7 +7,7 @@ /* Various curl functions for requesting data from nodes */ // Just a single curl request -function requester_single($daemon, $endpoint = '', $params = [], $result_in = '', $timeout = 600, $valid_codes = [200], $no_json_encode = false, $flags = []) +function requester_single($daemon, $endpoint = '', $params = [], $result_in = '', $ignore_errors = false, $timeout = 600, $valid_codes = [200], $no_json_encode = false, $flags = []) { static $curl = null; @@ -106,7 +106,7 @@ function requester_single($daemon, $endpoint = '', $params = [], $result_in = '' throw new RequesterException("requester_request(daemon:({$daemon_clean}), endpoint:({$endpoint}), params:({$params_log}), result_in:({$result_in})) failed: bad JSON; preg error: {$e_preg}, json error: {$e_json}"); // . print_r($output, true) } - if (isset($output['error'])) + if (isset($output['error']) && !$ignore_errors) { throw new RequesterException("requester_request(daemon:({$daemon_clean}), endpoint:({$endpoint}), params:({$params_log}), result_in:({$result_in})) errored: " . print_r($output['error'], true)); } diff --git a/Modules/Common/EVMERC1155Module.php b/Modules/Common/EVMERC1155Module.php index d1926eb7..4daf7f8b 100644 --- a/Modules/Common/EVMERC1155Module.php +++ b/Modules/Common/EVMERC1155Module.php @@ -64,6 +64,14 @@ final public function pre_process_block($block_id) if ((!in_array(EVMSpecialFeatures::zkEVM, $this->extra_features))) { + // Filecoin may have empty tipsets (ex. 3508881) + if (in_array(EVMSpecialFeatures::fEVM, $this->extra_features) && $this->block_hash === '') + { + $this->set_return_events([]); + $this->set_return_currencies([]); + return; + } + $multi_curl[] = requester_multi_prepare($this->select_node(), params: ['jsonrpc' => '2.0', 'method' => 'eth_getLogs', diff --git a/Modules/Common/EVMERC20Module.php b/Modules/Common/EVMERC20Module.php index 19a434af..e6fadcff 100644 --- a/Modules/Common/EVMERC20Module.php +++ b/Modules/Common/EVMERC20Module.php @@ -60,6 +60,14 @@ final public function pre_process_block($block_id) if ((!in_array(EVMSpecialFeatures::zkEVM, $this->extra_features))) { + // Filecoin may have empty tipsets (ex. 3508881) + if (in_array(EVMSpecialFeatures::fEVM, $this->extra_features) && $this->block_hash === '') + { + $this->set_return_events([]); + $this->set_return_currencies([]); + return; + } + $logs = requester_single($this->select_node(), params: ['jsonrpc' => '2.0', 'method' => 'eth_getLogs', diff --git a/Modules/Common/EVMERC721Module.php b/Modules/Common/EVMERC721Module.php index 62f28d1e..52ee86b4 100644 --- a/Modules/Common/EVMERC721Module.php +++ b/Modules/Common/EVMERC721Module.php @@ -62,6 +62,14 @@ final public function pre_process_block($block_id) if ((!in_array(EVMSpecialFeatures::zkEVM, $this->extra_features))) { + // Filecoin may have empty tipsets (ex. 3508881) + if (in_array(EVMSpecialFeatures::fEVM, $this->extra_features) && $this->block_hash === '') + { + $this->set_return_events([]); + $this->set_return_currencies([]); + return; + } + $logs = requester_single($this->select_node(), params: ['jsonrpc' => '2.0', 'method' => 'eth_getLogs', diff --git a/Modules/Common/EVMTraits.php b/Modules/Common/EVMTraits.php index 76b6e5e4..c86402ca 100644 --- a/Modules/Common/EVMTraits.php +++ b/Modules/Common/EVMTraits.php @@ -33,6 +33,7 @@ enum EVMSpecialFeatures case zkEVM; case HasSystemTransactions; case EffectiveGasPriceCanBeZero; + case fEVM; // Filecoin EVM } trait EVMTraits @@ -87,7 +88,32 @@ public function ensure_block($block_id, $break_on_first = false) throw new RequesterException("ensure_block(block_id: {$block_id}): no connection, previously: " . $e->getMessage()); } - $result0 = requester_multi_process($curl_results[0], result_in: 'result'); + $ignore_errors = false; + $result_in = 'result'; + if (in_array(EVMSpecialFeatures::fEVM, $this->extra_features)) + { + $ignore_errors = true; + $result_in = ''; + } + + $result0 = requester_multi_process($curl_results[0], ignore_errors: $ignore_errors, result_in: $result_in); + + // Filecoin may have empty tipsets (ex. 3508881) + if (in_array(EVMSpecialFeatures::fEVM, $this->extra_features)) + { + if (isset($result0['error'])) + { + if ($result0['error']['message'] === 'requested epoch was a null round') + { + $this->block_hash = ''; + $this->block_time = date('Y-m-d H:i:s', 0); + return; + } + throw new ModuleException("requester_multi_process errored: " . print_r($result0['error'], true)); + } + + $result0 = $result0['result']; + } $hash_key = (!in_array(EVMSpecialFeatures::zkEVM, $this->extra_features)) ? 'hash' diff --git a/Modules/Common/FilecoinLikeMainModule.php b/Modules/Common/FilecoinLikeMainModule.php new file mode 100644 index 00000000..5b66fb23 --- /dev/null +++ b/Modules/Common/FilecoinLikeMainModule.php @@ -0,0 +1,322 @@ +value => 'Miner fee', + FilecoinSpecialTransactions::FeeToBurn->value => 'Burnt fee', + ]; + + public ?bool $should_return_events = true; + public ?bool $should_return_currencies = false; + public ?bool $allow_empty_return_events = true; + + public ?bool $mempool_implemented = true; + public ?bool $forking_implemented = false; + + final public function pre_initialize() + { + $this->version = 1; + } + + final public function post_post_initialize() + { + } + + final public function pre_process_block($block_id) + { + $events = []; + $sort_key = 0; + + if ($block_id === MEMPOOL) + { + $pending_messages = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.MpoolPending', + 'params' => [[]], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + foreach ($pending_messages as $message) + { + $tx_hash = $message['CID']['/']; + $from = $message['Message']['From']; + $to = $message['Message']['To']; + $amount = $message['Message']['Value']; + + [$sub, $add] = $this->generate_event_pair($tx_hash, $from, $to, $amount, false, $sort_key); + array_push($events, $sub, $add); + } + + $this_time = date('Y-m-d H:i:s'); + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this_time; + } + + $this->set_return_events($events); + return; + } + + $tipset_header = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetTipSetByHeight', + 'params' => [ + $block_id, + [] + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + // Check for empty tipset + if ((int)$tipset_header['Height'] !== $block_id) + { + $this->set_return_events($events); + return; // Tipset is empty (ex. 3506564) + } + + // Tipset header at +1 height to get parent messages and receipts + $tipset_header_next = null; + $next_height = $block_id; + do + { + // Tipset may be empty so we need to find none empty next tipset + $next_height++; + $tipset_header_next = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetTipSetByHeight', + 'params' => [ + $next_height, + [] + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + } while ((int)$tipset_header_next['Height'] !== $next_height); + + // Parent info the same for all block Cids. + $block_cid_next = $tipset_header_next['Cids'][0]; + + // Tipset messages is transactions we parse + $tipset_messages = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetParentMessages', + 'params' => [ + $block_cid_next + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + timeout: $this->timeout + ); + + if (is_null($tipset_messages['result'])) + { + $this->set_return_events($events); + return; // Tipset is empty (ex. 3504986) + } + + $tipset_messages = $tipset_messages['result']; + + // Messages Receipts with additional info + $tipset_receipts = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetParentReceipts', + 'params' => [ + $block_cid_next + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + if (count($tipset_messages) !== count($tipset_receipts)) + throw new ModuleException("Messages and receipts count mismatch."); + + // We need to get baseFee value its the same in all blocks headers. + $base_fee = null; + // We need to get miner info for every message + $miners = []; + foreach ($tipset_header['Cids'] as $block_cid) + { + $block_header = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetBlock', + 'params' => [ + $block_cid + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + // Additional check that base_fee same for all blocks + if (is_null($base_fee)) + $base_fee = $block_header['ParentBaseFee']; + elseif ($base_fee !== $block_header['ParentBaseFee']) + throw new ModuleException("Base fee mismatch for blocks."); + + $block_messages = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetBlockMessages', + 'params' => [ + $block_cid + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + // Messages can duplicates so in tipset they come in straight order + foreach ($block_messages['BlsMessages'] as $msg) + { + $cid = $msg['CID']['/']; + if (!array_key_exists($cid, $miners)) + $miners[$cid] = $block_header['Miner']; + } + + foreach ($block_messages['SecpkMessages'] as $msg) + { + $cid = $msg['CID']['/']; + if (!array_key_exists($cid, $miners)) + $miners[$cid] = $block_header['Miner']; + } + } + + if (is_null($base_fee)) + throw new ModuleException("Invalid base_fee (null)."); + + for ($i = 0; $i < count($tipset_messages); $i++) + { + $message = $tipset_messages[$i]; + $receipt = $tipset_receipts[$i]; + + $tx_hash = $message['Cid']['/']; + $failed = false; + if ((int)$receipt['ExitCode'] !== 0) + $failed = true; + + // Processing Fee + // Docs: + // https://docs.filecoin.io/smart-contracts/filecoin-evm-runtime/how-gas-works#calculation-example + // https://filecoin.io/blog/posts/filecoin-features-gas-fees/ + // https://spec.filecoin.io/#section-systems.filecoin_vm.gas_fee + + $gas_fee_cap = $message['Message']['GasFeeCap']; + $gas_limit = $message['Message']['GasLimit']; + $gas_used = $receipt['GasUsed']; + $gas_premium = $message['Message']['GasPremium']; + + $miner_fee = null; + if (bccomp(bcadd($base_fee, $gas_premium), $gas_fee_cap) === 1) + $miner_fee = bcmul($gas_limit, bcsub($gas_fee_cap, $base_fee)); + else + $miner_fee = bcmul($gas_limit, $gas_premium); + + $base_to_burn = bcmul($gas_used, $base_fee); + $over_estimation_burn = $this->compute_gas_overestimation_burn($gas_used, $gas_limit, $base_fee); + $total_burned = bcadd($base_to_burn, $over_estimation_burn); + + $from = $message['Message']['From']; + $to = $message['Message']['To']; + $amount = $message['Message']['Value']; + + if ($miner_fee !== '0') + { + [$sub, $add] = $this->generate_event_pair($tx_hash, $from, $miners[$tx_hash], $miner_fee, $failed, $sort_key); + $sub['extra'] = FilecoinSpecialTransactions::FeeToMiner->value; + $add['extra'] = FilecoinSpecialTransactions::FeeToMiner->value; + array_push($events, $sub, $add); + } + if ($total_burned !== '0') + { + [$sub, $add] = $this->generate_event_pair($tx_hash, $from, 'the-void', $total_burned, $failed, $sort_key); + $sub['extra'] = FilecoinSpecialTransactions::FeeToBurn->value; + $add['extra'] = FilecoinSpecialTransactions::FeeToBurn->value; + array_push($events, $sub, $add); + } + + if ($amount !== '0') + { + [$sub, $add] = $this->generate_event_pair($tx_hash, $from, $to, $amount, $failed, $sort_key); + array_push($events, $sub, $add); + } + } + + 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($address) + { + $balance_data = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.WalletBalance', + 'params' => [$address], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + timeout: $this->timeout, + ignore_errors: true, + valid_codes: [200, 500] + ); + + if (!isset($balance_data['result'])) + return '0'; + + return $balance_data['result']; + } +} diff --git a/Modules/Common/FilecoinLikeTraceModule.php b/Modules/Common/FilecoinLikeTraceModule.php new file mode 100644 index 00000000..060d54fc --- /dev/null +++ b/Modules/Common/FilecoinLikeTraceModule.php @@ -0,0 +1,110 @@ +value => 'Block reward', + ]; + + public ?bool $should_return_events = true; + public ?bool $should_return_currencies = false; + public ?bool $allow_empty_return_events = true; + + public ?bool $mempool_implemented = false; + public ?bool $forking_implemented = false; + + final public function pre_initialize() + { + $this->version = 1; + } + + final public function post_post_initialize() + { + } + + final public function pre_process_block($block_id) + { + $events = []; + $sort_key = 0; + + $tipset_header = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.ChainGetTipSetByHeight', + 'params' => [ + $block_id, + [] + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + // Check for empty tipset + if ((int)$tipset_header['Height'] !== $block_id) + { + $this->set_return_events($events); + return; + } + + $internal_state = requester_single($this->select_node(), + params: [ + 'method' => 'Filecoin.StateCompute', + 'params' => [ + $block_id, + [], + $tipset_header['Cids'] + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + foreach ($internal_state['Trace'] as $trace) + { + $failed = false; + if ($trace['Error'] !== '') + $failed = true; + + $this->process_trace($trace['ExecutionTrace'], $failed, $events, $sort_key); + } + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = $this->block_time; + } + + $this->set_return_events($events); + } +} diff --git a/Modules/Common/FilecoinTraits.php b/Modules/Common/FilecoinTraits.php new file mode 100644 index 00000000..e1af566b --- /dev/null +++ b/Modules/Common/FilecoinTraits.php @@ -0,0 +1,198 @@ +select_node(), + params: [ + 'method' => 'Filecoin.ChainHead', + 'params' => [], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + result_in: 'result', + timeout: $this->timeout + ); + + // At latest tipset may be more than one block but all have the same height + // It also may have 0 blocks + if (count($header['Blocks']) === 0) + throw new ModuleException("Zero blocks array in tipset."); + + $block_height = (int)$header['Blocks'][0]['Height']; + // Additional checks all blocks have same height + foreach ($header['Blocks'] as $block_info) + { + if ((int)$block_info['Height'] !== $block_height) + throw new ModuleException("Blocks height mismatch"); + } + + // Latest block is Head - 1 which already have all Receipts and final messages order + return (int)$block_height - 1; + } + + public function ensure_block($block_id, $break_on_first = false) + { + $multi_curl = []; + foreach ($this->nodes as $node) + { + $multi_curl[] = requester_multi_prepare( + $node, + params: [ + 'method' => 'Filecoin.ChainGetTipSetByHeight', + 'params' => [ + $block_id, + [] + ], + 'id' => 0, + 'jsonrpc' => '2.0', + ], + 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_in: 'result'); + + $this->block_hash = $this->make_tipset_hash($block_id, $result['Cids']); + + $block_time = $result['Blocks'][0]['Timestamp']; + $this->ensure_blocks_timestamp($block_time, $result['Blocks']); + $this->block_time = date('Y-m-d H:i:s', (int)$block_time); + + foreach ($curl_results as $curl_result) + { + $result = requester_multi_process($curl_result, result_in: 'result'); + $block_hash = $this->make_tipset_hash($block_id, $result['Cids']); + if ($block_hash !== $this->block_hash) + { + throw new ConsensusException("ensure_block(block_id: {$block_id}): no consensus"); + } + } + } + } + + // Create unique tipset hash from height and all blocks hashes + function make_tipset_hash($block_id, array $cids): string + { + require_once __DIR__ . '/../../Engine/Crypto/Keccak.php'; + + $tipset_full_id = $block_id; + foreach ($cids as $cid) + { + $tipset_full_id = $tipset_full_id . $cid['/']; + } + return Keccak9::hash($tipset_full_id, 256); + } + + // We must ensure that all blocks in tipset have same timestamps + function ensure_blocks_timestamp(string $block_time, array $blocks) + { + foreach ($blocks as $block) + { + if ($block_time !== $block['Timestamp']) + throw new ModuleException("Block timestamp mismatch."); + } + } + + // Process all trace subcalls + function process_trace(array $trace, bool $failed, array &$events, int &$sort_key) + { + $calls = $trace['Subcalls'] ?? null; + if (is_null($calls)) + return; + + foreach ($calls as $call) + { + $from = $call['Msg']['From']; + $to = $call['Msg']['To']; + $amount = $call['Msg']['Value']; + + if ($amount === '0') + continue; + + [$sub, $add] = $this->generate_event_pair(null, $from, $to, $amount, $failed, $sort_key); + if ($from === 'f02') // Reward actor address + { + $sub['address'] = 'the-void'; + $sub['extra'] = FilecoinSpecialTransactions::BlockReward->value; + $add['extra'] = FilecoinSpecialTransactions::BlockReward->value; + } + if ($to === 'f099') // burn address + $add['address'] = 'the-void'; + array_push($events, $sub, $add); + + $this->process_trace($call, $failed, $events, $sort_key); + } + } + + // Utility functions + + function generate_event_pair(?string $tx, string $src, string $dst, string $amt, bool $fld, int &$sort_key): array + { + $sub = [ + 'transaction' => $tx, + 'sort_key' => $sort_key++, + 'address' => $src, + 'effect' => '-' . $amt, + 'failed' => $fld, + 'extra' => null + ]; + $add = [ + 'transaction' => $tx, + 'sort_key' => $sort_key++, + 'address' => $dst, + 'effect' => $amt, + 'failed' => $fld, + 'extra' => null + ]; + + return [$sub, $add]; + } + + // https://github.com/filecoin-project/lotus/blob/23d705e33b8220bbf7aa7c025747ca2972b1e7a9/chain/vm/burn.go#L38 + function compute_gas_overestimation_burn(string $gas_used, string $gas_limit, string $base_fee): string + { + if (bccomp($gas_used, '0') === 0) + return $gas_limit; + + $gas_overuse_num = '11'; + $gas_overuse_denom = '10'; + + $over = bcsub($gas_limit, bcdiv(bcmul($gas_overuse_num, $gas_used), $gas_overuse_denom)); + if (bccomp($over, '0') === -1) + return '0'; + + if (bccomp($over, $gas_used) === 1) + $over = $gas_used; + + $gas_to_burn = bcsub($gas_limit, $gas_used); + $gas_to_burn = bcmul($gas_to_burn, $over); + $gas_to_burn = bcdiv($gas_to_burn, $gas_used); + return bcmul($gas_to_burn, $base_fee); + } +} diff --git a/Modules/FilecoinEVMERC1155Module.php b/Modules/FilecoinEVMERC1155Module.php new file mode 100644 index 00000000..52d91925 --- /dev/null +++ b/Modules/FilecoinEVMERC1155Module.php @@ -0,0 +1,22 @@ +blockchain = 'filecoin'; + $this->module = 'filecoin-evm-erc-1155'; + $this->is_main = false; + $this->first_block_date = '2020-08-25'; + + // EVM-specific + $this->extra_features = [EVMSpecialFeatures::fEVM]; + } +} diff --git a/Modules/FilecoinEVMERC20Module.php b/Modules/FilecoinEVMERC20Module.php new file mode 100644 index 00000000..6b3f2f4d --- /dev/null +++ b/Modules/FilecoinEVMERC20Module.php @@ -0,0 +1,22 @@ +blockchain = 'filecoin'; + $this->module = 'filecoin-evm-erc-20'; + $this->is_main = false; + $this->first_block_date = '2020-08-25'; + + // EVM-specific + $this->extra_features = [EVMSpecialFeatures::fEVM]; + } +} diff --git a/Modules/FilecoinEVMERC721Module.php b/Modules/FilecoinEVMERC721Module.php new file mode 100644 index 00000000..bb3feb64 --- /dev/null +++ b/Modules/FilecoinEVMERC721Module.php @@ -0,0 +1,22 @@ +blockchain = 'filecoin'; + $this->module = 'filecoin-evm-erc-721'; + $this->is_main = false; + $this->first_block_date = '2020-08-25'; + + // EVM-specific + $this->extra_features = [EVMSpecialFeatures::fEVM]; + } +} diff --git a/Modules/FilecoinMainModule.php b/Modules/FilecoinMainModule.php new file mode 100644 index 00000000..2a14ad0d --- /dev/null +++ b/Modules/FilecoinMainModule.php @@ -0,0 +1,21 @@ +blockchain = 'filecoin'; + $this->module = 'filecoin-main'; + $this->is_main = true; + $this->first_block_date = '2020-08-25'; + $this->currency = 'filecoin'; + $this->currency_details = ['name' => 'Filecoin', 'symbol' => 'FIL', 'decimals' => 18, 'description' => null]; + } +} diff --git a/Modules/FilecoinTraceModule.php b/Modules/FilecoinTraceModule.php new file mode 100644 index 00000000..910a03f7 --- /dev/null +++ b/Modules/FilecoinTraceModule.php @@ -0,0 +1,20 @@ +blockchain = 'filecoin'; + $this->module = 'filecoin-trace'; + $this->is_main = false; + $this->first_block_date = '2020-08-25'; + $this->complements = 'filecoin-main'; + } +}