diff --git a/.env.example b/.env.example index 0efb815..6041910 100644 --- a/.env.example +++ b/.env.example @@ -949,6 +949,28 @@ MODULE_xrp-ledger-nft_NODES[]=http://login:password@127.0.0.2:1234/ MODULE_xrp-ledger-nft_REQUESTER_TIMEOUT=60 MODULE_xrp-ledger-nft_REQUESTER_THREADS=12 +##################### +## Main Stacks Module +##################### + +MODULES[]=stacks-main +MODULE_stacks-main_CLASS=StacksMainModule +MODULE_stacks-main_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_stacks-main_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_stacks-main_REQUESTER_TIMEOUT=60 +MODULE_stacks-main_REQUESTER_THREADS=12 + +################### +## FT Stacks Module +################### + +MODULES[]=stacks-ft +MODULE_stacks-ft_CLASS=StacksFTModule +MODULE_stacks-ft_NODES[]=http://login:password@127.0.0.1:1234/ +MODULE_stacks-ft_NODES[]=http://login:password@127.0.0.2:1234/ +MODULE_stacks-ft_REQUESTER_TIMEOUT=60 +MODULE_stacks-ft_REQUESTER_THREADS=12 + ##################### ## Ton Minimal Module ##################### diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 34c10b3..52de657 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,6 +5,7 @@ - Core, UTXO modules, EVM modules * [Yulian Volianskyi](https://github.com/jzethar) - Beacon Chain modules + - Stacks modules * [alexqrid](https://github.com/alexqrid) - TVM modules - BRC-20 module diff --git a/Engine/Enums.php b/Engine/Enums.php index 92cafb7..6b6193f 100644 --- a/Engine/Enums.php +++ b/Engine/Enums.php @@ -51,6 +51,7 @@ enum CurrencyFormat: string case Numeric = 'Numeric'; // Currency identifiers are numbers (e.g. Omni layer) case AlphaNumeric = 'AlphaNumeric'; case UnsafeAlphaNumeric = 'UnsafeAlphaNumeric'; // This is for cases when currency ids may contain special characters such as `/` + // If you choose to use this type, first use `base64_encode()` on the currency id case HexWithout0x = 'HexWithout0x'; case HexWith0x = 'HexWith0x'; // E.g. ERC-20 currency identifiers are their contract addresses } diff --git a/Modules/Common/StacksLikeFTModule.php b/Modules/Common/StacksLikeFTModule.php new file mode 100644 index 0000000..72acb1b --- /dev/null +++ b/Modules/Common/StacksLikeFTModule.php @@ -0,0 +1,435 @@ +version = 1; + } + + final public function post_post_initialize() + { + // + } + + final public function pre_process_block($block_id) + { + $curl_results = []; + $currencies_to_process = []; + $currencies = []; + + if ($block_id == MEMPOOL) + { + $mempool = requester_single( + $this->select_node(), + endpoint: "/api/extended/v1/tx/mempool?limit={$this->limit}", + timeout: $this->timeout + ); + $amount = $mempool['total']; + $curl_results = array_merge($curl_results, $mempool['results']); + if (($amount - $this->limit) > $this->limit) + { + for ($offset = $this->limit; $offset <= ($amount); $offset += $this->limit) + { + $multi_curl[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/api/extended/v1/tx/mempool?limit={$this->limit}&offset=$offset", + timeout: $this->timeout + ); + } + $results = requester_multi( + $multi_curl, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout + ); + $results = requester_multi_process_all($results, reorder: false, result_in: 'results'); + foreach ($results as $r) + $curl_results = array_merge($curl_results, $r); + } + else + { + $curl_results = requester_single( + $this->select_node(), + endpoint: "/api/extended/v1/tx/mempool?limit={$this->limit}", + timeout: $this->timeout, + result_in: 'results' + ); + } + } + else + { + $block = requester_single( + $this->select_node(), + endpoint: "/api/extended/v2/blocks/{$block_id}", + timeout: $this->timeout + ); + $true_block_time = date('Y-m-d H:i:s', (int)$block['block_time']); + + $this->block_time = $true_block_time; + if ($block['tx_count'] == 0) + { + $this->block_time = $true_block_time; + $this->set_return_currencies([]); + $this->set_return_events([]); + return; + } + + $curl_results = []; + $multi_curl = []; + if ($block['tx_count'] > $this->limit) + { + for ($offset = 0; $offset <= $block['tx_count']; $offset += $this->limit) + { + $multi_curl[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/api/extended/v2/blocks/{$block_id}/transactions?limit={$this->limit}&offset=$offset", + timeout: $this->timeout + ); + } + + $results = requester_multi( + $multi_curl, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout + ); + $results = requester_multi_process_all($results, reorder: false, result_in: 'results'); + foreach ($results as $r) + $curl_results = array_merge($curl_results, $r); + } + else + { + $curl_results = requester_single( + $this->select_node(), + endpoint: "/api/extended/v2/blocks/{$block_id}/transactions?limit={$this->limit}", + timeout: $this->timeout, + result_in: 'results' + ); + } + } + + $transactions = []; + $multi_curl = []; + foreach ($curl_results as $tr) + { + $multi_curl[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/api/extended/v1/tx/{$tr['tx_id']}", + timeout: $this->timeout + ); + } + $transactions = requester_multi( + $multi_curl, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout, + valid_codes: [200, 500], # until we are totally sure that `requester_multi_process_all` ignores it + ); + + if ($block_id == MEMPOOL) + { + $output = []; + foreach ($transactions as $v) + { + $out = requester_multi_process($v, ignore_errors: true); + if(isset($out['statusCode'])) + continue; + $output[] = $out; + } + unset($transactions); + $transactions = $output; + } + else + $transactions = requester_multi_process_all($transactions, reorder: false); + + // sort according to tx index in the block + if ($block_id != MEMPOOL) + usort($transactions, function ($a, $b) { + return [$a['tx_index']] <=> [$b['tx_index']]; + }); + + + $events = []; + $sort_key = 0; + + foreach ($transactions as $op) + { + if ($block_id === MEMPOOL) + if (isset($this->processed_transactions[$op['tx_id']])) + continue; + else + $this->processed_transactions[$op['tx_id']] = true; + + if (isset($op['event_count']) && (int)$op['event_count'] > 0) + { + foreach ($op['events'] as $ev) + { + switch ($ev['event_type']) + { + case 'fungible_token_asset': + { + $contract_pos = strpos($ev['asset']['asset_id'], '::'); + $currency_id = substr($ev['asset']['asset_id'], 0, $contract_pos); + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => $ev['asset']['sender'] ?: 'the-void', + 'currency' => $currency_id, + 'sort_key' => $sort_key++, + 'effect' => '-' . $ev['asset']['amount'], + 'failed' => !($op['tx_status'] == 'success') + ]; + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => $ev['asset']['recipient'] ?: 'the-void', + 'currency' => $currency_id, + 'sort_key' => $sort_key++, + 'effect' => $ev['asset']['amount'], + 'failed' => !($op['tx_status'] == 'success') + ]; + $currencies_to_process[] = $ev['asset']['asset_id']; // in currency block shall be cut + break; + } + case 'stx_asset': + case 'stx_lock': + case 'smart_contract_log': + case 'non_fungible_token_asset': + break; + default: + throw new ModuleException("Unknown event: " . $op['event_type'] . " in transaction: " . $op['tx_id']); + } + } + } + } + + if ($block_id == MEMPOOL) + { + $currencies_to_process = []; + $currencies = null; + } + + foreach ($currencies_to_process as &$c) + if ($p = strpos($c, '::')) + $c = substr($c, 0, $p); + + $currencies_to_process = array_values(array_unique($currencies_to_process)); // Removing duplicates + $currencies_to_process = check_existing_currencies($currencies_to_process, $this->currency_format); + + if ($currencies_to_process) + { + if ($this->token_api) + { + $multi_curr = []; + foreach ($currencies_to_process as $currency_id) + { + $multi_curr[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/token/metadata/v1/ft/{$currency_id}", + timeout: $this->timeout + ); + } + $results = requester_multi( + $multi_curr, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout, + valid_codes: [200, 404, 422] + ); + $output = []; + + foreach ($results as $v) + $output[] = requester_multi_process($v, ignore_errors: true); + foreach ($output as $r) + { + if (isset($r['error'])) { + $currencies[] = [ + "id" => $currency_id, + "decimals" => 0, + "name" => "", + "symbol" => "", + ]; + continue; + } + $contract_pos = strpos($r['asset_identifier'], '::'); + $currency_id = substr($r['asset_identifier'], 0, $contract_pos); + $decimals = $r['decimals'] ?? 0; + $name = $r['name'] ?? ''; + $symb = $r['symbol'] ?? ''; + $currencies[] = [ + "id" => $currency_id, + "decimals" => $decimals, + "name" => $name, + "symbol" => $symb, + ]; + } + } + else + { + $body = [ + "sender" => "{$this->default_caller}", + "arguments" => [], + ]; + + foreach ($currencies_to_process as $currency_id) + { + $contract_name_pos = strpos($currency_id, '.'); + + $cr_contract = substr($currency_id, 0, $contract_name_pos); + $cr_contract_name = substr($currency_id, $contract_name_pos + 1); + + // decimals + $requester_single_dec = requester_single( + $this->select_node(), + endpoint: "/core/v2/contracts/call-read/{$cr_contract}/{$cr_contract_name}/get-decimals", + params: $body, + timeout: $this->timeout + ); + + if ($requester_single_dec['okay'] == 'true') + { + try + { + $decimals = to_int64_from_0xhex('0x' . substr($requester_single_dec['result'], 6)); + if ($decimals > 32767) + $decimals = 0; + } + catch (MathException) { + $decimals = 0; + } + } + else + $decimals = 0; + + // name + $requester_single_name = requester_single( + $this->select_node(), + endpoint: "/core/v2/contracts/call-read/{$cr_contract}/{$cr_contract_name}/get-name", + params: $body, + timeout: $this->timeout + ); + + if ($requester_single_name['okay'] == 'true') + { + if ($requester_single_name['result'][5] != 'd') + throw new DeveloperError("No: {$currency_id}"); + $name = trim(hex2bin(substr($requester_single_name['result'], 6))); + $name = preg_replace('/[^\x20-\x7E]/', '', $name); + } + else + $name = ""; + + // symbol + $requester_single_symb = requester_single( + $this->select_node(), + endpoint: "/core/v2/contracts/call-read/{$cr_contract}/{$cr_contract_name}/get-symbol", + params: $body, + timeout: $this->timeout + ); + + if ($requester_single_symb['okay'] == 'true') + { + if ($requester_single_symb['result'][5] != 'd') + throw new DeveloperError("No: {$currency_id}"); + $symb = trim(hex2bin(substr($requester_single_symb['result'], 6))); + $symb = preg_replace('/[^\x20-\x7E]/', '', $symb); + } + else + $symb = ""; + + $currencies[] = [ + "id" => $currency_id, + "decimals" => $decimals, + "name" => $name, + "symbol" => $symb, + ]; + } + } + } + + $this_time = date('Y-m-d H:i:s'); + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = ($block_id !== MEMPOOL) ? $this->block_time : $this_time; + } + + $this->set_return_events($events); + $this->set_return_currencies($currencies); + } + + // Getting balances from the node + final function api_get_balance(string $address, array $currencies): array + { + if (!$currencies) + return []; + + $real_currencies = []; + $return = []; + + // Input currencies should be in format like this: `stacks-ft/{currency}` + foreach ($currencies as $c) + $real_currencies[] = explode('/', $c)[1]; + + $request = requester_single( + $this->select_node(), + endpoint: "/api/extended/v1/address/{$address}/balances", + result_in: 'fungible_tokens', + timeout: $this->timeout + ); + + foreach($real_currencies as $currency) + { + $found = false; + foreach ($request as $token_name => $balances) + { + $contract_pos = strpos($token_name, '::'); + $currency_id = substr($token_name, 0, $contract_pos); + if ($currency == $currency_id) + { + $return[] = $balances['balance']; + $found = true; + break; + } + } + if (!$found) + $return[] = '0'; + } + return $return; + } +} diff --git a/Modules/Common/StacksLikeMainModule.php b/Modules/Common/StacksLikeMainModule.php new file mode 100644 index 0000000..ff2bded --- /dev/null +++ b/Modules/Common/StacksLikeMainModule.php @@ -0,0 +1,290 @@ +version = 1; + } + + final public function post_post_initialize() + { + // + } + + final public function pre_process_block($block_id) + { + $curl_results = []; + if ($block_id == MEMPOOL) + { + $mempool = requester_single( + $this->select_node(), + endpoint: "/api/extended/v1/tx/mempool?limit={$this->limit}", + timeout: $this->timeout + ); + $amount = $mempool['total']; + $curl_results = array_merge($curl_results, $mempool['results']); + if (($amount - $this->limit) > $this->limit) + { + for ($offset = $this->limit; $offset <= ($amount); $offset += $this->limit) + { + $multi_curl[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/api/extended/v1/tx/mempool?limit={$this->limit}&offset=$offset", + timeout: $this->timeout + ); + } + $results = requester_multi( + $multi_curl, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout + ); + $results = requester_multi_process_all($results, reorder: false, result_in: 'results'); + foreach ($results as $r) + $curl_results = array_merge($curl_results, $r); + } + else + { + $curl_results = requester_single( + $this->select_node(), + endpoint: "/api/extended/v1/tx/mempool?limit={$this->limit}", + timeout: $this->timeout, + result_in: 'results' + ); + } + } + else + { + $block = requester_single( + $this->select_node(), + endpoint: "/api/extended/v2/blocks/{$block_id}", + timeout: $this->timeout + ); + $true_block_time = date('Y-m-d H:i:s', (int)$block['block_time']); + + $this->block_time = $true_block_time; + if ($block['tx_count'] == 0) + { + $this->set_return_currencies([]); + $this->set_return_events([]); + return; + } + + $multi_curl = []; + if ($block['tx_count'] > $this->limit) + { + for ($offset = 0; $offset <= $block['tx_count']; $offset += $this->limit) + { + $multi_curl[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/api/extended/v2/blocks/{$block_id}/transactions?limit={$this->limit}&offset=$offset", + timeout: $this->timeout + ); + } + + $results = requester_multi( + $multi_curl, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout + ); + $results = requester_multi_process_all($results, reorder: false, result_in: 'results'); + foreach ($results as $r) + $curl_results = array_merge($curl_results, $r); + } + else + { + $curl_results = requester_single( + $this->select_node(), + endpoint: "/api/extended/v2/blocks/{$block_id}/transactions?limit={$this->limit}", + timeout: $this->timeout, + result_in: 'results' + ); + } + } + + $multi_curl = []; + foreach ($curl_results as $tr) + { + $multi_curl[] = requester_multi_prepare( + $this->select_node(), + endpoint: "/api/extended/v1/tx/{$tr['tx_id']}", + timeout: $this->timeout + ); + } + $transactions = requester_multi( + $multi_curl, + limit: envm($this->module, 'REQUESTER_THREADS'), + timeout: $this->timeout, + valid_codes: [200, 500], # until we are totally sure that `requester_multi_process_all` ignores it + ); + + if ($block_id == MEMPOOL) + { + $output = []; + foreach ($transactions as $v) + { + $out = requester_multi_process($v, ignore_errors: true); + if(isset($out['statusCode'])) + continue; + $output[] = $out; + } + unset($transactions); + $transactions = $output; + } + else + $transactions = requester_multi_process_all($transactions, reorder: false); + + // sort according to tx index in the block + if ($block_id != MEMPOOL) + usort($transactions, function ($a, $b) { + return [$a['tx_index']] <=> [$b['tx_index']]; + }); + + + $events = []; + $sort_key = 0; + + foreach ($transactions as $op) + { + if ($block_id === MEMPOOL) + if (isset($this->processed_transactions[$op['tx_id']])) + continue; + else + $this->processed_transactions[$op['tx_id']] = true; + + if (isset($op['event_count']) && (int)$op['event_count'] > 0) + { + foreach ($op['events'] as $ev) + { + switch ($ev['event_type']) + { + case 'stx_asset': + { + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => $ev['asset']['sender'] ?: 'the-void', + 'sort_key' => $sort_key++, + 'effect' => '-' . $ev['asset']['amount'], + 'failed' => !($op['tx_status'] == 'success'), + 'extra' => null, + ]; + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => $ev['asset']['recipient'] ?: 'the-void', + 'sort_key' => $sort_key++, + 'effect' => $ev['asset']['amount'], + 'failed' => !($op['tx_status'] == 'success'), + 'extra' => null, + ]; + break; + } + case 'stx_lock': + { + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => $ev['stx_lock_event']['locked_address'], + 'sort_key' => $sort_key++, + 'effect' => '-' . $ev['stx_lock_event']['locked_amount'], + 'failed' => !($op['tx_status'] == 'success'), + 'extra' => null, + ]; + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => 'locker', + 'sort_key' => $sort_key++, + 'effect' => $ev['stx_lock_event']['locked_amount'], + 'failed' => !($op['tx_status'] == 'success'), + 'extra' => null, + ]; + break; + } + case 'smart_contract_log': + case 'fungible_token_asset': + case 'non_fungible_token_asset': + break; + default: + throw new ModuleException("Unknown event: " . $op['event_type'] . " in transaction: " . $op['tx_id']); + } + } + + } + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => $op['sender_address'], + 'sort_key' => $sort_key++, + 'effect' => '-' . $op['fee_rate'], + 'failed' => false, + 'extra' => 'f', + ]; + $events[] = [ + 'transaction' => $op['tx_id'], + 'address' => 'the-void', + 'sort_key' => $sort_key++, + 'effect' => $op['fee_rate'], + 'failed' => false, + 'extra' => 'f', + ]; + } + + $this_time = date('Y-m-d H:i:s'); + + foreach ($events as &$event) + { + $event['block'] = $block_id; + $event['time'] = ($block_id !== MEMPOOL) ? $this->block_time : $this_time; + } + + $this->set_return_events($events); + } + + // Getting balances from the node + final public function api_get_balance(string $address): string + { + $request = requester_single( + $this->select_node(), + endpoint: "/api/extended/v1/address/{$address}/balances", + result_in: 'stx', + timeout: $this->timeout + ); + + if (!isset($request['balance'])) + return '0'; + else + return (string)$request['balance']; + } +} diff --git a/Modules/Common/StacksLikeTraits.php b/Modules/Common/StacksLikeTraits.php new file mode 100644 index 0000000..859935b --- /dev/null +++ b/Modules/Common/StacksLikeTraits.php @@ -0,0 +1,52 @@ +select_node() . '/api/extended/v2/blocks?limit=1')['results'][0]['height']; + } + + public function ensure_block($block_id, $break_on_first = false) + { + $multi_curl = []; + + foreach ($this->nodes as $node) + { + $multi_curl[] = requester_multi_prepare($node . "/api/extended/v2/blocks/{$block_id}?limit=1", timeout: $this->timeout); + + if ($break_on_first) + break; + } + + try + { + $curl_results = requester_multi($multi_curl, limit: count($this->nodes), timeout: $this->timeout); + } + catch (RequesterException $e) + { + throw new RequesterException("ensure_ledger(ledger_index: {$block_id}): no connection, previously: " . $e->getMessage()); + } + + $hash = requester_multi_process($curl_results[0], result_in: 'hash'); + + if (count($curl_results) > 1) + { + foreach ($curl_results as $result) + { + if (requester_multi_process($result, result_in: 'hash') !== $hash) + { + throw new ConsensusException("ensure_ledger(ledger_index: {$block_id}): no consensus"); + } + } + } + + $this->block_hash = $hash; + } +} diff --git a/Modules/StacksFTModule.php b/Modules/StacksFTModule.php new file mode 100644 index 0000000..2c7e034 --- /dev/null +++ b/Modules/StacksFTModule.php @@ -0,0 +1,25 @@ +blockchain = 'stacks'; + $this->module = 'stacks-ft'; + $this->is_main = false; + $this->first_block_date = '2021-01-14'; + $this->first_block_id = 1; + $this->token_api = false; // on false it uses Stacks node, on true -- token API by Hiro which is inconsistent + + $this->tests = [ + ['block' => 289295, 'result' => 'a:2:{s:6:"events";a:2:{i:0;a:8:{s:11:"transaction";s:66:"0x05549a6b3c9329346fd0e2639631b8ecd2cda5d206895baa42ecd3348a318ad6";s:7:"address";s:57:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01";s:8:"currency";s:52:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex";s:8:"sort_key";i:0;s:6:"effect";s:12:"-86063186607";s:6:"failed";s:1:"f";s:5:"block";i:289295;s:4:"time";s:19:"2024-12-03 15:12:31";}i:1;a:8:{s:11:"transaction";s:66:"0x05549a6b3c9329346fd0e2639631b8ecd2cda5d206895baa42ecd3348a318ad6";s:7:"address";s:41:"SP3V49RD5EPQ7DVPH10VX1QABQ7KJBN48B2CGV0HR";s:8:"currency";s:52:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex";s:8:"sort_key";i:1;s:6:"effect";s:11:"86063186607";s:6:"failed";s:1:"f";s:5:"block";i:289295;s:4:"time";s:19:"2024-12-03 15:12:31";}}s:10:"currencies";a:1:{i:0;a:4:{s:2:"id";s:52:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex";s:8:"decimals";i:8;s:4:"name";s:10:"ALEX Token";s:6:"symbol";s:4:"ALEX";}}}'], + ['block' => 169234, 'result' => 'a:2:{s:6:"events";a:12:{i:0;a:8:{s:11:"transaction";s:66:"0xc7693a66aefbb79d566a64a39c674c542f8c0a21517cfcee36a2064d52e7805d";s:7:"address";s:57:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01";s:8:"currency";s:52:"SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-abtc";s:8:"sort_key";i:0;s:6:"effect";s:8:"-5570776";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:1;a:8:{s:11:"transaction";s:66:"0xc7693a66aefbb79d566a64a39c674c542f8c0a21517cfcee36a2064d52e7805d";s:7:"address";s:41:"SP2J1K2BX0HNTP8JEZ23MR3Z38JFPQA49X0BJ45AF";s:8:"currency";s:52:"SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-abtc";s:8:"sort_key";i:1;s:6:"effect";s:7:"5570776";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:2;a:8:{s:11:"transaction";s:66:"0xf90760aa13a5b8819c7f083162ba7b8bacef99a94507357685e311421aba9b1b";s:7:"address";s:57:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01";s:8:"currency";s:62:"SP3NE50GEXFG9SZGTT51P40X2CKYSZ5CC4ZTZ7A2G.welshcorgicoin-token";s:8:"sort_key";i:2;s:6:"effect";s:11:"-6797710484";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:3;a:8:{s:11:"transaction";s:66:"0xf90760aa13a5b8819c7f083162ba7b8bacef99a94507357685e311421aba9b1b";s:7:"address";s:41:"SP1DY3DQMVAA1F8JJAJBKPQ0HKQ1FZG67JG0YD5P3";s:8:"currency";s:62:"SP3NE50GEXFG9SZGTT51P40X2CKYSZ5CC4ZTZ7A2G.welshcorgicoin-token";s:8:"sort_key";i:3;s:6:"effect";s:10:"6797710484";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:4;a:8:{s:11:"transaction";s:66:"0x3fec9148f0cc0297f928a1cdc512d441c6e00334120bc964b7bf35067ded11ac";s:7:"address";s:66:"SP25K3XPVBNWXPMYDXBPSZHGC8APW0Z21CWJ3Y3B1.wen-nakamoto-stxcity-dex";s:8:"currency";s:62:"SP25K3XPVBNWXPMYDXBPSZHGC8APW0Z21CWJ3Y3B1.wen-nakamoto-stxcity";s:8:"sort_key";i:4;s:6:"effect";s:12:"-91906903237";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:5;a:8:{s:11:"transaction";s:66:"0x3fec9148f0cc0297f928a1cdc512d441c6e00334120bc964b7bf35067ded11ac";s:7:"address";s:41:"SP33QYJHG988R7XD10B5MPKG7NCWQJ77ETKMA2HK4";s:8:"currency";s:62:"SP25K3XPVBNWXPMYDXBPSZHGC8APW0Z21CWJ3Y3B1.wen-nakamoto-stxcity";s:8:"sort_key";i:5;s:6:"effect";s:11:"91906903237";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:6;a:8:{s:11:"transaction";s:66:"0xd052ed6c08ee8ea383f4b4727c570473dfd8335d625cf9a7b5960557f5de71ba";s:7:"address";s:60:"SPARHENQD3QXG6VTHPNQBXSA72508JZZA4NW7VNG.seedoil-stxcity-dex";s:8:"currency";s:56:"SPARHENQD3QXG6VTHPNQBXSA72508JZZA4NW7VNG.seedoil-stxcity";s:8:"sort_key";i:6;s:6:"effect";s:14:"-2119920537162";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:7;a:8:{s:11:"transaction";s:66:"0xd052ed6c08ee8ea383f4b4727c570473dfd8335d625cf9a7b5960557f5de71ba";s:7:"address";s:41:"SP33QYJHG988R7XD10B5MPKG7NCWQJ77ETKMA2HK4";s:8:"currency";s:56:"SPARHENQD3QXG6VTHPNQBXSA72508JZZA4NW7VNG.seedoil-stxcity";s:8:"sort_key";i:7;s:6:"effect";s:13:"2119920537162";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:8;a:8:{s:11:"transaction";s:66:"0x9279f953252c336114838f558d1398f602d9d94b78f974849e4bca75682c0b1f";s:7:"address";s:66:"SP2XNCG1PE6X2QAHWM11QKBQZJKN7EMS2HZKP9QHE.stacks-mouse-stxcity-dex";s:8:"currency";s:62:"SP2XNCG1PE6X2QAHWM11QKBQZJKN7EMS2HZKP9QHE.stacks-mouse-stxcity";s:8:"sort_key";i:8;s:6:"effect";s:12:"-88137922891";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:9;a:8:{s:11:"transaction";s:66:"0x9279f953252c336114838f558d1398f602d9d94b78f974849e4bca75682c0b1f";s:7:"address";s:41:"SP33QYJHG988R7XD10B5MPKG7NCWQJ77ETKMA2HK4";s:8:"currency";s:62:"SP2XNCG1PE6X2QAHWM11QKBQZJKN7EMS2HZKP9QHE.stacks-mouse-stxcity";s:8:"sort_key";i:9;s:6:"effect";s:11:"88137922891";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:10;a:8:{s:11:"transaction";s:66:"0xb9e980485199208bb920d690d63596648ff3e36deadd3be272b5785fa2ea011d";s:7:"address";s:41:"SP31TAM0T6NMVKHVFYJXKYQ09S671RDC2NPAD2HYA";s:8:"currency";s:65:"SPSCWDV3RKV5ZRN1FQD84YE1NQFEDJ9R1F4DYQ11.newyorkcitycoin-token-v2";s:8:"sort_key";i:10;s:6:"effect";s:11:"-8750000000";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}i:11;a:8:{s:11:"transaction";s:66:"0xb9e980485199208bb920d690d63596648ff3e36deadd3be272b5785fa2ea011d";s:7:"address";s:57:"SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01";s:8:"currency";s:65:"SPSCWDV3RKV5ZRN1FQD84YE1NQFEDJ9R1F4DYQ11.newyorkcitycoin-token-v2";s:8:"sort_key";i:11;s:6:"effect";s:10:"8750000000";s:6:"failed";s:1:"f";s:5:"block";i:169234;s:4:"time";s:19:"2024-10-10 08:04:06";}}s:10:"currencies";a:6:{i:0;a:4:{s:2:"id";s:52:"SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-abtc";s:8:"decimals";i:8;s:4:"name";s:4:"aBTC";s:6:"symbol";s:4:"aBTC";}i:1;a:4:{s:2:"id";s:62:"SP3NE50GEXFG9SZGTT51P40X2CKYSZ5CC4ZTZ7A2G.welshcorgicoin-token";s:8:"decimals";i:6;s:4:"name";s:14:"Welshcorgicoin";s:6:"symbol";s:5:"WELSH";}i:2;a:4:{s:2:"id";s:62:"SP25K3XPVBNWXPMYDXBPSZHGC8APW0Z21CWJ3Y3B1.wen-nakamoto-stxcity";s:8:"decimals";i:6;s:4:"name";s:12:"WEN NAKAMOTO";s:6:"symbol";s:3:"WEN";}i:3;a:4:{s:2:"id";s:56:"SPARHENQD3QXG6VTHPNQBXSA72508JZZA4NW7VNG.seedoil-stxcity";s:8:"decimals";i:6;s:4:"name";s:7:"SeedOil";s:6:"symbol";s:4:"SOIL";}i:4;a:4:{s:2:"id";s:62:"SP2XNCG1PE6X2QAHWM11QKBQZJKN7EMS2HZKP9QHE.stacks-mouse-stxcity";s:8:"decimals";i:6;s:4:"name";s:12:"Stacks mouse";s:6:"symbol";s:8:"Stxmouse";}i:5;a:4:{s:2:"id";s:65:"SPSCWDV3RKV5ZRN1FQD84YE1NQFEDJ9R1F4DYQ11.newyorkcitycoin-token-v2";s:8:"decimals";i:6;s:4:"name";s:15:"newyorkcitycoin";s:6:"symbol";s:3:"NYC";}}}'], + ]; + } +} diff --git a/Modules/StacksMainModule.php b/Modules/StacksMainModule.php new file mode 100644 index 0000000..6136001 --- /dev/null +++ b/Modules/StacksMainModule.php @@ -0,0 +1,27 @@ +blockchain = 'stacks'; + $this->module = 'stacks-main'; + $this->is_main = true; + $this->currency = 'stx'; + $this->currency_details = ['name' => 'STX', 'symbol' => 'STX', 'decimals' => 6, 'description' => null]; + $this->first_block_date = '2021-01-14'; + $this->first_block_id = 1; + $this->mempool_implemented = true; + + $this->tests = [ + ['block' => 289575, 'result' => 'a:2:{s:6:"events";a:12:{i:0;a:8:{s:11:"transaction";s:66:"0x853266296f67f718acd8c09ccb56cc8a5a08ea1a0b6d7e22354e8c68019dac08";s:7:"address";s:40:"SPX8T06E8FJQ33CX8YVR9CC6D9DSTF6JE0Y8R7DS";s:8:"sort_key";i:0;s:6:"effect";s:10:"-502300000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:1;a:8:{s:11:"transaction";s:66:"0x853266296f67f718acd8c09ccb56cc8a5a08ea1a0b6d7e22354e8c68019dac08";s:7:"address";s:41:"SP307BHDXSX759Z2XFAM405REWVFJK05HKG7BWRQB";s:8:"sort_key";i:1;s:6:"effect";s:9:"502300000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:2;a:8:{s:11:"transaction";s:66:"0x853266296f67f718acd8c09ccb56cc8a5a08ea1a0b6d7e22354e8c68019dac08";s:7:"address";s:40:"SPX8T06E8FJQ33CX8YVR9CC6D9DSTF6JE0Y8R7DS";s:8:"sort_key";i:2;s:6:"effect";s:7:"-700000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:3;a:8:{s:11:"transaction";s:66:"0x853266296f67f718acd8c09ccb56cc8a5a08ea1a0b6d7e22354e8c68019dac08";s:7:"address";s:8:"the-void";s:8:"sort_key";i:3;s:6:"effect";s:6:"700000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:4;a:8:{s:11:"transaction";s:66:"0xe558cb89d9d7a5ddffb597911f0b597e6391b16252fb9743f1a1c8bf851a0d65";s:7:"address";s:41:"SP3AP6DRSQ6P4FETB5M33D082Q2ABGJW60MT6103Q";s:8:"sort_key";i:4;s:6:"effect";s:11:"-1377500000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:5;a:8:{s:11:"transaction";s:66:"0xe558cb89d9d7a5ddffb597911f0b597e6391b16252fb9743f1a1c8bf851a0d65";s:7:"address";s:41:"SP187Y6HQJQH4CTDS5NRXDNDEWVCDA9BY3B0VMXD7";s:8:"sort_key";i:5;s:6:"effect";s:10:"1377500000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:6;a:8:{s:11:"transaction";s:66:"0xe558cb89d9d7a5ddffb597911f0b597e6391b16252fb9743f1a1c8bf851a0d65";s:7:"address";s:41:"SP3AP6DRSQ6P4FETB5M33D082Q2ABGJW60MT6103Q";s:8:"sort_key";i:6;s:6:"effect";s:7:"-500000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:7;a:8:{s:11:"transaction";s:66:"0xe558cb89d9d7a5ddffb597911f0b597e6391b16252fb9743f1a1c8bf851a0d65";s:7:"address";s:8:"the-void";s:8:"sort_key";i:7;s:6:"effect";s:6:"500000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:8;a:8:{s:11:"transaction";s:66:"0x92b46aee92a96c8e04f6f3cfefc6a4fb5a28827fbaf3053920964236c4263a06";s:7:"address";s:41:"SP27ANV45PCAG98PGFA2GVN9K7QYY1KWWS1V6RFSX";s:8:"sort_key";i:8;s:6:"effect";s:12:"-19156267000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:9;a:8:{s:11:"transaction";s:66:"0x92b46aee92a96c8e04f6f3cfefc6a4fb5a28827fbaf3053920964236c4263a06";s:7:"address";s:41:"SP2ADT4TAV92SPBNP3WE9GPJ12F6CKGWMEJ1H1BB9";s:8:"sort_key";i:9;s:6:"effect";s:11:"19156267000";s:6:"failed";s:1:"f";s:5:"extra";N;s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:10;a:8:{s:11:"transaction";s:66:"0x92b46aee92a96c8e04f6f3cfefc6a4fb5a28827fbaf3053920964236c4263a06";s:7:"address";s:41:"SP27ANV45PCAG98PGFA2GVN9K7QYY1KWWS1V6RFSX";s:8:"sort_key";i:10;s:6:"effect";s:7:"-300000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}i:11;a:8:{s:11:"transaction";s:66:"0x92b46aee92a96c8e04f6f3cfefc6a4fb5a28827fbaf3053920964236c4263a06";s:7:"address";s:8:"the-void";s:8:"sort_key";i:11;s:6:"effect";s:6:"300000";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:289575;s:4:"time";s:19:"2024-12-03 16:41:13";}}s:10:"currencies";N;}'], + ['block' => 12, 'result' => 'a:2:{s:6:"events";a:2:{i:0;a:8:{s:11:"transaction";s:66:"0x33f4d20bfa55945efb0bafea2299266e4149e95978b7b33ac930d9bdeabb36d2";s:7:"address";s:41:"SP3S9C931DEC0NZWEQBPCW6HGMN8T0TJHED5YDG5S";s:8:"sort_key";i:0;s:6:"effect";s:2:"-0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:12;s:4:"time";s:19:"2024-07-23 16:47:57";}i:1;a:8:{s:11:"transaction";s:66:"0x33f4d20bfa55945efb0bafea2299266e4149e95978b7b33ac930d9bdeabb36d2";s:7:"address";s:8:"the-void";s:8:"sort_key";i:1;s:6:"effect";s:1:"0";s:6:"failed";s:1:"f";s:5:"extra";s:1:"f";s:5:"block";i:12;s:4:"time";s:19:"2024-07-23 16:47:57";}}s:10:"currencies";N;}'], + ]; + } +} \ No newline at end of file