<?php
declare(strict_types=1);
/*
|--------------------------------------------------------------------------
| Configuration
|--------------------------------------------------------------------------
*/
const API_URL = 'https://api.hive-engine.com/rpc/contracts';
const TOKEN = 'GLYPH';
const STAKE_REQUIRED = 5.0;
const REWARD_FOR_5 = 0.00071;
const PAGE_SIZE = 1000;
const EXCLUDED_ACCOUNT = 'we-are-ai';
/*
|--------------------------------------------------------------------------
| Helper functions
|--------------------------------------------------------------------------
*/
function format8(float $number): string
{
return number_format($number, 8, '.', '');
}
function apiFind(string $table, array $query, int $offset = 0): array
{
$payload = [
'jsonrpc' => '2.0',
'method' => 'find',
'params' => [
'contract' => 'tokens',
'table' => $table,
'query' => $query,
'limit' => PAGE_SIZE,
'offset' => $offset,
'indexes' => []
],
'id' => time()
];
$ch = curl_init(API_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json'
],
CURLOPT_TIMEOUT => 120,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
]);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception('cURL error: ' . $error);
}
curl_close($ch);
$json = json_decode($response, true);
if (!is_array($json)) {
throw new Exception('Invalid API response.');
}
if (isset($json['error'])) {
throw new Exception(
$json['error']['message'] ?? 'Unknown API error.'
);
}
return is_array($json['result'] ?? null)
? $json['result']
: [];
}
/*
|--------------------------------------------------------------------------
| Get eligible GLYPH accounts
|--------------------------------------------------------------------------
*/
function getGlyphAccounts(): array
{
$accounts = [];
$offset = 0;
while (true) {
$rows = apiFind(
'balances',
['symbol' => TOKEN],
$offset
);
if (empty($rows)) {
break;
}
foreach ($rows as $row) {
$account = trim((string)($row['account'] ?? ''));
if ($account === '') {
continue;
}
$stake = (float)($row['stake'] ?? 0);
$delegationIn = (float)(
$row['receivedStake']
?? $row['received_stake']
?? $row['delegatedIn']
?? $row['delegationIn']
?? 0
);
if ($stake <= 0 && $delegationIn <= 0) {
continue;
}
$totalEligible = $stake + $delegationIn;
$dailyReward = (
$totalEligible / STAKE_REQUIRED
) * REWARD_FOR_5;
$accounts[$account] = [
'account' => $account,
'stake' => $stake,
'delegationIn' => $delegationIn,
'totalEligible' => $totalEligible,
'dailyReward' => $dailyReward
];
}
if (count($rows) < PAGE_SIZE) {
break;
}
$offset += PAGE_SIZE;
}
usort(
$accounts,
static function (array $a, array $b): int {
return $b['totalEligible'] <=> $a['totalEligible'];
}
);
return array_values($accounts);
}
/*
|--------------------------------------------------------------------------
| CSV export
|--------------------------------------------------------------------------
*/
if (
isset($_GET['download']) &&
$_GET['download'] === 'csv'
) {
try {
ini_set('display_errors', '0');
error_reporting(0);
$accounts = getGlyphAccounts();
if (ob_get_length()) {
ob_clean();
}
header('Content-Type: text/csv; charset=UTF-8');
header(
'Content-Disposition: attachment; filename="glyph_rewards.csv"'
);
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
$output = fopen('php://output', 'w');
fwrite($output, "\xEF\xBB\xBF");
fputcsv($output, [
'ACCOUNT',
'DAILY REWARD',
'GLYPH',
'WIN STACK YOUR GLYPH'
], ',');
foreach ($accounts as $account) {
$accountName = strtolower(
trim($account['account'] ?? '')
);
if ($accountName === EXCLUDED_ACCOUNT) {
continue;
}
fputcsv($output, [
$account['account'],
format8((float)$account['dailyReward']),
'GLYPH',
'win stack your GLYPH'
], ',');
}
fclose($output);
exit;
} catch (Throwable $e) {
http_response_code(500);
header('Content-Type: text/plain; charset=UTF-8');
exit('CSV export error: ' . $e->getMessage());
}
}
/*
|--------------------------------------------------------------------------
| Normal page scan
|--------------------------------------------------------------------------
*/
$accounts = [];
$error = '';
$scanned = false;
if (isset($_GET['scan'])) {
try {
$accounts = getGlyphAccounts();
$scanned = true;
} catch (Throwable $e) {
$error = $e->getMessage();
}
}
$totalAccounts = count($accounts);
$totalGlyph = 0.0;
$totalReward = 0.0;
foreach ($accounts as $account) {
$totalGlyph += (float)$account['totalEligible'];
$totalReward += (float)$account['dailyReward'];
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GLYPH Reward Dashboard</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: Arial, Helvetica, sans-serif;
color: #ffffff;
background:
radial-gradient(circle at top left, #ff7eb3, transparent 35%),
radial-gradient(circle at bottom right, #6c63ff, transparent 35%),
linear-gradient(135deg, #141e30, #243b55);
padding: 30px 15px;
}
.container {
max-width: 1350px;
margin: auto;
}
.hero {
text-align: center;
padding: 35px 20px;
margin-bottom: 25px;
border-radius: 25px;
background: linear-gradient(
135deg,
rgba(255, 126, 179, 0.95),
rgba(108, 99, 255, 0.95)
);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.35);
}
.hero h1 {
margin: 0;
font-size: 42px;
text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2);
}
.hero p {
margin: 12px 0 0;
font-size: 18px;
}
.panel {
padding: 25px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.25);
backdrop-filter: blur(12px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.25);
}
.buttons {
display: flex;
justify-content: center;
gap: 15px;
flex-wrap: wrap;
margin-bottom: 25px;
}
.button {
display: inline-block;
padding: 14px 22px;
border-radius: 50px;
color: #ffffff;
text-decoration: none;
font-weight: bold;
transition: 0.25s;
box-shadow: 0 7px 15px rgba(0, 0, 0, 0.25);
}
.button:hover {
transform: translateY(-4px) scale(1.03);
box-shadow: 0 12px 22px rgba(0, 0, 0, 0.35);
}
.scan-button {
background: linear-gradient(135deg, #00c6ff, #0072ff);
}
.csv-button {
background: linear-gradient(135deg, #00f260, #0575e6);
}
.stats {
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(220px, 1fr)
);
gap: 18px;
margin-bottom: 25px;
}
.stat-card {
padding: 22px;
border-radius: 18px;
text-align: center;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.25),
rgba(255, 255, 255, 0.08)
);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.stat-card .icon {
font-size: 32px;
}
.stat-card span {
display: block;
margin-top: 8px;
color: #e6e6e6;
font-size: 14px;
}
.stat-card strong {
display: block;
margin-top: 8px;
color: #ffffff;
font-size: 24px;
}
.error {
padding: 15px;
margin-bottom: 20px;
border-radius: 12px;
color: #ffffff;
background: #e63946;
font-weight: bold;
}
.empty {
padding: 35px;
text-align: center;
border-radius: 16px;
background: rgba(0, 0, 0, 0.2);
color: #eeeeee;
font-size: 18px;
}
.table-wrapper {
overflow-x: auto;
border-radius: 15px;
}
table {
width: 100%;
min-width: 850px;
border-collapse: collapse;
overflow: hidden;
background: rgba(255, 255, 255, 0.95);
color: #222222;
}
th {
padding: 15px 12px;
background: linear-gradient(135deg, #ff512f, #dd2476);
color: #ffffff;
text-align: left;
white-space: nowrap;
}
td {
padding: 13px 12px;
border-bottom: 1px solid #dddddd;
}
tr:nth-child(even) {
background: #f5f7ff;
}
tr:hover {
background: #ffe5f1;
}
.number {
text-align: right;
font-family: Consolas, monospace;
}
.account-name {
font-weight: bold;
color: #5b21b6;
}
.footer {
margin-top: 25px;
text-align: center;
color: #dddddd;
font-size: 13px;
}
@media (max-width: 600px) {
body {
padding: 15px 8px;
}
.hero h1 {
font-size: 30px;
}
.panel {
padding: 15px;
}
.button {
width: 100%;
text-align: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="hero">
<h1>๏ฟฝ๏ฟฝ GLYPH Reward Dashboard</h1>
<p>Track staking, incoming delegations and daily rewards โจ</p>
</div>
<div class="panel">
<div class="buttons">
<a class="button scan-button" href="?scan=1">
๏ฟฝ๏ฟฝ Refresh Scan
</a>
<?php if ($scanned && !empty($accounts)): ?>
<a class="button csv-button" href="?download=csv">
๏ฟฝ๏ฟฝ Download CSV
</a>
<?php endif; ?>
</div>
<?php if ($error !== ''): ?>
<div class="error">
โ ๏ธ <?= htmlspecialchars(
$error,
ENT_QUOTES,
'UTF-8'
) ?>
</div>
<?php endif; ?>
<?php if ($scanned): ?>
<div class="stats">
<div class="stat-card">
<div class="icon">๏ฟฝ๏ฟฝ</div>
<span>Eligible Accounts</span>
<strong><?= $totalAccounts ?></strong>
</div>
<div class="stat-card">
<div class="icon">๏ฟฝ๏ฟฝ</div>
<span>Total Eligible GLYPH</span>
<strong><?= format8($totalGlyph) ?></strong>
</div>
<div class="stat-card">
<div class="icon">๏ฟฝ๏ฟฝ</div>
<span>Total Daily Rewards</span>
<strong><?= format8($totalReward) ?></strong>
</div>
</div>
<?php if (!empty($accounts)): ?>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>#
<th>ACCOUNT</th>
<th>STAKE</th>
<th>INCOMING DELEGATION</th>
<th>TOTAL ELIGIBLE</th>
<th>DAILY REWARD</th>
</tr>
</thead>
<tbody>
<?php foreach (
$accounts as $index => $account
): ?>
<tr>
<td><?= $index + 1 ?></td>
<td class="account-name">
<?= htmlspecialchars(
$account['account'],
ENT_QUOTES,
'UTF-8'
) ?>
</td>
<td class="number">
<?= format8(
(float)$account['stake']
) ?>
</td>
<td class="number">
<?= format8(
(float)$account['delegationIn']
) ?>
</td>
<td class="number">
<?= format8(
(float)$account['totalEligible']
) ?>
</td>
<td class="number">
<?= format8(
(float)$account['dailyReward']
) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty">
๏ฟฝ๏ฟฝ No eligible account found.
</div>
<?php endif; ?>
<?php else: ?>
<div class="empty">
๏ฟฝ๏ฟฝ Click โRefresh Scanโ to start scanning GLYPH accounts.
</div>
<?php endif; ?>
</div>
<div class="footer">
GLYPH staking reward calculator ๏ฟฝ๏ฟฝ
</div>
</div>
<style>
.token-transfer-patch {
margin-top: 25px;
padding: 25px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.25);
backdrop-filter: blur(12px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.25);
}
.token-transfer-patch h2 {
text-align: center;
margin-top: 0;
}
.token-transfer-patch label {
display: block;
margin-top: 12px;
margin-bottom: 6px;
font-weight: bold;
}
.token-transfer-patch input[type="text"],
.token-transfer-patch input[type="file"] {
width: 100%;
padding: 12px;
border: 0;
border-radius: 10px;
font-size: 15px;
}
.token-transfer-actions {
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
margin: 20px 0;
}
.token-transfer-actions button {
border: 0;
cursor: pointer;
padding: 14px 22px;
border-radius: 50px;
color: white;
font-weight: bold;
font-size: 15px;
box-shadow: 0 7px 15px rgba(0, 0, 0, 0.25);
}
.token-import-button {
background: linear-gradient(135deg, #ff9966, #ff5e62);
}
.token-send-button {
background: linear-gradient(135deg, #00c853, #64dd17);
}
.token-transfer-status {
margin-top: 18px;
padding: 14px;
border-radius: 12px;
text-align: center;
background: rgba(0, 0, 0, 0.25);
font-weight: bold;
}
#tokenTransferTable {
width: 100%;
min-width: 650px;
border-collapse: collapse;
background: white;
color: #222;
}
#tokenTransferTable th {
padding: 12px;
background: linear-gradient(135deg, #ff512f, #dd2476);
color: white;
text-align: left;
}
#tokenTransferTable td {
padding: 11px;
border-bottom: 1px solid #ddd;
}
</style>
<div class="token-transfer-patch">
<h2>๏ฟฝ๏ฟฝ Token Transfers</h2>
<label for="tokenTransferSender">
Sender Account
</label>
<input
type="text"
id="tokenTransferSender"
placeholder="your-account"
>
<label for="tokenTransferCsv">
CSV File
</label>
<input
type="file"
id="tokenTransferCsv"
accept=".csv"
>
<div class="token-transfer-actions">
<button
type="button"
class="token-import-button"
onclick="importTokenTransferCSV()"
>
๏ฟฝ๏ฟฝ Import CSV
</button>
<button
type="button"
class="token-send-button"
onclick="sendTokenTransfers()"
>
๏ฟฝ๏ฟฝ Send Tokens
</button>
</div>
<div class="table-wrapper">
<table id="tokenTransferTable">
<thead>
<tr>
<th>Recipient</th>
<th>Amount</th>
<th>Token</th>
<th>Memo</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div
id="tokenTransferStatus"
class="token-transfer-status"
>
No CSV file loaded.
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@hiveio/keychain/dist/keychain.min.js"></script>
<script>
let tokenTransferList = [];
function importTokenTransferCSV() {
const file = document.getElementById('tokenTransferCsv').files[0];
if (!file) {
alert('Please select a CSV file.');
return;
}
const reader = new FileReader();
reader.onload = function(event) {
const lines = event.target.result.split(/\r?\n/);
const tbody = document.querySelector(
'#tokenTransferTable tbody'
);
tokenTransferList = [];
tbody.innerHTML = '';
lines.forEach(function(line) {
line = line.trim();
if (!line) {
return;
}
const columns = line.split(',');
if (columns.length < 3) {
return;
}
const recipient = columns[0].trim();
const amount = columns[1].trim();
const token = columns[2].trim().toUpperCase();
const memo = columns.slice(3).join(',').trim();
if (
recipient.toLowerCase() === 'recipient' ||
recipient.toLowerCase() === 'account'
) {
return;
}
if (!recipient || !amount || !token) {
return;
}
const transfer = {
recipient: recipient,
amount: amount,
token: token,
memo: memo
};
tokenTransferList.push(transfer);
const row = document.createElement('tr');
[recipient, amount, token, memo].forEach(function(value) {
const cell = document.createElement('td');
cell.textContent = value;
row.appendChild(cell);
});
tbody.appendChild(row);
});
document.getElementById('tokenTransferStatus').textContent =
'โ
' + tokenTransferList.length + ' transfer(s) loaded.';
};
reader.readAsText(file);
}
function sendTokenTransfers() {
const sender = document
.getElementById('tokenTransferSender')
.value
.trim();
if (!sender) {
alert('Please enter the sender account.');
return;
}
if (tokenTransferList.length === 0) {
alert('Please import a CSV file first.');
return;
}
if (
typeof hive_keychain === 'undefined' ||
!hive_keychain.requestCustomJson
) {
alert('Hive Keychain is not available.');
return;
}
sendNextTokenTransfer(0, sender);
}
function sendNextTokenTransfer(index, sender) {
if (index >= tokenTransferList.length) {
document.getElementById('tokenTransferStatus').textContent =
'โ
All transfers have been completed.';
return;
}
const transfer = tokenTransferList[index];
const customJson = JSON.stringify({
contractName: 'tokens',
contractAction: 'transfer',
contractPayload: {
symbol: transfer.token,
to: transfer.recipient,
quantity: transfer.amount,
memo: transfer.memo
}
});
document.getElementById('tokenTransferStatus').textContent =
'โณ Sending transfer ' +
(index + 1) +
'/' +
tokenTransferList.length +
'...';
hive_keychain.requestCustomJson(
sender,
'ssc-mainnet-hive',
'Active',
customJson,
'Send ' + transfer.token,
function(response) {
if (response.success) {
setTimeout(function() {
sendNextTokenTransfer(index + 1, sender);
}, 1200);
} else {
document.getElementById(
'tokenTransferStatus'
).textContent =
'โ Transfer error: ' +
(
response.message ||
response.error ||
'Unknown error'
);
}
}
);
}
</script>
</body>
</html>
------------------------------------------------------
Good morning everyone, I know that some of you enjoy coding, so here is a small, simple, and interesting piece of code with a few explanations.
# ๐ Creating a Dashboard for the GLYPH Token
This PHP code creates a small web page capable of analyzing accounts that hold or receive delegations of **GLYPH**, and calculating a theoretical daily reward for each account.
The page also includes:
- A button to launch the analysis.
- A display showing the number of eligible accounts.
- The total amount of GLYPH taken into account.
- The total daily rewards.
- A detailed table for each account.
- A CSV export feature.
- A CSV import system.
- A token-sending function using Hive Keychain.
---
## ๐ 1. How does the code work?
### Configuration
At the beginning of the file, several constants make it easy to modify the main settings:
```php
const API_URL = 'https://api.hive-engine.com/rpc/contracts';
const TOKEN = 'GLYPH';
const STAKE_REQUIRED = 5.0;
const REWARD_FOR_5 = 0.00071;
const PAGE_SIZE = 1000;
const EXCLUDED_ACCOUNT = 'we-are-ai';
Here is the role of each element:
| Constant | Function |
|---|---|
API_URL | The Hive Engine API address used to retrieve the data |
TOKEN | The token being analyzed |
STAKE_REQUIRED | The amount of tokens required to obtain the reference reward |
REWARD_FOR_5 | The reward planned for that amount |
PAGE_SIZE | The number of results retrieved per request |
EXCLUDED_ACCOUNT | The account excluded from the CSV export |
In this example, the calculation is:
$dailyReward = (
$totalEligible / STAKE_REQUIRED
) * REWARD_FOR_5;
If an account has 10 GLYPH and 5 GLYPH generates 0.00071, then the account will theoretically receive twice the reward:
10 / 5 ร 0.00071 = 0.00142
This is therefore a proportional calculation.
๐ 2. Retrieving data from the API
The apiFind() function sends a request to the Hive Engine API.
function apiFind(string $table, array $query, int $offset = 0): array
It receives three pieces of information:
- The name of the table to query.
- The search criteria.
- The offset used to retrieve results page by page.
In the code, the request is used like this:
$rows = apiFind(
'balances',
['symbol' => TOKEN],
$offset
);
This means:
Search the
balancestable for all accounts holding the token defined in theTOKENconstant.
The API then returns information such as:
- The account name.
- The staked amount.
- Incoming delegations.
- The token symbol.
- Other information related to the balance.
๐ฅ 3. Finding eligible accounts
The main function used to analyze accounts is:
function getGlyphAccounts(): array
It goes through the results returned by the API.
The code retrieves the account name:
$account = trim((string)($row['account'] ?? ''));
Then it retrieves the staked amount:
$stake = (float)($row['stake'] ?? 0);
And the incoming delegations:
$delegationIn = (float)(
$row['receivedStake']
?? $row['received_stake']
?? $row['delegatedIn']
?? $row['delegationIn']
?? 0
);
This part is interesting because it checks several possible field names:
receivedStakereceived_stakedelegatedIndelegationIn
This makes the code more flexible if the field name changes depending on the API or the contract being used.
The code then ignores accounts that do not hold anything:
if ($stake <= 0 && $delegationIn <= 0) {
continue;
}
Then it adds the staking and incoming delegations together:
$totalEligible = $stake + $delegationIn;
Finally, it calculates the daily reward:
$dailyReward = (
$totalEligible / STAKE_REQUIRED
) * REWARD_FOR_5;
Each account is then stored in an array with several pieces of information:
$accounts[$account] = [
'account' => $account,
'stake' => $stake,
'delegationIn' => $delegationIn,
'totalEligible' => $totalEligible,
'dailyReward' => $dailyReward
];
๐ 4. Ranking the accounts
After retrieving the accounts, they are sorted from the highest eligible amount to the lowest:
usort(
$accounts,
static function (array $a, array $b): int {
return $b['totalEligible'] <=> $a['totalEligible'];
}
);
The first account displayed will therefore be the one holding the largest amount of eligible tokens.
๐ฅ 5. CSV export
When the following address is used:
your-page.php?download=csv
The code generates a CSV file containing:
- The account.
- The token.
- The daily reward.
- A text used as an instruction or memo.
The file header is created here:
fputcsv($output, [
'ACCOUNT',
'GLYPH',
'DAILY REWARD',
'WIN STACK YOUR GLYPH'
], ',');
Each account is then added to the file:
fputcsv($output, [
$account['account'],
'GLYPH',
format8((float)$account['dailyReward']),
'win stack your GLYPH'
], ',');
The account defined here:
const EXCLUDED_ACCOUNT = 'we-are-ai';
is excluded from the CSV file.
๐ช How can you use another token?
To analyze another token, the main modification is very simple.
You only need to change:
const TOKEN = 'GLYPH';
For example, to analyze a token called TEST:
const TOKEN = 'TEST';
Or another Hive Engine token:
const TOKEN = 'BEE';
The rest of the system will continue to work in the same way.
โ ๏ธ Also modify the token name in the CSV
Even though the search will use the new token correctly, the CSV file currently contains the text written directly into the code:
'GLYPH',
It is better to replace it with:
TOKEN,
The complete line becomes:
fputcsv($output, [
$account['account'],
TOKEN,
format8((float)$account['dailyReward']),
'win stack your ' . TOKEN
], ',');
That way, if you change the constant:
const TOKEN = 'BEE';
The CSV will automatically contain:
BEE
instead of GLYPH.
โ๏ธ Automatically change the page title
The current title is written directly into the HTML:
<title>GLYPH Reward Dashboard</title>
You can replace it with:
<title><?= htmlspecialchars(TOKEN) ?> Reward Dashboard</title>
The title will then automatically adapt to the selected token.
You can also change the main heading:
<h1>๏ฟฝ๏ฟฝ GLYPH Reward Dashboard</h1>
To:
<h1>๏ฟฝ๏ฟฝ <?= htmlspecialchars(TOKEN) ?> Reward Dashboard</h1>
And the subtitle:
<p>Track staking, incoming delegations and daily rewards โจ</p>
For example:
<p>
Track staking, delegations and rewards
for <?= htmlspecialchars(TOKEN) ?> โจ
</p>
๐ง The three main changes needed to switch tokens
To summarize, the three most useful changes are:
1. Change the token symbol
const TOKEN = 'ANOTHER_TOKEN';
2. Automatically use the symbol in the CSV
Replace:
'GLYPH',
With:
TOKEN,
3. Automatically use the symbol in the titles
Replace text containing GLYPH with:
<?= htmlspecialchars(TOKEN) ?>
With these few changes, the page becomes reusable for several Hive Engine tokens.
๐ What else can be analyzed for a token?
The current code mainly analyzes:
- The account.
- Staking.
- Incoming delegations.
- The total eligible amount.
- The calculated reward.
However, depending on the token, the API may provide other information.
For example, you could display:
- The available balance.
- Tokens delegated to other accounts.
- Incoming delegations.
- Recent transfers.
- The total number of holders.
- The richest accounts.
- The total circulating supply.
- Active accounts.
- Rewards already distributed.
- The date of the latest operations.
- Accounts that have sent or received the most tokens.
๐ฐ Adding the available balance
The current code mainly uses:
$row['stake']
To also display the liquid balance, you could retrieve a field such as:
$balance = (float)($row['balance'] ?? 0);
Then add it to the account data:
$accounts[$account] = [
'account' => $account,
'balance' => $balance,
'stake' => $stake,
'delegationIn' => $delegationIn,
'totalEligible' => $totalEligible,
'dailyReward' => $dailyReward
];
You would then need to add a column to the HTML table:
<th>AVAILABLE BALANCE</th>
And display the value:
<td class="number">
<?= format8((float)$account['balance']) ?>
</td>
You could then compare:
Available balance + staking + incoming delegations
๐ค Adding outgoing delegations
To find out how many tokens an account has delegated to other users, you would need to retrieve another field, if it is available in the API response:
$delegationOut = (float)(
$row['delegatedOut']
?? $row['delegationOut']
?? 0
);
Then add it to the account data:
'delegationOut' => $delegationOut,
You could then calculate the amount that is actually available:
$netEligible = $stake
+ $delegationIn
- $delegationOut;
This would prevent tokens delegated to someone else from being counted as available.
๐ Adding a ranking based on wealth
The current ranking uses:
$b['totalEligible'] <=> $a['totalEligible']
You can easily rank the accounts according to another criterion.
Ranking by daily reward
usort(
$accounts,
static function (array $a, array $b): int {
return $b['dailyReward'] <=> $a['dailyReward'];
}
);
Ranking by staking
usort(
$accounts,
static function (array $a, array $b): int {
return $b['stake'] <=> $a['stake'];
}
);
Ranking by available balance
usort(
$accounts,
static function (array $a, array $b): int {
return $b['balance'] <=> $a['balance'];
}
);
You only need to change one logical line to modify the ranking.
๐งฎ Modifying the reward calculation
The current calculation is linear:
$dailyReward = (
$totalEligible / STAKE_REQUIRED
) * REWARD_FOR_5;
This means:
- 5 tokens provide the reference reward.
- 10 tokens provide twice that reward.
- 20 tokens provide four times that reward.
You could also add a maximum limit:
$dailyReward = min(
($totalEligible / STAKE_REQUIRED) * REWARD_FOR_5,
0.05000000
);
In this example, the reward can never exceed 0.05.
You could also create a tier-based system:
if ($totalEligible >= 100) {
$dailyReward = 0.02000000;
} elseif ($totalEligible >= 50) {
$dailyReward = 0.01000000;
} elseif ($totalEligible >= 5) {
$dailyReward = 0.00071000;
} else {
$dailyReward = 0;
}
This allows you to assign a different reward depending on several levels.
๐ Analyzing token transfers
The current code queries the following table:
'balances'
To analyze token movements, you would need to query a table related to transfers, usually called:
'contracts'
or another specific table depending on the possibilities offered by the API.
You could search for token operations and display:
- The sender.
- The recipient.
- The amount.
- The date.
- The memo.
- The type of operation.
The principle would be similar to the current function:
$rows = apiFind(
'contracts',
['symbol' => TOKEN],
$offset
);
However, you should verify the exact table and field names available for the token or contract being used. Not all tokens provide exactly the same data.
๐ Adding the date and time of the latest transfer
If the API returns a date or block identifier, you could add information such as:
$lastTransfer = $row['timestamp'] ?? '';
Then display it in the table:
<td>
<?= htmlspecialchars(
(string)$account['lastTransfer'],
ENT_QUOTES,
'UTF-8'
) ?>
</td>
This would allow you to see which accounts have been recently active.
๐ A few important points
Although this code is useful for creating a dashboard, a few improvements are recommended.
1. Do not disable SSL verification
The code contains:
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
This disables SSL security verification. In a production environment, it is better to use:
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2
2. Check the data received from the API
Fields such as stake, balance, or receivedStake may vary depending on the contract and API version. It is therefore important to check the actual response before using them.
3. The CSV reader is very simple
The code uses:
const columns = line.split(',');
This works for a basic CSV file, but it can cause problems if a field contains a comma inside quotation marks. For a larger project, it would be better to use a proper CSV parser.
4. Test transfers before sending them
The sending function uses Hive Keychain and requests a transaction for each line in the file. It is recommended to:
- Check the recipients.
- Check the amounts.
- Test with small amounts.
- Add a confirmation step before sending.
- Avoid loading a CSV file from an unknown source.
5. Transfers are sent from the browser
Transactions are signed by Hive Keychain, which is safer than storing a private key on the server. You should never place a private key directly in this PHP file or in the JavaScript code.
โ Summary
This code is a good starting point for creating a simple dashboard around a Hive Engine token.
It already allows you to:
- Search for accounts holding a token.
- Add staking and incoming delegations together.
- Calculate a daily reward.
- Rank accounts.
- Export the results to a CSV file.
- Import a payment CSV file.
- Send tokens using Hive Keychain.
To adapt it to another token, the essential change is very limited:
const TOKEN = 'ANOTHER_TOKEN';
Then replace text written directly into the code, such as:
'GLYPH'
With:
TOKEN
Depending on the information available through the API, you can also add:
- Available balances.
- Outgoing delegations.
- Transfers.
- Activity dates.
- Rankings.
- Reward limits.
- Tier-based systems.
- Additional statistics.
With only two or three well-placed changes, this small dashboard can become a much more complete tool for tracking, analyzing, and distributing rewards for a token.