diff --git a/api.php b/api.php new file mode 100755 index 0000000..7ac69bf --- /dev/null +++ b/api.php @@ -0,0 +1,862 @@ +get_address($public_key)); +} elseif ($q == "base58") { + /** + * @api {get} /api.php?q=base58 03. base58 + * @apiName base58 + * @apiGroup API + * @apiDescription Converts a string to base58. + * + * @apiParam {string} data Input string + * + * @apiSuccess {string} data Output string + */ + + api_echo(base58_encode($data['data'])); +} elseif ($q == "getBalance") { + /** + * @api {get} /api.php?q=getBalance 04. getBalance + * @apiName getBalance + * @apiGroup API + * @apiDescription Returns the balance of a specific account or public key. + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * @apiParam {string} [alias] alias + * + * @apiSuccess {string} data The ARO balance + */ + + $public_key = $data['public_key']; + $account = $data['account']; + $alias = $data['alias']; + if (!empty($public_key) && strlen($public_key) < 32) { + api_err("Invalid public key"); + } + if (!empty($public_key)) { + $account = $acc->get_address($public_key); + } + if (!empty($alias)) { + $account = $acc->alias2account($alias); + } + if (empty($account)) { + api_err("Invalid account id"); + } + $account = san($account); + api_echo($acc->balance($account)); +} elseif ($q == "getPendingBalance") { + /** + * @api {get} /api.php?q=getPendingBalance 05. getPendingBalance + * @apiName getPendingBalance + * @apiGroup API + * @apiDescription Returns the pending balance, which includes pending transactions, of a specific account or public key. + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * + * @apiSuccess {string} data The ARO balance + */ + + $account = $data['account']; + $public_key = san($data['public_key'] ?? ''); + if (!empty($public_key) && strlen($public_key) < 32) { + api_err("Invalid public key"); + } + if (!empty($public_key)) { + $account = $acc->get_address($public_key); + } + if (empty($account)) { + api_err("Invalid account id"); + } + $account = san($account); + api_echo($acc->pending_balance($account)); +} elseif ($q == "getTransactions") { + /** + * @api {get} /api.php?q=getTransactions 06. getTransactions + * @apiName getTransactions + * @apiGroup API + * @apiDescription Returns the latest transactions of an account. + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * @apiParam {numeric} [limit] Number of confirmed transactions, max 100, min 1 + * + * @apiSuccess {string} block Block ID + * @apiSuccess {numeric} confirmation Number of confirmations + * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format + * @apiSuccess {string} dst Transaction destination + * @apiSuccess {numeric} fee The transaction's fee + * @apiSuccess {numeric} height Block height + * @apiSuccess {string} id Transaction ID/HASH + * @apiSuccess {string} message Transaction's message + * @apiSuccess {string} signature Transaction's signature + * @apiSuccess {string} public_key Account's public_key + * @apiSuccess {string} src Sender's address + * @apiSuccess {string} type "debit", "credit" or "mempool" + * @apiSuccess {numeric} val Transaction value + * @apiSuccess {numeric} version Transaction version + */ + + $account = san($data['account']); + $public_key = san($data['public_key'] ?? ''); + if (!empty($public_key) && strlen($public_key) < 32) { + api_err("Invalid public key"); + } + if (!empty($public_key)) { + $account = $acc->get_address($public_key); + } + if (empty($account)) { + api_err("Invalid account id"); + } + + $limit = intval($data['limit']); + $transactions = $acc->get_mempool_transactions($account); + $transactions = array_merge($transactions, $acc->get_transactions($account, $limit)); + api_echo($transactions); +} elseif ($q == "getTransaction") { + /** + * @api {get} /api.php?q=getTransaction 07. getTransaction + * @apiName getTransaction + * @apiGroup API + * @apiDescription Returns one transaction. + * + * @apiParam {string} transaction Transaction ID + * + * @apiSuccess {string} block Block ID + * @apiSuccess {numeric} confirmation Number of confirmations + * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format + * @apiSuccess {string} dst Transaction destination + * @apiSuccess {numeric} fee The transaction's fee + * @apiSuccess {numeric} height Block height + * @apiSuccess {string} id Transaction ID/HASH + * @apiSuccess {string} message Transaction's message + * @apiSuccess {string} signature Transaction's signature + * @apiSuccess {string} public_key Account's public_key + * @apiSuccess {string} src Sender's address + * @apiSuccess {string} type "debit", "credit" or "mempool" + * @apiSuccess {numeric} val Transaction value + * @apiSuccess {numeric} version Transaction version + */ + + $id = san($data['transaction']); + $res = $trx->get_transaction($id); + if ($res === false) { + $res = $trx->get_mempool_transaction($id); + if ($res === false) { + api_err("invalid transaction"); + } + } + api_Echo($res); +} elseif ($q == "getPublicKey") { + /** + * @api {get} /api.php?q=getPublicKey 08. getPublicKey + * @apiName getPublicKey + * @apiGroup API + * @apiDescription Returns the public key of a specific account. + * + * @apiParam {string} account Account id / address + * + * @apiSuccess {string} data The public key + */ + + $account = san($data['account']); + if (empty($account)) { + api_err("Invalid account id"); + } + $public_key = $acc->public_key($account); + if ($public_key === false) { + api_err("No public key found for this account"); + } else { + api_echo($public_key); + } +} elseif ($q == "generateAccount") { + /** + * @api {get} /api.php?q=generateAccount 09. generateAccount + * @apiName generateAccount + * @apiGroup API + * @apiDescription Generates a new account. This function should only be used when the node is on the same host or over a really secure network. + * + * @apiSuccess {string} address Account address + * @apiSuccess {string} public_key Public key + * @apiSuccess {string} private_key Private key + */ + + $acc = new Account(); + $res = $acc->generate_account(); + api_echo($res); +} elseif ($q == "currentBlock") { + /** + * @api {get} /api.php?q=currentBlock 10. currentBlock + * @apiName currentBlock + * @apiGroup API + * @apiDescription Returns the current block. + * + * @apiSuccess {string} id Blocks id + * @apiSuccess {string} generator Block Generator + * @apiSuccess {numeric} height Height + * @apiSuccess {numeric} date Block's date in UNIX TIMESTAMP format + * @apiSuccess {string} nonce Mining nonce + * @apiSuccess {string} signature Signature signed by the generator + * @apiSuccess {numeric} difficulty The base target / difficulty + * @apiSuccess {string} argon Mining argon hash + */ + + $current = $block->current(); + api_echo($current); +} elseif ($q == "getBlock") { + /** + * @api {get} /api.php?q=getBlock 11. getBlock + * @apiName getBlock + * @apiGroup API + * @apiDescription Returns the block. + * + * @apiParam {numeric} height Block Height + * + * @apiSuccess {string} id Block id + * @apiSuccess {string} generator Block Generator + * @apiSuccess {numeric} height Height + * @apiSuccess {numeric} date Block's date in UNIX TIMESTAMP format + * @apiSuccess {string} nonce Mining nonce + * @apiSuccess {string} signature Signature signed by the generator + * @apiSuccess {numeric} difficulty The base target / difficulty + * @apiSuccess {string} argon Mining argon hash + */ + $height = san($data['height']); + $ret = $block->get($height); + if ($ret == false) { + api_err("Invalid block"); + } else { + api_echo($ret); + } +} elseif ($q == "getBlockTransactions") { + /** + * @api {get} /api.php?q=getBlockTransactions 12. getBlockTransactions + * @apiName getBlockTransactions + * @apiGroup API + * @apiDescription Returns the transactions of a specific block. + * + * @apiParam {numeric} [height] Block Height + * @apiParam {string} [block] Block id + * @apiParam {boolean} [includeMiningRewards] Include mining rewards + * + * @apiSuccess {string} block Block ID + * @apiSuccess {numeric} confirmations Number of confirmations + * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format + * @apiSuccess {string} dst Transaction destination + * @apiSuccess {numeric} fee The transaction's fee + * @apiSuccess {numeric} height Block height + * @apiSuccess {string} id Transaction ID/HASH + * @apiSuccess {string} message Transaction's message + * @apiSuccess {string} signature Transaction's signature + * @apiSuccess {string} public_key Account's public_key + * @apiSuccess {string} src Sender's address + * @apiSuccess {string} type "debit", "credit" or "mempool" + * @apiSuccess {numeric} val Transaction value + * @apiSuccess {numeric} version Transaction version + */ + $height = san($data['height']); + $block = san($data['block']); + $includeMiningRewards = ( + isset($data['includeMiningRewards']) && + !($data['includeMiningRewards'] === '0' || $data['includeMiningRewards'] === 'false') + ); + + $ret = $trx->get_transactions($height, $block, $includeMiningRewards); + + if ($ret === false) { + api_err("Invalid block"); + } else { + api_echo($ret); + } +} elseif ($q == "version") { + /** + * @api {get} /api.php?q=version 13. version + * @apiName version + * @apiGroup API + * @apiDescription Returns the node's version. + * + * + * @apiSuccess {string} data Version + */ + api_echo(VERSION); +} elseif ($q == "send") { + /** + * @api {get} /api.php?q=send 14. send + * @apiName send + * @apiGroup API + * @apiDescription Sends a transaction. + * + * @apiParam {numeric} val Transaction value (without fees) + * @apiParam {string} dst Destination address + * @apiParam {string} public_key Sender's public key + * @apiParam {string} [signature] Transaction signature. It's recommended that the transaction is signed before being sent to the node to avoid sending your private key to the node. + * @apiParam {string} [private_key] Sender's private key. Only to be used when the transaction is not signed locally. + * @apiParam {numeric} [date] Transaction's date in UNIX TIMESTAMP format. Requried when the transaction is pre-signed. + * @apiParam {string} [message] A message to be included with the transaction. Maximum 128 chars. + * @apiParam {numeric} [version] The version of the transaction. 1 to send coins. + * + * @apiSuccess {string} data Transaction id + */ + $current = $block->current(); + + if ($current['height'] > 10790 && $current['height'] < 10810) { + api_err("Hard fork in progress. Please retry the transaction later!"); //10800 + } + + $acc = new Account(); + $block = new Block(); + + $trx = new Transaction(); + $version = intval($data['version']); + $dst = san($data['dst']); + if ($version < 1) { + $version = 1; + } + + + + if ($version==1) { + if (!$acc->valid($dst)) { + api_err("Invalid destination address"); + } + $dst_b = base58_decode($dst); + if (strlen($dst_b) != 64) { + api_err("Invalid destination address"); + } + } elseif ($version==2) { + $dst=strtoupper($dst); + $dst = san($dst); + if (!$acc->valid_alias($dst)) { + api_err("Invalid destination alias"); + } + } + + + + + $public_key = san($data['public_key']); + if (!$acc->valid_key($public_key)) { + api_err("Invalid public key"); + } + if ($_config['use_official_blacklist']!==false) { + if (Blacklist::checkPublicKey($public_key)) { + api_err("Blacklisted account"); + } + } + + + + $private_key = san($data['private_key']); + if (!$acc->valid_key($private_key)) { + api_err("Invalid private key"); + } + $signature = san($data['signature']); + if (!$acc->valid_key($signature)) { + api_err("Invalid signature"); + } + $date = $data['date'] + 0; + + if ($date == 0) { + $date = time(); + } + if ($date < time() - (3600 * 24 * 48)) { + api_err("The date is too old"); + } + if ($date > time() + 86400) { + api_err("Invalid Date"); + } + + + $message=$data['message']; + if (strlen($message) > 128) { + api_err("The message must be less than 128 chars"); + } + $val = $data['val'] + 0; + $fee = $val * 0.0025; + if ($fee < 0.00000001) { + $fee = 0.00000001; + } + + + if ($fee > 10 && $current['height'] > 10800) { + $fee = 10; //10800 + } + if ($val < 0) { + api_err("Invalid value"); + } + + + // set alias + if ($version==3) { + $fee=10; + $message = san($message); + $message=strtoupper($message); + if (!$acc->free_alias($message)) { + api_err("Invalid alias"); + } + if ($acc->has_alias($public_key)) { + api_err("This account already has an alias"); + } + } + + if ($version>=100&&$version<110) { + if ($version==100) { + $message=preg_replace("/[^0-9\.]/", "", $message); + if (!filter_var($message, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + api_err("Invalid Node IP - $message !"); + } + $val=100000; + } + } + + $val = number_format($val, 8, '.', ''); + $fee = number_format($fee, 8, '.', ''); + + + if (empty($public_key) && empty($private_key)) { + api_err("Either the private key or the public key must be sent"); + } + + + if (empty($private_key) && empty($signature)) { + api_err("Either the private_key or the signature must be sent"); + } + + + + if (empty($public_key)) { + $pk = coin2pem($private_key, true); + $pkey = openssl_pkey_get_private($pk); + $pub = openssl_pkey_get_details($pkey); + $public_key = pem2coin($pub['key']); + } + $transaction = [ + "val" => $val, + "fee" => $fee, + "dst" => $dst, + "public_key" => $public_key, + "date" => $date, + "version" => $version, + "message" => $message, + "signature" => $signature, + ]; + + if (!empty($private_key)) { + $signature = $trx->sign($transaction, $private_key); + $transaction['signature'] = $signature; + } + + + $hash = $trx->hash($transaction); + $transaction['id'] = $hash; + + + + + if (!$trx->check($transaction)) { + api_err("Transaction signature failed"); + } + + $res = $db->single("SELECT COUNT(1) FROM mempool WHERE id=:id", [":id" => $hash]); + if ($res != 0) { + api_err("The transaction is already in mempool"); + } + + $res = $db->single("SELECT COUNT(1) FROM transactions WHERE id=:id", [":id" => $hash]); + if ($res != 0) { + api_err("The transaction is already in a block"); + } + + + $src = $acc->get_address($public_key); + $transaction['src'] = $src; + $balance = $db->single("SELECT balance FROM accounts WHERE id=:id", [":id" => $src]); + if ($balance < $val + $fee) { + api_err("Not enough funds"); + } + + + $memspent = $db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:src", [":src" => $src]); + if ($balance - $memspent < $val + $fee) { + api_err("Not enough funds (mempool)"); + } + + + $trx->add_mempool($transaction, "local"); + $hash=escapeshellarg(san($hash)); + system("php propagate.php transaction $hash > /dev/null 2>&1 &"); + api_echo($hash); +} elseif ($q == "mempoolSize") { + /** + * @api {get} /api.php?q=mempoolSize 15. mempoolSize + * @apiName mempoolSize + * @apiGroup API + * @apiDescription Returns the number of transactions in mempool. + * + * @apiSuccess {numeric} data Number of mempool transactions + */ + + $res = $db->single("SELECT COUNT(1) FROM mempool"); + api_echo($res); +} elseif ($q == 'randomNumber') { + /** + * @api {get} /api.php?q=randomNumber 16. randomNumber + * @apiName randomNumber + * @apiGroup API + * @apiDescription Returns a random number based on an ARO block id. + * + * @apiParam {numeric} height The height of the block on which the random number will be based on (should be a future block when starting) + * @apiParam {numeric} min Minimum number (default 1) + * @apiParam {numeric} max Maximum number + * @apiParam {string} seed A seed to generate different numbers for each use cases. + * @apiSuccess {numeric} data The random number + */ + + $height = san($_GET['height']); + $max = intval($_GET['max']); + if (empty($_GET['min'])) { + $min = 1; + } else { + $min = intval($_GET['min']); + } + + $blk = $db->single("SELECT id FROM blocks WHERE height=:h", [":h" => $height]); + if ($blk === false) { + api_err("Unknown block. Future?"); + } + $base = hash("sha256", $blk.$_GET['seed']); + + $seed1 = hexdec(substr($base, 0, 12)); + // generate random numbers based on the seed + mt_srand($seed1, MT_RAND_MT19937); + $res = mt_rand($min, $max); + api_echo($res); +} elseif ($q == "checkSignature") { + /** + * @api {get} /api.php?q=checkSignature 17. checkSignature + * @apiName checkSignature + * @apiGroup API + * @apiDescription Checks a signature against a public key + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [signature] signature + * @apiParam {string} [data] signed data + * + * + * @apiSuccess {boolean} data true or false + */ + + $public_key=san($data['public_key']); + $signature=san($data['signature']); + $data=$data['data']; + + api_echo(ec_verify($data, $signature, $public_key)); +} elseif ($q == "masternodes") { + /** + * @api {get} /api.php?q=masternodes 18. masternodes + * @apiName masternodes + * @apiGroup API + * @apiDescription Returns all the masternode data + * + * @apiParam {string} [public_key] Optional public key + * + * @apiSuccess {json} data masternode date + */ + $bind=[]; + $whr=''; + $public_key=san($data['public_key']); + if(!empty($public_key)){ + $whr="WHERE public_key=:public_key"; + $bind[':public_key']=$public_key; + } + $res=$db->run("SELECT * FROM masternode $whr ORDER by public_key ASC",$bind); + + api_echo(["masternodes"=>$res, "hash"=>md5(json_encode($res))]); +} elseif ($q == "getAlias") { + /** + * @api {get} /api.php?q=getAlias 19. getAlias + * @apiName getAlias + * @apiGroup API + * @apiDescription Returns the alias of an account + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * + * + * @apiSuccess {string} data alias + */ + + $public_key = $data['public_key']; + $account = $data['account']; + if (!empty($public_key) && strlen($public_key) < 32) { + api_err("Invalid public key"); + } + if (!empty($public_key)) { + $account = $acc->get_address($public_key); + } + + if (empty($account)) { + api_err("Invalid account id"); + } + $account = san($account); + + api_echo($acc->account2alias($account)); +} elseif ($q === 'sanity') { + /** + * @api {get} /api.php?q=sanity 20. sanity + * @apiName sanity + * @apiGroup API + * @apiDescription Returns details about the node's sanity process. + * + * @apiSuccess {object} data A collection of data about the sanity process. + * @apiSuccess {boolean} data.sanity_running Whether the sanity process is currently running. + * @apiSuccess {number} data.last_sanity The timestamp for the last time the sanity process was run. + * @apiSuccess {boolean} data.sanity_sync Whether the sanity process is currently synchronising. + */ + $sanity = file_exists(__DIR__.'/tmp/sanity-lock'); + + $lastSanity = (int)$db->single("SELECT val FROM config WHERE cfg='sanity_last'"); + $sanitySync = (bool)$db->single("SELECT val FROM config WHERE cfg='sanity_sync'"); + api_echo(['sanity_running' => $sanity, 'last_sanity' => $lastSanity, 'sanity_sync' => $sanitySync]); +} elseif ($q === 'node-info') { + /** + * @api {get} /api.php?q=node-info 21. node-info + * @apiName node-info + * @apiGroup API + * @apiDescription Returns details about the node. + * + * @apiSuccess {object} data A collection of data about the node. + * @apiSuccess {string} data.hostname The hostname of the node. + * @apiSuccess {string} data.version The current version of the node. + * @apiSuccess {string} data.dbversion The database schema version for the node. + * @apiSuccess {number} data.accounts The number of accounts known by the node. + * @apiSuccess {number} data.transactions The number of transactions known by the node. + * @apiSuccess {number} data.mempool The number of transactions in the mempool. + * @apiSuccess {number} data.masternodes The number of masternodes known by the node. + * @apiSuccess {number} data.peers The number of valid peers. + */ + $dbVersion = $db->single("SELECT val FROM config WHERE cfg='dbversion'"); + $hostname = $db->single("SELECT val FROM config WHERE cfg='hostname'"); + $acc = $db->single("SELECT COUNT(1) FROM accounts"); + $tr = $db->single("SELECT COUNT(1) FROM transactions"); + $masternodes = $db->single("SELECT COUNT(1) FROM masternode"); + $mempool = $db->single("SELECT COUNT(1) FROM mempool"); + $peers = $db->single("SELECT COUNT(1) FROM peers WHERE blacklisted $hostname, + 'version' => VERSION, + 'dbversion' => $dbVersion, + 'accounts' => $acc, + 'transactions' => $tr, + 'mempool' => $mempool, + 'masternodes' => $masternodes, + 'peers' => $peers + ]); +} elseif ($q === 'checkAddress') { + /** + * @api {get} /api.php?q=checkAddress 22. checkAddress + * @apiName checkAddress + * @apiGroup API + * @apiDescription Checks the validity of an address. + * + * @apiParam {string} account Account id / address + * @apiParam {string} [public_key] Public key + * + * @apiSuccess {boolean} data True if the address is valid, false otherwise. + */ + + $address=$data['account']; + $public_key=$data['public_key']; + $acc = new Account(); + if (!$acc->valid($address)) { + api_err(false); + } + + $dst_b = base58_decode($address); + if (strlen($dst_b) != 64) { + api_err(false); + } + if (!empty($public_key)) { + if($acc->get_address($public_key)!=$address){ + api_err(false); + } + } + api_echo(true); +} elseif ($q === "assetBalance"){ + + $asset = san($data['asset']); + $public_key = san($data['public_key']); + $account = san($data['account']); + if (!empty($public_key) && strlen($public_key) < 32) { + api_err("Invalid public key"); + } + if (!empty($public_key)) { + $account = $acc->get_address($public_key); + } + + if(empty($asset)&&empty($account)){ + api_err("An asset or an account are necessary"); + } + + if(!empty($asset)&&!empty($account)){ + api_err("Choose either account or asset parameter"); + } + + $whr="WHERE assets_balance."; + $bind=[]; + if(!empty($asset)){ + $whr.="asset=:asset "; + $bind[':asset']=$asset; + } + elseif(!empty($account)){ + $whr.="account=:account "; + $bind[':account']=$account; + } + + $r=$db->run("SELECT asset, alias, account, assets_balance.balance FROM assets_balance LEFT JOIN accounts ON accounts.id=assets_balance.asset $whr LIMIT 1000",$bind); + + if ($r) + api_echo($r); + else + api_err("An asset or an account not found"); + +} elseif ($q === "asset-orders"){ + $asset = san($data['asset']); + $account = san($data['account']); + if(empty($asset)&&empty($account)){ + api_err("An asset or an account are necessary"); + } + $whr="status=0"; + $bind=[]; + if(!empty($asset)){ + $whr.=" AND asset=:asset "; + $bind[':asset']=$asset; + } + if(!empty($account)){ + $whr.=" AND account=:account "; + $bind[':account']=$account; + } + + $r=$db->run("SELECT * FROM assets_market WHERE $whr", $bind); + api_echo($r); + +} elseif ($q === "assets"){ + $asset = san($data['asset']); + $whr=""; + $bind=[]; + if(!empty($asset)){ + $whr.=" WHERE assets.id=:asset "; + $bind[':asset']=$asset; + } + $r=$db->run("SELECT assets.*, accounts.alias, accounts.balance FROM assets LEFT JOIN accounts ON accounts.id=assets.id $whr LIMIT 1000",$bind); + api_echo($r); + +} else { + api_err("Invalid request"); +}