diff --git a/CMakeLists.txt b/CMakeLists.txt index d084e17..7ba9c7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ SET(FILES ${CMAKE_SOURCE_DIR}/asset_utils.cpp ${CMAKE_SOURCE_DIR}/qbond.cpp ${CMAKE_SOURCE_DIR}/qearn.cpp ${CMAKE_SOURCE_DIR}/qpi_adapter.cpp + ${CMAKE_SOURCE_DIR}/qrwa.cpp ${CMAKE_SOURCE_DIR}/qswap.cpp ${CMAKE_SOURCE_DIR}/quottery.cpp ${CMAKE_SOURCE_DIR}/qutil.cpp @@ -47,6 +48,7 @@ SET(HEADER_FILES oracle_utils.h prompt.h proposal.h + qrwa.h qbond.h qearn.h qswap.h diff --git a/README.md b/README.md index f656f14..ec7812b 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,45 @@ Commands: -msvaultgetvaultowners Get MsVault owners given vault ID. +[QRWA COMMANDS] + -qrwapayout [epoch] + -qrwapayout
[epoch] + Show payout ring buffer for the specified pool or address. + Pool A = Qubic Mining, Pool B = SC Assets, Pool C = BTC Mining, Pool D = MLM Water. + Optional epoch filter: only show payouts for the given epoch. + -qrwastatus + Show qRWA totals (distributed per pool) and configured contract addresses. + -qrwaassets + Show assets and tokens held by the qRWA contract. + -qrwagovparams + Show current governance parameters (admin, cost addresses, percentages). + -qrwagovpoll + Show details of a governance poll by ID. + -qrwagovpollids + List active governance poll IDs. + -qrwadividends + Show dividend balances for all pools (revenue, QMINE/qRWA splits). + -qrwascdividends + Show SC dividend tracking — Pool B revenue sources from other smart contracts. + -qrwadonate + Donate QMINE to the treasury. Requires prior QX management rights transfer. + Valid seed and node ip/port are required. + -qrwavotegov + Submit or vote on a governance parameter proposal. All 5 addresses and 3 percentages required. + Valid seed and node ip/port are required. + -qrwasetpoolaaddr
+ Set Pool A revenue address (admin-only). + Valid seed and node ip/port are required. + -qrwasetpooldaddr
+ Set Pool D revenue address (admin-only). + Valid seed and node ip/port are required. + -qrwadepositasset + Deposit a general asset into the contract (admin-only). + Valid seed and node ip/port are required. + -qrwarevokeasset + Revoke asset management rights back to QX (100 QU fee). + Valid seed and node ip/port are required. + [QSWAP COMMANDS] -qswapgetfee Show current Qswap fees. diff --git a/argparser.h b/argparser.h index 6613ff1..f4ce10f 100644 --- a/argparser.h +++ b/argparser.h @@ -181,6 +181,37 @@ void print_help() printf("\t-querypriceviacontract <...>\n"); printf("\t\tSend price query via contract. Useful for testing contract queries and subscriptions. Skip arguments to get detailed documentation.\n"); + printf("\n[QRWA COMMANDS]\n"); + printf("\t-qrwapayout [epoch]\n"); + printf("\t-qrwapayout
[epoch]\n"); + printf("\t\tShow payout ring buffer for the specified pool.\n"); + printf("\t-qrwastatus\n"); + printf("\t\tShow qRWA totals and configured addresses.\n"); + printf("\t-qrwaassets\n"); + printf("\t\tShow assets and tokens held by the qRWA contract.\n"); + printf("\t-qrwagovparams\n"); + printf("\t\tShow current governance parameters.\n"); + printf("\t-qrwagovpoll \n"); + printf("\t\tShow details of a governance poll by ID.\n"); + printf("\t-qrwagovpollids\n"); + printf("\t\tList active governance poll IDs.\n"); + printf("\t-qrwadividends\n"); + printf("\t\tShow dividend balances for all pools.\n"); + printf("\t-qrwascdividends\n"); + printf("\t\tShow SC dividend tracking (Pool B revenue sources).\n"); + printf("\t-qrwadonate \n"); + printf("\t\tDonate QMINE to the treasury (requires prior QX management rights transfer).\n"); + printf("\t-qrwavotegov \n"); + printf("\t\tSubmit or vote on a governance parameter proposal.\n"); + printf("\t-qrwasetpoolaaddr
\n"); + printf("\t\tSet Pool A revenue address (admin-only).\n"); + printf("\t-qrwasetpooldaddr
\n"); + printf("\t\tSet Pool D revenue address (admin-only).\n"); + printf("\t-qrwadepositasset \n"); + printf("\t\tDeposit a general asset into the contract (admin-only).\n"); + printf("\t-qrwarevokeasset \n"); + printf("\t\tRevoke asset management rights back to QX (100 QU fee).\n"); + printf("\n[SMART CONTRACT COMMANDS]\n"); printf("\t-callcontractfunction \n"); printf("\t\tCall a contract function of contract index and print the output. Valid node ip/port are required.\t\n"); @@ -1178,6 +1209,156 @@ void parseArgument(int argc, char** argv) break; } + /************************* + ***** QRWA COMMANDS ***** + *************************/ + + if (strcmp(argv[i], "-qrwapayout") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + if (strcmp(argv[i + 1], "pool_a") == 0) + g_cmd = QRWA_PAYOUT_POOL_A; + else if (strcmp(argv[i + 1], "pool_b") == 0) + g_cmd = QRWA_PAYOUT_POOL_B; + else if (strcmp(argv[i + 1], "pool_c") == 0) + g_cmd = QRWA_PAYOUT_POOL_C; + else if (strcmp(argv[i + 1], "pool_d") == 0) + g_cmd = QRWA_PAYOUT_POOL_D; + else if (strlen(argv[i + 1]) == 60) + { + g_cmd = QRWA_PAYOUT_ADDRESS; + g_qrwa_identity = argv[i + 1]; + } + else { LOG("Invalid argument '%s'. Use pool_a, pool_b, pool_c, pool_d, or a 60-char address.\n", argv[i + 1]); exit(1); } + if (i + 2 < argc && argv[i + 2][0] != '-') + { + g_qrwa_epoch = atoi(argv[i + 2]); + i += 3; + } + else + { + i += 2; + } + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwastatus") == 0) + { + g_cmd = QRWA_STATUS; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwaassets") == 0) + { + g_cmd = QRWA_ASSETS; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwagovparams") == 0) + { + g_cmd = QRWA_GOV_PARAMS; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwagovpoll") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QRWA_GOV_POLL; + g_qrwa_poll_id = strtoull(argv[i + 1], nullptr, 10); + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwagovpollids") == 0) + { + g_cmd = QRWA_GOV_POLL_IDS; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwadividends") == 0) + { + g_cmd = QRWA_DIVIDENDS; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwascdividends") == 0) + { + g_cmd = QRWA_SC_DIVIDENDS; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwadonate") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QRWA_DONATE_TREASURY; + g_qrwa_amount = strtoull(argv[i + 1], nullptr, 10); + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwavotegov") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(8) + g_cmd = QRWA_VOTE_GOV_PARAMS; + g_qrwa_gov_admin = argv[i + 1]; + g_qrwa_gov_electricity = argv[i + 2]; + g_qrwa_gov_maintenance = argv[i + 3]; + g_qrwa_gov_reinvestment = argv[i + 4]; + g_qrwa_gov_qminedev = argv[i + 5]; + g_qrwa_gov_electricity_pct = strtoull(argv[i + 6], nullptr, 10); + g_qrwa_gov_maintenance_pct = strtoull(argv[i + 7], nullptr, 10); + g_qrwa_gov_reinvestment_pct = strtoull(argv[i + 8], nullptr, 10); + i += 9; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwasetpoolaaddr") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QRWA_SET_POOL_A_ADDR; + g_qrwa_new_address = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwasetpooldaddr") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QRWA_SET_POOL_D_ADDR; + g_qrwa_new_address = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwadepositasset") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(3) + g_cmd = QRWA_DEPOSIT_ASSET; + g_qrwa_issuer = argv[i + 1]; + g_qrwa_asset_name = argv[i + 2]; + g_qrwa_amount = strtoull(argv[i + 3], nullptr, 10); + i += 4; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qrwarevokeasset") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(3) + g_cmd = QRWA_REVOKE_ASSET_MGMT; + g_qrwa_issuer = argv[i + 1]; + g_qrwa_asset_name = argv[i + 2]; + g_qrwa_num_shares = strtoll(argv[i + 3], nullptr, 10); + i += 4; + CHECK_OVER_PARAMETERS + break; + } + /*********************** ***** QX COMMANDS ***** ***********************/ diff --git a/global.h b/global.h index 620fb42..404bb18 100644 --- a/global.h +++ b/global.h @@ -260,6 +260,26 @@ int64_t g_qbond_burnAmount = 0; char* g_qbond_owner = nullptr; bool g_qbond_updateCFAOperation = false; +// qrwa +uint8_t g_qrwa_pool = 0; // 0=pool_a 1=pool_b 2=pool_c +int g_qrwa_epoch = -1; // -1 = all epochs, >=0 = filter by epoch +char* g_qrwa_identity = nullptr; // address filter for -qrwapayout
+uint64_t g_qrwa_poll_id = 0; // governance poll ID +uint64_t g_qrwa_amount = 0; // amount for donate/deposit +char* g_qrwa_issuer = nullptr; // asset issuer for deposit/revoke +char* g_qrwa_asset_name = nullptr; // asset name for deposit/revoke +int64_t g_qrwa_num_shares = 0; // for revoke management rights +char* g_qrwa_new_address = nullptr; // for SetPoolARevenueAddress +// Gov params for VoteGovParams +char* g_qrwa_gov_admin = nullptr; +char* g_qrwa_gov_electricity = nullptr; +char* g_qrwa_gov_maintenance = nullptr; +char* g_qrwa_gov_reinvestment = nullptr; +char* g_qrwa_gov_qminedev = nullptr; +uint64_t g_qrwa_gov_electricity_pct = 0; +uint64_t g_qrwa_gov_maintenance_pct = 0; +uint64_t g_qrwa_gov_reinvestment_pct = 0; + // escrow char* g_escrowAcceptorId = nullptr; char* g_escrow_offeredAssetsCommaSeparated = nullptr; diff --git a/main.cpp b/main.cpp index 58091cb..e100107 100644 --- a/main.cpp +++ b/main.cpp @@ -22,6 +22,7 @@ #include "test_utils.h" #include "nostromo.h" #include "qbond.h" +#include "qrwa.h" #include "escrow.h" int run(int argc, char* argv[]) @@ -328,6 +329,95 @@ int run(int argc, char* argv[]) sanityCheckSeed(g_seed); quotteryCancelBet(g_nodeIp, g_nodePort, g_seed, g_quottery_betId, g_offsetScheduledTick); break; + case QRWA_PAYOUT_POOL_A: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaPayout(g_nodeIp, g_nodePort, QRWA_POOL_A, g_qrwa_epoch); + break; + case QRWA_PAYOUT_POOL_B: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaPayout(g_nodeIp, g_nodePort, QRWA_POOL_B, g_qrwa_epoch); + break; + case QRWA_PAYOUT_POOL_C: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaPayout(g_nodeIp, g_nodePort, QRWA_POOL_C, g_qrwa_epoch); + break; + case QRWA_PAYOUT_POOL_D: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaPayout(g_nodeIp, g_nodePort, QRWA_POOL_D, g_qrwa_epoch); + break; + case QRWA_PAYOUT_ADDRESS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaPayoutAddress(g_nodeIp, g_nodePort, g_qrwa_identity, g_qrwa_epoch); + break; + case QRWA_STATUS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaStatus(g_nodeIp, g_nodePort); + break; + case QRWA_ASSETS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaAssets(g_nodeIp, g_nodePort); + break; + case QRWA_GOV_PARAMS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaGovParams(g_nodeIp, g_nodePort); + break; + case QRWA_GOV_POLL: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaGovPoll(g_nodeIp, g_nodePort, g_qrwa_poll_id); + break; + case QRWA_GOV_POLL_IDS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaGovPollIds(g_nodeIp, g_nodePort); + break; + case QRWA_DIVIDENDS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaDividends(g_nodeIp, g_nodePort); + break; + case QRWA_SC_DIVIDENDS: + sanityCheckNode(g_nodeIp, g_nodePort); + qrwaScDividends(g_nodeIp, g_nodePort); + break; + case QRWA_DONATE_TREASURY: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qrwaDonateToTreasury(g_nodeIp, g_nodePort, g_seed, g_qrwa_amount, g_offsetScheduledTick); + break; + case QRWA_VOTE_GOV_PARAMS: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + QRWAGovParams proposal = {}; + getPublicKeyFromIdentity(g_qrwa_gov_admin, proposal.mAdminAddress); + getPublicKeyFromIdentity(g_qrwa_gov_electricity, proposal.electricityAddress); + getPublicKeyFromIdentity(g_qrwa_gov_maintenance, proposal.maintenanceAddress); + getPublicKeyFromIdentity(g_qrwa_gov_reinvestment, proposal.reinvestmentAddress); + getPublicKeyFromIdentity(g_qrwa_gov_qminedev, proposal.qmineDevAddress); + proposal.electricityPercent = g_qrwa_gov_electricity_pct; + proposal.maintenancePercent = g_qrwa_gov_maintenance_pct; + proposal.reinvestmentPercent = g_qrwa_gov_reinvestment_pct; + qrwaVoteGovParams(g_nodeIp, g_nodePort, g_seed, proposal, g_offsetScheduledTick); + break; + } + case QRWA_SET_POOL_A_ADDR: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qrwaSetPoolARevenueAddress(g_nodeIp, g_nodePort, g_seed, g_qrwa_new_address, g_offsetScheduledTick); + break; + case QRWA_SET_POOL_D_ADDR: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qrwaSetPoolDRevenueAddress(g_nodeIp, g_nodePort, g_seed, g_qrwa_new_address, g_offsetScheduledTick); + break; + case QRWA_DEPOSIT_ASSET: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qrwaDepositGeneralAsset(g_nodeIp, g_nodePort, g_seed, g_qrwa_issuer, g_qrwa_asset_name, g_qrwa_amount, g_offsetScheduledTick); + break; + case QRWA_REVOKE_ASSET_MGMT: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qrwaRevokeAssetMgmt(g_nodeIp, g_nodePort, g_seed, g_qrwa_issuer, g_qrwa_asset_name, g_qrwa_num_shares, g_offsetScheduledTick); + break; case TOOGLE_MAIN_AUX: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); diff --git a/qpi_adapter.cpp b/qpi_adapter.cpp index 33965bb..e0bb8b6 100644 --- a/qpi_adapter.cpp +++ b/qpi_adapter.cpp @@ -40,9 +40,9 @@ std::string toString(const QPI::DateAndTime& dt) { char buffer[100]; if (dt.getMicrosecDuringMillisec()) - sprintf(buffer, "%04d-%02d-%02d_%02d:%02d:%02d.%03d'%03d", dt.getYear(), dt.getMonth(), dt.getDay(), dt.getHour(), dt.getMinute(), dt.getSecond(), dt.getMillisec(), dt.getMicrosecDuringMillisec()); + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d_%02d:%02d:%02d.%03d'%03d", dt.getYear(), dt.getMonth(), dt.getDay(), dt.getHour(), dt.getMinute(), dt.getSecond(), dt.getMillisec(), dt.getMicrosecDuringMillisec()); else - sprintf(buffer, "%04d-%02d-%02d_%02d:%02d:%02d.%03d", dt.getYear(), dt.getMonth(), dt.getDay(), dt.getHour(), dt.getMinute(), dt.getSecond(), dt.getMillisec()); + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d_%02d:%02d:%02d.%03d", dt.getYear(), dt.getMonth(), dt.getDay(), dt.getHour(), dt.getMinute(), dt.getSecond(), dt.getMillisec()); std::string str(buffer); if (!dt.isValid()) str += " (invalid date)"; diff --git a/qrwa.cpp b/qrwa.cpp new file mode 100644 index 0000000..6ac0769 --- /dev/null +++ b/qrwa.cpp @@ -0,0 +1,805 @@ +#include +#include +#include + +#include "structs.h" +#include "connection.h" +#include "wallet_utils.h" +#include "logger.h" +#include "key_utils.h" +#include "contracts.h" +#include "asset_utils.h" +#include "qrwa.h" +#include "k12_and_key_utils.h" + +// ─── Helpers ─────────────────────────────────────────────────── + +static const char* payoutTypeName(uint8_t t) +{ + switch (t) + { + case QRWA_PAYOUT_TYPE_QMINE_HOLDER: return "QMINE-Holder"; + case QRWA_PAYOUT_TYPE_QMINE_DEV: return "QMINE-Dev"; + case QRWA_PAYOUT_TYPE_QRWA_HOLDER: return "qRWA-Holder"; + case QRWA_PAYOUT_TYPE_DEDICATED_QRWA: return "Dedicated-qRWA"; + case QRWA_PAYOUT_TYPE_POOL_D_QRWA: return "PoolD-qRWA"; + default: return "Unknown"; + } +} + +static bool isZeroKey(const uint8_t key[32]) +{ + for (int i = 0; i < 32; i++) + if (key[i] != 0) return false; + return true; +} + +static void printIdentity(const char* label, const uint8_t pubkey[32]) +{ + char id[128] = {}; + getIdentityFromPublicKey(pubkey, id, false); + LOG("%s%s\n", label, id); +} + +// Compute a deterministic payout hash from the entry data. +// Hash = K12(recipient[32] || amount[8] || tick[4] || epoch[2] || payoutType[1] || pool[1]) +// This mirrors what the contract would compute with qpi.K12(). +static void computePayoutHash(const QRWAPayoutEntry& e, uint8_t pool, char* hashOut) +{ + uint8_t buf[48]; // 32 + 8 + 4 + 2 + 1 + 1 = 48 + memcpy(buf, e.recipient, 32); + memcpy(buf + 32, &e.amount, 8); + memcpy(buf + 40, &e.tick, 4); + memcpy(buf + 44, &e.epoch, 2); + buf[46] = e.payoutType; + buf[47] = pool; + + uint8_t digest[32] = {}; + KangarooTwelve(buf, sizeof(buf), digest, 32); + getTxHashFromDigest(digest, hashOut); +} + +// Print paginated payout results (entries already ordered newest-first by contract). +// epochFilter: -1 = show all, >=0 = only show entries matching that epoch +template +static uint32_t printPayoutPage(const OutputT& result, const char* poolLabel, int epochFilter) +{ + const auto& payouts = result.payouts; + uint16_t count = result.returnedCount; + + // If no raw entries on this page, nothing to show + bool hasAnyEntry = false; + for (uint16_t i = 0; i < count; i++) + { + if (payouts[i].amount != 0 || !isZeroKey(payouts[i].recipient)) + { hasAnyEntry = true; break; } + } + if (!hasAnyEntry) + return 0; + + uint64_t totalAmount = 0; + uint32_t entryCount = 0; + uint64_t typeTotal[5] = {}; + uint32_t typeCount[5] = {}; + + for (uint16_t i = 0; i < count; i++) + { + if (payouts[i].amount == 0 && isZeroKey(payouts[i].recipient)) + continue; + if (epochFilter >= 0 && payouts[i].epoch != (uint16_t)epochFilter) + continue; + entryCount++; + totalAmount += payouts[i].amount; + uint8_t t = payouts[i].payoutType; + if (t < 5) + { + typeTotal[t] += payouts[i].amount; + typeCount[t]++; + } + } + + if (entryCount == 0) + return 0; + + if (epochFilter >= 0) + LOG("\n═══ %s Payouts — Epoch %d (page %u/%u) ═══════════════\n", + poolLabel, epochFilter, (unsigned)result.page + 1, (unsigned)result.totalPages); + else + LOG("\n═══ %s Payouts (page %u/%u) ══════════════════════════\n", + poolLabel, (unsigned)result.page + 1, (unsigned)result.totalPages); + LOG(" Entries: %u Total QU: %llu\n", + entryCount, (unsigned long long)totalAmount); + LOG(" Ring buffer: %llu slots, nextIdx=%u\n", + (unsigned long long)QRWA_PAYOUT_RING_SIZE, (unsigned)result.nextIdx); + + for (int t = 0; t < 5; t++) + { + if (typeCount[t] == 0) continue; + LOG(" Type %d (%s): %u entries, %llu QU\n", + t, payoutTypeName(t), typeCount[t], (unsigned long long)typeTotal[t]); + } + + LOG("\n Payouts (newest first):\n"); + LOG(" %-60s %16s %12s %12s %10s %6s %s\n", "Recipient", "Amount", "QMINE", "qRWA", "Tick", "Epoch", "Type"); + LOG(" %-60s %16s %12s %12s %10s %6s %s\n", + "------------------------------------------------------------", + "----------------", "------------", "------------", "----------", "------", "----------------"); + + for (uint16_t i = 0; i < count; i++) + { + const auto& e = payouts[i]; + if (e.amount == 0 && isZeroKey(e.recipient)) + continue; + if (epochFilter >= 0 && e.epoch != (uint16_t)epochFilter) + continue; + + char identity[128] = {}; + getIdentityFromPublicKey(e.recipient, identity, false); + + LOG(" %-60s %16llu %12llu %12llu %10u %6u %s\n", + identity, + (unsigned long long)e.amount, + (unsigned long long)e.qmineHolding, + (unsigned long long)e.qrwaHolding, + e.tick, + (unsigned)e.epoch, + payoutTypeName(e.payoutType)); + } + + return entryCount; +} + +// ─── qrwaPayout — one command per pool ───────────────────────── + +void qrwaPayout(const char* nodeIp, int nodePort, QRWAPoolType pool, int epochFilter) +{ + // Always show totals first + QRWAGetTotalDistributed_output totals = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_TOTAL_DISTRIBUTED, nullptr, 0, &totals, sizeof(totals))) + { + LOG("totalPoolADistributed (Qubic Mining): %llu QU\n", (unsigned long long)totals.totalPoolADistributed); + LOG("totalPoolBDistributed (SC Assets): %llu QU\n", (unsigned long long)totals.totalPoolBDistributed); + LOG("totalPoolCDistributed (BTC Mining): %llu QU\n", (unsigned long long)totals.totalPoolCDistributed); + LOG("totalPoolDDistributed (MLM Water): %llu QU\n", (unsigned long long)totals.totalPoolDDistributed); + } + else + { + LOG("ERROR: Could not query GetTotalDistributed (fn 6)\n"); + } + + if (epochFilter >= 0) + LOG("Filtering by epoch: %d\n", epochFilter); + + // Each pool command shows exactly one ring buffer: + // pool_a → fn 11 (Pool A payouts, after gov fees) + // pool_b → fn 13 (Pool B payouts, no gov fees) + // pool_c → fn 14 (Pool C payouts, dedicated) + struct RingQuery { uint8_t fnId; const char* label; }; + RingQuery queries[1] = {}; + + switch (pool) + { + case QRWA_POOL_A: + queries[0] = { QRWA_GET_PAYOUTS_QMINE, "Pool A — Qubic Mining (fn 11)" }; + break; + case QRWA_POOL_B: + queries[0] = { QRWA_GET_PAYOUTS_QRWA, "Pool B — SC Assets Revenue (fn 13)" }; + break; + case QRWA_POOL_C: + queries[0] = { QRWA_GET_PAYOUTS_DEDICATED, "Pool C — BTC Mining (fn 14)" }; + break; + case QRWA_POOL_D: + queries[0] = { QRWA_GET_PAYOUTS_POOL_D, "Pool D — MLM Water (fn 16)" }; + break; + } + + for (uint16_t page = 0; ; page++) + { + QRWAGetPayouts_input pageInput = {}; + pageInput.page = page; + + bool ok = false; + uint32_t found = 0; + + if (queries[0].fnId == QRWA_GET_PAYOUTS_QMINE) + { + QRWAGetPayoutsQmine_output result = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + queries[0].fnId, &pageInput, sizeof(pageInput), &result, sizeof(result)); + if (ok) + { + found = printPayoutPage(result, queries[0].label, epochFilter); + if (page + 1 >= result.totalPages) break; + } + } + else if (queries[0].fnId == QRWA_GET_PAYOUTS_QRWA) + { + QRWAGetPayoutsQrwa_output result = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + queries[0].fnId, &pageInput, sizeof(pageInput), &result, sizeof(result)); + if (ok) + { + found = printPayoutPage(result, queries[0].label, epochFilter); + if (page + 1 >= result.totalPages) break; + } + } + else if (queries[0].fnId == QRWA_GET_PAYOUTS_DEDICATED) + { + QRWAGetPayoutsDedicated_output result = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + queries[0].fnId, &pageInput, sizeof(pageInput), &result, sizeof(result)); + if (ok) + { + found = printPayoutPage(result, queries[0].label, epochFilter); + if (page + 1 >= result.totalPages) break; + } + } + else if (queries[0].fnId == QRWA_GET_PAYOUTS_POOL_D) + { + QRWAGetPayoutsPoolD_output result = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + queries[0].fnId, &pageInput, sizeof(pageInput), &result, sizeof(result)); + if (ok) + { + found = printPayoutPage(result, queries[0].label, epochFilter); + if (page + 1 >= result.totalPages) break; + } + } + + if (!ok) + { + LOG(" (fn %u: keine Eintraege / Ring-Buffer leer)\n", (unsigned)queries[0].fnId); + break; + } + if (epochFilter < 0) break; // no filter: first page only + if (found == 0) break; // no more matching entries + } + + // For Pool B: show SC Dividend Tracker (fn 15) — revenue sources + if (pool == QRWA_POOL_B) + { + QRWAGetScDividendTracking_output scTracker = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_SC_DIVIDEND_TRACKING, nullptr, 0, &scTracker, sizeof(scTracker)) + && scTracker.count > 0) + { + LOG("\n═══ SC Dividend Sources (fn 15) — Revenue an Pool B ═══════════════\n"); + LOG(" Tracked SC assets: %llu\n\n", (unsigned long long)scTracker.count); + LOG(" %-60s %8s %20s\n", "SC Contract", "Name", "Cumulative QU"); + LOG(" %-60s %8s %20s\n", + "------------------------------------------------------------", + "--------", "--------------------"); + for (uint64_t i = 0; i < scTracker.count && i < QRWA_MAX_ASSETS; i++) + { + char scIdentity[128] = {}; + getIdentityFromPublicKey(scTracker.scContractIds[i], scIdentity, false); + // Extract contract index from first 8 bytes of the public key + uint64_t idx = 0; + memcpy(&idx, scTracker.scContractIds[i], sizeof(uint64_t)); + const char* name = getContractName((uint32_t)idx); + if (!name) name = "?"; + LOG(" %-60s %8s %20llu\n", + scIdentity, name, + (unsigned long long)scTracker.cumulativeDividends[i]); + } + } + } +} + +// ─── qrwaPayoutAddress — payouts for a specific address ──────── + +void qrwaPayoutAddress(const char* nodeIp, int nodePort, const char* identity, int epochFilter) +{ + uint8_t targetKey[32] = {}; + getPublicKeyFromIdentity(identity, targetKey); + + char verifiedIdentity[128] = {}; + getIdentityFromPublicKey(targetKey, verifiedIdentity, false); + + LOG("qRWA Payouts for: %s\n", verifiedIdentity); + if (epochFilter >= 0) + LOG("Filtering by epoch: %d\n", epochFilter); + + struct PoolQuery { + uint8_t fnId; + const char* label; + }; + PoolQuery pools[4] = { + { QRWA_GET_PAYOUTS_QMINE, "Pool A (Qubic Mining)" }, + { QRWA_GET_PAYOUTS_QRWA, "Pool B (SC Assets)" }, + { QRWA_GET_PAYOUTS_DEDICATED, "Pool C (BTC Mining)" }, + { QRWA_GET_PAYOUTS_POOL_D, "Pool D (MLM Water)" }, + }; + + uint32_t grandTotal = 0; + uint64_t grandAmount = 0; + + for (int p = 0; p < 4; p++) + { + uint32_t poolEntries = 0; + uint64_t poolAmount = 0; + bool headerPrinted = false; + + for (uint16_t page = 0; ; page++) + { + QRWAGetPayouts_input pageInput = {}; + pageInput.page = page; + + // All three output structs have identical layout + QRWAGetPayoutsQmine_output result = {}; + bool ok = false; + + if (pools[p].fnId == QRWA_GET_PAYOUTS_QMINE) + { + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + pools[p].fnId, &pageInput, sizeof(pageInput), &result, sizeof(result)); + } + else if (pools[p].fnId == QRWA_GET_PAYOUTS_QRWA) + { + QRWAGetPayoutsQrwa_output r = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + pools[p].fnId, &pageInput, sizeof(pageInput), &r, sizeof(r)); + if (ok) memcpy(&result, &r, sizeof(result)); + } + else if (pools[p].fnId == QRWA_GET_PAYOUTS_DEDICATED) + { + QRWAGetPayoutsDedicated_output r = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + pools[p].fnId, &pageInput, sizeof(pageInput), &r, sizeof(r)); + if (ok) memcpy(&result, &r, sizeof(result)); + } + else + { + QRWAGetPayoutsPoolD_output r = {}; + ok = runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + pools[p].fnId, &pageInput, sizeof(pageInput), &r, sizeof(r)); + if (ok) memcpy(&result, &r, sizeof(result)); + } + + if (!ok) break; + + for (uint16_t i = 0; i < result.returnedCount; i++) + { + const auto& e = result.payouts[i]; + if (e.amount == 0 && isZeroKey(e.recipient)) + continue; + if (memcmp(e.recipient, targetKey, 32) != 0) + continue; + if (epochFilter >= 0 && e.epoch != (uint16_t)epochFilter) + continue; + + if (!headerPrinted) + { + LOG("\n═══ %s ═══════════════════════════════════\n", pools[p].label); + LOG(" %16s %12s %12s %10s %6s %-16s %s\n", + "Amount", "QMINE", "qRWA", "Tick", "Epoch", "Type", "PayoutHash"); + LOG(" %16s %12s %12s %10s %6s %-16s %s\n", + "----------------", "------------", "------------", + "----------", "------", "----------------", + "------------------------------------------------------------"); + headerPrinted = true; + } + + char payoutHash[64] = {}; + computePayoutHash(e, (uint8_t)p, payoutHash); + + LOG(" %16llu %12llu %12llu %10u %6u %-16s %s\n", + (unsigned long long)e.amount, + (unsigned long long)e.qmineHolding, + (unsigned long long)e.qrwaHolding, + e.tick, + (unsigned)e.epoch, + payoutTypeName(e.payoutType), + payoutHash); + + poolEntries++; + poolAmount += e.amount; + } + + if (page + 1 >= result.totalPages) break; + } + + if (poolEntries > 0) + { + LOG(" ── %u payouts, total %llu QU ──\n", poolEntries, (unsigned long long)poolAmount); + grandTotal += poolEntries; + grandAmount += poolAmount; + } + } + + if (grandTotal == 0) + LOG("\n (keine Payouts fuer diese Adresse gefunden)\n"); + else + LOG("\n GESAMT: %u payouts, %llu QU\n", grandTotal, (unsigned long long)grandAmount); +} + +// ─── qrwaStatus — overview (totals + addresses) ─────────────── + +void qrwaStatus(const char* nodeIp, int nodePort) +{ + LOG("═══ qRWA Contract Status ═══════════════════════════════\n\n"); + + // Totals + QRWAGetTotalDistributed_output totals = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_TOTAL_DISTRIBUTED, nullptr, 0, &totals, sizeof(totals))) + { + LOG("Total Distributed:\n"); + LOG(" Pool A (Qubic Mining): %llu QU\n", (unsigned long long)totals.totalPoolADistributed); + LOG(" Pool B (SC Assets): %llu QU\n", (unsigned long long)totals.totalPoolBDistributed); + LOG(" Pool C (BTC Mining): %llu QU\n", (unsigned long long)totals.totalPoolCDistributed); + LOG(" Pool D (MLM Water): %llu QU\n", (unsigned long long)totals.totalPoolDDistributed); + LOG(" Payout QMINE Begin: %llu\n", (unsigned long long)totals.payoutTotalQmineBegin); + } + else + { + LOG("ERROR: Could not query GetTotalDistributed (fn 6)\n"); + } + + // Contract addresses + QRWAGetContractAddresses_output addrs = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_CONTRACT_ADDRESSES, nullptr, 0, &addrs, sizeof(addrs))) + { + LOG("\nContract Addresses:\n"); + printIdentity(" Pool A Revenue: ", addrs.poolARevenueAddress); + printIdentity(" Pool C Dedicated: ", addrs.dedicatedRevenueAddress); + printIdentity(" Pool D Revenue: ", addrs.poolDRevenueAddress); + printIdentity(" Fundraising: ", addrs.fundraisingAddress); + printIdentity(" Exchange: ", addrs.exchangeAddress); + } + else + { + LOG("ERROR: Could not query GetContractAddresses (fn 12)\n"); + } + + // Treasury + QRWAGetTreasuryBalance_output treasury = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_TREASURY_BALANCE, nullptr, 0, &treasury, sizeof(treasury))) + { + LOG("\nTreasury:\n"); + LOG(" QMINE Balance: %llu\n", (unsigned long long)treasury.balance); + } + + // Dividend balances + QRWAGetDividendBalances_output divs = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_DIVIDEND_BALANCES, nullptr, 0, &divs, sizeof(divs))) + { + LOG("\nDividend Balances:\n"); + LOG(" Revenue Pool A: %llu QU\n", (unsigned long long)divs.revenuePoolA); + LOG(" Revenue Pool B: %llu QU\n", (unsigned long long)divs.revenuePoolB); + LOG(" Dedicated Revenue: %llu QU\n", (unsigned long long)divs.dedicatedRevenuePool); + LOG(" Pool A QMINE Div: %llu QU\n", (unsigned long long)divs.poolAQmineDividend); + LOG(" Pool A qRWA Div: %llu QU\n", (unsigned long long)divs.poolAQrwaDividend); + LOG(" Pool B QMINE Div: %llu QU\n", (unsigned long long)divs.poolBQmineDividend); + LOG(" Pool B qRWA Div: %llu QU\n", (unsigned long long)divs.poolBQrwaDividend); + LOG(" Pool C QMINE Div: %llu QU\n", (unsigned long long)divs.poolCQmineDividend); + LOG(" Pool C qRWA Div: %llu QU\n", (unsigned long long)divs.poolCQrwaDividend); + LOG(" Pool D Revenue: %llu QU\n", (unsigned long long)divs.poolDRevenuePool); + LOG(" Pool D QMINE Div: %llu QU\n", (unsigned long long)divs.poolDQmineDividend); + LOG(" Pool D qRWA Div: %llu QU\n", (unsigned long long)divs.poolDQrwaDividend); + } + + // Governance params + QRWAGetGovParams_output gov = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_GOV_PARAMS, nullptr, 0, &gov, sizeof(gov))) + { + LOG("\nGovernance Parameters:\n"); + printIdentity(" Admin: ", gov.params.mAdminAddress); + printIdentity(" Electricity: ", gov.params.electricityAddress); + printIdentity(" Maintenance: ", gov.params.maintenanceAddress); + printIdentity(" Reinvestment: ", gov.params.reinvestmentAddress); + printIdentity(" QMINE Dev: ", gov.params.qmineDevAddress); + LOG(" Electricity %%: %llu\n", (unsigned long long)gov.params.electricityPercent); + LOG(" Maintenance %%: %llu\n", (unsigned long long)gov.params.maintenancePercent); + LOG(" Reinvestment %%: %llu\n", (unsigned long long)gov.params.reinvestmentPercent); + } +} + +// ─── qrwaAssets — show assets held by the contract ───────────── + +void qrwaAssets(const char* nodeIp, int nodePort) +{ + // Treasury balance (QMINE tokens held) + QRWAGetTreasuryBalance_output treasury = {}; + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_TREASURY_BALANCE, nullptr, 0, &treasury, sizeof(treasury))) + { + LOG("QMINE Treasury Balance: %llu QU\n", (unsigned long long)treasury.balance); + } + else + { + LOG("ERROR: Could not query GetTreasuryBalance (fn 4)\n"); + } + + // General assets (non-QMINE tokens/shares) + QRWAGetGeneralAssets_output* assets = new QRWAGetGeneralAssets_output(); + memset(assets, 0, sizeof(QRWAGetGeneralAssets_output)); + if (runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_GENERAL_ASSETS, nullptr, 0, assets, sizeof(QRWAGetGeneralAssets_output))) + { + uint64_t count = assets->count; + LOG("\nGeneral Assets held by qRWA contract: %llu\n", (unsigned long long)count); + + if (count > QRWA_MAX_ASSETS) count = QRWA_MAX_ASSETS; + + for (uint64_t i = 0; i < count; i++) + { + char assetName[8] = {}; + assetNameToString(assets->assets[i].assetName, assetName); + + char issuerIdentity[128] = {}; + getIdentityFromPublicKey(assets->assets[i].issuer, issuerIdentity, false); + + LOG(" [%llu] %s / %s — Balance: %llu\n", + (unsigned long long)(i + 1), + assetName, + issuerIdentity, + (unsigned long long)assets->balances[i]); + } + + if (count == 0) + { + LOG(" (no assets)\n"); + } + } + else + { + LOG("ERROR: Could not query GetGeneralAssets (fn 10)\n"); + } + delete assets; +} + +// ─── qrwaGovParams — show current governance parameters ──────── + +void qrwaGovParams(const char* nodeIp, int nodePort) +{ + QRWAGetGovParams_output result = {}; + if (!runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_GOV_PARAMS, nullptr, 0, &result, sizeof(result))) + { + LOG("ERROR: Could not query GetGovParams (fn 1)\n"); + return; + } + + LOG("═══ qRWA Governance Parameters ═══════════════════════════\n"); + printIdentity(" Admin: ", result.params.mAdminAddress); + printIdentity(" Electricity: ", result.params.electricityAddress); + printIdentity(" Maintenance: ", result.params.maintenanceAddress); + printIdentity(" Reinvestment: ", result.params.reinvestmentAddress); + printIdentity(" QMINE Dev: ", result.params.qmineDevAddress); + LOG(" Electricity %%: %llu\n", (unsigned long long)result.params.electricityPercent); + LOG(" Maintenance %%: %llu\n", (unsigned long long)result.params.maintenancePercent); + LOG(" Reinvestment %%: %llu\n", (unsigned long long)result.params.reinvestmentPercent); +} + +// ─── qrwaGovPoll — show details of a specific governance poll ── + +void qrwaGovPoll(const char* nodeIp, int nodePort, uint64_t proposalId) +{ + QRWAGetGovPoll_input input = {}; + input.proposalId = proposalId; + + QRWAGetGovPoll_output result = {}; + if (!runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_GOV_POLL, &input, sizeof(input), &result, sizeof(result))) + { + LOG("ERROR: Could not query GetGovPoll (fn 2)\n"); + return; + } + + if (result.status == 0) + { + LOG("Governance poll %llu: NOT FOUND\n", (unsigned long long)proposalId); + return; + } + + const char* statusStr = "Unknown"; + switch (result.proposal.status) + { + case 0: statusStr = "Empty"; break; + case 1: statusStr = "Active"; break; + case 2: statusStr = "Passed"; break; + case 3: statusStr = "Failed"; break; + } + + LOG("═══ Governance Poll #%llu ═══════════════════════════════\n", (unsigned long long)proposalId); + LOG(" Status: %s (%llu)\n", statusStr, (unsigned long long)result.proposal.status); + LOG(" Score: %llu\n", (unsigned long long)result.proposal.score); + LOG(" Proposed Parameters:\n"); + printIdentity(" Admin: ", result.proposal.params.mAdminAddress); + printIdentity(" Electricity: ", result.proposal.params.electricityAddress); + printIdentity(" Maintenance: ", result.proposal.params.maintenanceAddress); + printIdentity(" Reinvestment: ", result.proposal.params.reinvestmentAddress); + printIdentity(" QMINE Dev: ", result.proposal.params.qmineDevAddress); + LOG(" Electricity %%: %llu\n", (unsigned long long)result.proposal.params.electricityPercent); + LOG(" Maintenance %%: %llu\n", (unsigned long long)result.proposal.params.maintenancePercent); + LOG(" Reinvestment %%: %llu\n", (unsigned long long)result.proposal.params.reinvestmentPercent); +} + +// ─── qrwaGovPollIds — list active governance poll IDs ────────── + +void qrwaGovPollIds(const char* nodeIp, int nodePort) +{ + QRWAGetActiveGovPollIds_output result = {}; + if (!runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_ACTIVE_GOV_POLL_IDS, nullptr, 0, &result, sizeof(result))) + { + LOG("ERROR: Could not query GetActiveGovPollIds (fn 8)\n"); + return; + } + + LOG("═══ Active Governance Polls ═══════════════════════════════\n"); + LOG(" Count: %llu\n", (unsigned long long)result.count); + uint64_t count = result.count; + if (count > QRWA_MAX_GOV_POLLS) count = QRWA_MAX_GOV_POLLS; + + for (uint64_t i = 0; i < count; i++) + { + LOG(" [%llu] Proposal ID: %llu\n", (unsigned long long)(i + 1), (unsigned long long)result.ids[i]); + } + + if (count == 0) + LOG(" (no active governance polls)\n"); +} + +// ─── qrwaDividends — show dividend balances for all pools ────── + +void qrwaDividends(const char* nodeIp, int nodePort) +{ + QRWAGetDividendBalances_output result = {}; + if (!runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_DIVIDEND_BALANCES, nullptr, 0, &result, sizeof(result))) + { + LOG("ERROR: Could not query GetDividendBalances (fn 5)\n"); + return; + } + + LOG("═══ qRWA Dividend Balances ═══════════════════════════════\n"); + LOG(" Revenue Pools:\n"); + LOG(" Pool A (Qubic Mining): %llu QU\n", (unsigned long long)result.revenuePoolA); + LOG(" Pool B (SC Assets): %llu QU\n", (unsigned long long)result.revenuePoolB); + LOG(" Dedicated Revenue Pool: %llu QU\n", (unsigned long long)result.dedicatedRevenuePool); + LOG("\n Sub-Dividends:\n"); + LOG(" Pool A — QMINE Holders: %llu QU\n", (unsigned long long)result.poolAQmineDividend); + LOG(" Pool A — qRWA Holders: %llu QU\n", (unsigned long long)result.poolAQrwaDividend); + LOG(" Pool B — QMINE Holders: %llu QU\n", (unsigned long long)result.poolBQmineDividend); + LOG(" Pool B — qRWA Holders: %llu QU\n", (unsigned long long)result.poolBQrwaDividend); + LOG(" Pool C — QMINE Holders: %llu QU\n", (unsigned long long)result.poolCQmineDividend); + LOG(" Pool C — qRWA Holders: %llu QU\n", (unsigned long long)result.poolCQrwaDividend); + LOG(" Pool D — Revenue: %llu QU\n", (unsigned long long)result.poolDRevenuePool); + LOG(" Pool D — QMINE Holders: %llu QU\n", (unsigned long long)result.poolDQmineDividend); + LOG(" Pool D — qRWA Holders: %llu QU\n", (unsigned long long)result.poolDQrwaDividend); +} + +// ─── qrwaScDividends — show SC dividend tracking ────────────── + +void qrwaScDividends(const char* nodeIp, int nodePort) +{ + QRWAGetScDividendTracking_output* result = new QRWAGetScDividendTracking_output(); + memset(result, 0, sizeof(QRWAGetScDividendTracking_output)); + if (!runContractFunction(nodeIp, nodePort, QRWA_CONTRACT_INDEX, + QRWA_GET_SC_DIVIDEND_TRACKING, nullptr, 0, result, sizeof(QRWAGetScDividendTracking_output))) + { + LOG("ERROR: Could not query GetScDividendTracking (fn 15)\n"); + delete result; + return; + } + + LOG("═══ SC Dividend Tracking (Pool B Revenue Sources) ═══════\n"); + LOG(" Tracked SC assets: %llu\n\n", (unsigned long long)result->count); + + uint64_t count = result->count; + if (count > QRWA_MAX_ASSETS) count = QRWA_MAX_ASSETS; + + if (count == 0) + { + LOG(" (no SC dividends tracked)\n"); + delete result; + return; + } + + LOG(" %-60s %8s %20s\n", "SC Contract", "Name", "Cumulative QU"); + LOG(" %-60s %8s %20s\n", + "------------------------------------------------------------", + "--------", "--------------------"); + + for (uint64_t i = 0; i < count; i++) + { + char scIdentity[128] = {}; + getIdentityFromPublicKey(result->scContractIds[i], scIdentity, false); + uint64_t idx = 0; + memcpy(&idx, result->scContractIds[i], sizeof(uint64_t)); + const char* name = getContractName((uint32_t)idx); + if (!name) name = "?"; + LOG(" %-60s %8s %20llu\n", + scIdentity, name, + (unsigned long long)result->cumulativeDividends[i]); + } + delete result; +} + +// ─── Procedure wrappers ──────────────────────────────────────── + +void qrwaDonateToTreasury(const char* nodeIp, int nodePort, const char* seed, + uint64_t amount, uint32_t scheduledTickOffset) +{ + QRWADonateToTreasury_input input = {}; + input.amount = amount; + + LOG("Donating %llu QMINE to qRWA treasury...\n", (unsigned long long)amount); + makeContractTransaction(nodeIp, nodePort, seed, QRWA_CONTRACT_INDEX, + QRWA_PROC_DONATE_TO_TREASURY, 0, sizeof(input), &input, scheduledTickOffset); +} + +void qrwaVoteGovParams(const char* nodeIp, int nodePort, const char* seed, + const QRWAGovParams& proposal, uint32_t scheduledTickOffset) +{ + QRWAVoteGovParams_input input = {}; + input.proposal = proposal; + + LOG("Submitting governance vote...\n"); + makeContractTransaction(nodeIp, nodePort, seed, QRWA_CONTRACT_INDEX, + QRWA_PROC_VOTE_GOV_PARAMS, 0, sizeof(input), &input, scheduledTickOffset); +} + +void qrwaSetPoolARevenueAddress(const char* nodeIp, int nodePort, const char* seed, + const char* newAddress, uint32_t scheduledTickOffset) +{ + QRWASetPoolARevenueAddress_input input = {}; + getPublicKeyFromIdentity(newAddress, input.newAddress); + + char verifiedId[128] = {}; + getIdentityFromPublicKey(input.newAddress, verifiedId, false); + LOG("Setting Pool A revenue address to: %s\n", verifiedId); + + makeContractTransaction(nodeIp, nodePort, seed, QRWA_CONTRACT_INDEX, + QRWA_PROC_SET_POOL_A_ADDRESS, 0, sizeof(input), &input, scheduledTickOffset); +} + +void qrwaSetPoolDRevenueAddress(const char* nodeIp, int nodePort, const char* seed, + const char* newAddress, uint32_t scheduledTickOffset) +{ + QRWASetPoolDRevenueAddress_input input = {}; + getPublicKeyFromIdentity(newAddress, input.newAddress); + + char verifiedId[128] = {}; + getIdentityFromPublicKey(input.newAddress, verifiedId, false); + LOG("Setting Pool D revenue address to: %s\n", verifiedId); + + makeContractTransaction(nodeIp, nodePort, seed, QRWA_CONTRACT_INDEX, + QRWA_PROC_SET_POOL_D_ADDRESS, 0, sizeof(input), &input, scheduledTickOffset); +} + +void qrwaDepositGeneralAsset(const char* nodeIp, int nodePort, const char* seed, + const char* issuerIdentity, const char* assetName, + uint64_t amount, uint32_t scheduledTickOffset) +{ + QRWADepositGeneralAsset_input input = {}; + getPublicKeyFromIdentity(issuerIdentity, input.issuer); + input.assetName = assetNameFromString(assetName); + input.amount = amount; + + LOG("Depositing %llu shares of %s into qRWA...\n", (unsigned long long)amount, assetName); + makeContractTransaction(nodeIp, nodePort, seed, QRWA_CONTRACT_INDEX, + 7, 0, sizeof(input), &input, scheduledTickOffset); // proc 7 +} + +void qrwaRevokeAssetMgmt(const char* nodeIp, int nodePort, const char* seed, + const char* issuerIdentity, const char* assetName, + int64_t numberOfShares, uint32_t scheduledTickOffset) +{ + QRWARevokeAssetManagementRights_input input = {}; + getPublicKeyFromIdentity(issuerIdentity, input.issuer); + input.assetName = assetNameFromString(assetName); + input.numberOfShares = numberOfShares; + + LOG("Revoking management of %lld shares of %s from qRWA (100 QU fee)...\n", + (long long)numberOfShares, assetName); + makeContractTransaction(nodeIp, nodePort, seed, QRWA_CONTRACT_INDEX, + 8, 100, sizeof(input), &input, scheduledTickOffset); // proc 8, 100 QU fee +} diff --git a/qrwa.h b/qrwa.h new file mode 100644 index 0000000..6f7ffc6 --- /dev/null +++ b/qrwa.h @@ -0,0 +1,347 @@ +#pragma once + +#include "structs.h" + +// ─── Contract constants ──────────────────────────────────────── +#define QRWA_CONTRACT_INDEX 20 + +// Query function numbers (inputType in RequestContractFunction) +#define QRWA_GET_GOV_PARAMS 1 +#define QRWA_GET_GOV_POLL 2 +#define QRWA_GET_TREASURY_BALANCE 4 +#define QRWA_GET_DIVIDEND_BALANCES 5 +#define QRWA_GET_TOTAL_DISTRIBUTED 6 +#define QRWA_GET_CONTRACT_ADDRESSES 12 +#define QRWA_GET_ACTIVE_GOV_POLL_IDS 8 +#define QRWA_GET_GENERAL_ASSET_BAL 9 +#define QRWA_GET_GENERAL_ASSETS 10 +#define QRWA_GET_PAYOUTS_QMINE 11 +#define QRWA_GET_PAYOUTS_QRWA 13 +#define QRWA_GET_PAYOUTS_DEDICATED 14 +#define QRWA_GET_SC_DIVIDEND_TRACKING 15 +#define QRWA_GET_PAYOUTS_POOL_D 16 + +// Procedure IDs (inputType in InvokeContractProcedure) +#define QRWA_PROC_DONATE_TO_TREASURY 3 +#define QRWA_PROC_VOTE_GOV_PARAMS 4 +#define QRWA_PROC_DEPOSIT_GENERAL_ASSET 7 +#define QRWA_PROC_REVOKE_ASSET_MGMT 8 +#define QRWA_PROC_SET_POOL_A_ADDRESS 9 +#define QRWA_PROC_SET_POOL_D_ADDRESS 10 + +constexpr uint64_t QRWA_PAYOUT_RING_SIZE = 16384; +constexpr uint64_t QRWA_PAYOUT_PAGE_SIZE = 512; +constexpr uint64_t QRWA_MAX_ASSETS = 1024; +constexpr uint64_t QRWA_MAX_GOV_POLLS = 64; + +// Payout types +constexpr uint8_t QRWA_PAYOUT_TYPE_QMINE_HOLDER = 0; +constexpr uint8_t QRWA_PAYOUT_TYPE_QMINE_DEV = 1; +constexpr uint8_t QRWA_PAYOUT_TYPE_QRWA_HOLDER = 2; +constexpr uint8_t QRWA_PAYOUT_TYPE_DEDICATED_QRWA = 3; +constexpr uint8_t QRWA_PAYOUT_TYPE_POOL_D_QRWA = 4; + +// ─── Input/Output structs ────────────────────────────────────── +// +// All structs below are byte-for-byte mirrors of the equivalent QPI types in +// submodules/core/src/contracts/qRWA.h (e.g. QRWAPayoutEntry, QRWAGovParams, +// QRWAGovProposal, QRWAAsset). The submodule pin is up-to-date so the canonical +// definitions are available, and we'd ideally just `using QRWAGovParams = +// ::QRWA::QRWAGovParams;` etc. +// +// Why we still mirror them as flat structs here: +// * contracts/qRWA.h begins with `using namespace QPI;` at file scope and +// pulls in qpi.h, which transitively requires CONTRACT_INDEX / +// CONTRACT_STATE_TYPE / CONTRACT_STATE2_TYPE macros to be defined per +// translation unit and depends on contract_core/pre_qpi_def.h. +// * That setup conflicts with this CLI's `defines.h` (e.g. defines.h has +// `#define EXCHANGE_PUBLIC_PEERS 0` while +// core/src/network_messages/network_message_type.h declares an enum value +// `EXCHANGE_PUBLIC_PEERS = 0` — the macro expansion breaks the enum). +// * Pulling qpi.h via qrwa.h would also leak `using namespace QPI;` into +// every other CLI translation unit that includes us (main.cpp), polluting +// names like `id`, `uint64`, `Asset` globally. +// +// Once the CLI grows a generic adapter that quarantines QPI types behind a +// thin shim (similar to qpi_adapter.h but without macro collisions), these +// flat duplicates can be replaced with `using` aliases. Until then the wire +// layout is the contract here. + +// Mirrors ::QRWA::QRWAPayoutEntry. The trailing 1-byte `_pad0` exists in the +// contract for two reasons that the wire format inherits: +// 1. After `payoutType` (1 byte) at offset 55, `_pad0` rounds the entry +// total to 56 bytes — a multiple of 8 — so QPI's +// Array packs without internal +// gaps and reads/writes are 8-byte aligned for `amount`/`holding` etc. +// 2. The contract serializer copies entries with sizeof(QRWAPayoutEntry); +// omitting the pad here would produce a 1-byte short-read on the next +// entry and corrupt the whole payouts page. +struct QRWAPayoutEntry +{ + uint8_t recipient[32]; + uint64_t amount; + uint64_t qmineHolding; + uint64_t qrwaHolding; + uint32_t tick; + uint16_t epoch; + uint8_t payoutType; + uint8_t _pad0; +}; + +// fn 1 — mirrors ::QRWA::QRWAGovParams +struct QRWAGovParams +{ + uint8_t mAdminAddress[32]; + uint8_t electricityAddress[32]; + uint8_t maintenanceAddress[32]; + uint8_t reinvestmentAddress[32]; + uint8_t qmineDevAddress[32]; + uint64_t electricityPercent; + uint64_t maintenancePercent; + uint64_t reinvestmentPercent; +}; +struct QRWAGetGovParams_output +{ + QRWAGovParams params; +}; + +// fn 2 +struct QRWAGovProposal +{ + uint64_t proposalId; + uint64_t status; // 0=Empty, 1=Active, 2=Passed, 3=Failed + uint64_t score; + QRWAGovParams params; +}; +struct QRWAGetGovPoll_input +{ + uint64_t proposalId; +}; +struct QRWAGetGovPoll_output +{ + QRWAGovProposal proposal; + uint64_t status; // 0=NotFound, 1=Found +}; + +// fn 4 +struct QRWAGetTreasuryBalance_output +{ + uint64_t balance; +}; + +// fn 5 +struct QRWAGetDividendBalances_output +{ + uint64_t revenuePoolA; + uint64_t revenuePoolB; + uint64_t dedicatedRevenuePool; + uint64_t poolAQmineDividend; + uint64_t poolAQrwaDividend; + uint64_t poolBQmineDividend; + uint64_t poolBQrwaDividend; + uint64_t poolCQmineDividend; + uint64_t poolCQrwaDividend; + uint64_t poolDRevenuePool; + uint64_t poolDQmineDividend; + uint64_t poolDQrwaDividend; +}; + +// fn 6 +struct QRWAGetTotalDistributed_output +{ + uint64_t totalPoolADistributed; + uint64_t totalPoolBDistributed; + uint64_t totalPoolCDistributed; + uint64_t totalPoolDDistributed; + uint64_t payoutTotalQmineBegin; +}; + +// fn 7 +struct QRWAGetContractAddresses_output +{ + uint8_t dedicatedRevenueAddress[32]; + uint8_t poolARevenueAddress[32]; + uint8_t poolDRevenueAddress[32]; + uint8_t fundraisingAddress[32]; + uint8_t exchangeAddress[32]; +}; + +// fn 8 +struct QRWAGetActiveGovPollIds_output +{ + uint64_t count; + uint64_t ids[QRWA_MAX_GOV_POLLS]; // Array +}; + +// fn 9 +struct QRWAGetGeneralAssetBalance_input +{ + uint8_t issuer[32]; + uint64_t assetName; +}; +struct QRWAGetGeneralAssetBalance_output +{ + uint64_t balance; + uint64_t status; // 1=found, 0=not found +}; + +// fn 10 +struct QRWAAssetEntry +{ + uint8_t issuer[32]; + uint64_t assetName; +}; +struct QRWAGetGeneralAssets_output +{ + uint64_t count; + QRWAAssetEntry assets[QRWA_MAX_ASSETS]; + uint64_t balances[QRWA_MAX_ASSETS]; +}; + +// Pagination input (shared layout for fn 11, 13, 14) +struct QRWAGetPayouts_input +{ + uint16_t page; // 0 = newest entries +}; + +// fn 11 +struct QRWAGetPayoutsQmine_output +{ + QRWAPayoutEntry payouts[QRWA_PAYOUT_PAGE_SIZE]; + uint16_t nextIdx; + uint16_t returnedCount; + uint16_t page; + uint16_t totalPages; +}; + +// fn 13 +struct QRWAGetPayoutsQrwa_output +{ + QRWAPayoutEntry payouts[QRWA_PAYOUT_PAGE_SIZE]; + uint16_t nextIdx; + uint16_t returnedCount; + uint16_t page; + uint16_t totalPages; +}; + +// fn 14 +struct QRWAGetPayoutsDedicated_output +{ + QRWAPayoutEntry payouts[QRWA_PAYOUT_PAGE_SIZE]; + uint16_t nextIdx; + uint16_t returnedCount; + uint16_t page; + uint16_t totalPages; +}; + +// fn 16 +struct QRWAGetPayoutsPoolD_output +{ + QRWAPayoutEntry payouts[QRWA_PAYOUT_PAGE_SIZE]; + uint16_t nextIdx; + uint16_t returnedCount; + uint16_t page; + uint16_t totalPages; +}; + +// fn 15 +struct QRWAGetScDividendTracking_output +{ + uint64_t count; + uint8_t scContractIds[QRWA_MAX_ASSETS][32]; // Array + uint64_t cumulativeDividends[QRWA_MAX_ASSETS]; // Array +}; + +// ─── Pool selector enum ──────────────────────────────────────── +enum QRWAPoolType +{ + QRWA_POOL_A, // Pool A — Qubic Mining (fn 11) + QRWA_POOL_B, // Pool B — SC Assets Revenue (fn 13) + QRWA_POOL_C, // Pool C — BTC Mining (fn 14) + QRWA_POOL_D, // Pool D — MLM Water (fn 16) +}; + +// ─── Procedure input/output structs ──────────────────────────── + +// proc 3 +struct QRWADonateToTreasury_input +{ + uint64_t amount; +}; +struct QRWADonateToTreasury_output +{ + uint64_t status; +}; + +// proc 4 +struct QRWAVoteGovParams_input +{ + QRWAGovParams proposal; +}; +struct QRWAVoteGovParams_output +{ + uint64_t status; +}; + +// proc 6 +struct QRWASetPoolARevenueAddress_input +{ + uint8_t newAddress[32]; +}; +struct QRWASetPoolARevenueAddress_output +{ + uint64_t status; +}; + +// proc 10 +struct QRWASetPoolDRevenueAddress_input +{ + uint8_t newAddress[32]; +}; +struct QRWASetPoolDRevenueAddress_output +{ + uint64_t status; +}; + +// proc 7 +struct QRWADepositGeneralAsset_input +{ + uint8_t issuer[32]; + uint64_t assetName; + uint64_t amount; +}; +struct QRWADepositGeneralAsset_output +{ + uint64_t status; +}; + +// proc 8 +struct QRWARevokeAssetManagementRights_input +{ + uint8_t issuer[32]; + uint64_t assetName; + int64_t numberOfShares; +}; +struct QRWARevokeAssetManagementRights_output +{ + int64_t transferredNumberOfShares; + uint64_t status; +}; + +// ─── CLI function declarations ───────────────────────────────── +// epochFilter: -1 = show all, >=0 = filter by epoch +void qrwaPayout(const char* nodeIp, int nodePort, QRWAPoolType pool, int epochFilter = -1); +void qrwaPayoutAddress(const char* nodeIp, int nodePort, const char* identity, int epochFilter = -1); +void qrwaStatus(const char* nodeIp, int nodePort); +void qrwaAssets(const char* nodeIp, int nodePort); +void qrwaGovParams(const char* nodeIp, int nodePort); +void qrwaGovPoll(const char* nodeIp, int nodePort, uint64_t proposalId); +void qrwaGovPollIds(const char* nodeIp, int nodePort); +void qrwaDividends(const char* nodeIp, int nodePort); +void qrwaScDividends(const char* nodeIp, int nodePort); +void qrwaDonateToTreasury(const char* nodeIp, int nodePort, const char* seed, uint64_t amount, uint32_t scheduledTickOffset); +void qrwaVoteGovParams(const char* nodeIp, int nodePort, const char* seed, const QRWAGovParams& proposal, uint32_t scheduledTickOffset); +void qrwaSetPoolARevenueAddress(const char* nodeIp, int nodePort, const char* seed, const char* newAddress, uint32_t scheduledTickOffset); +void qrwaSetPoolDRevenueAddress(const char* nodeIp, int nodePort, const char* seed, const char* newAddress, uint32_t scheduledTickOffset); +void qrwaDepositGeneralAsset(const char* nodeIp, int nodePort, const char* seed, const char* issuerIdentity, const char* assetName, uint64_t amount, uint32_t scheduledTickOffset); +void qrwaRevokeAssetMgmt(const char* nodeIp, int nodePort, const char* seed, const char* issuerIdentity, const char* assetName, int64_t numberOfShares, uint32_t scheduledTickOffset); diff --git a/structs.h b/structs.h index 7cc047f..d6d70eb 100644 --- a/structs.h +++ b/structs.h @@ -213,6 +213,24 @@ enum COMMAND GET_ORACLE_SUBSCRIPTION, SEND_ORACLE_QUERY_TX, SEND_ORACLE_CONTRACT_TX, + QRWA_PAYOUT_POOL_A, + QRWA_PAYOUT_POOL_B, + QRWA_PAYOUT_POOL_C, + QRWA_PAYOUT_POOL_D, + QRWA_PAYOUT_ADDRESS, + QRWA_STATUS, + QRWA_ASSETS, + QRWA_GOV_PARAMS, + QRWA_GOV_POLL, + QRWA_GOV_POLL_IDS, + QRWA_DIVIDENDS, + QRWA_SC_DIVIDENDS, + QRWA_DONATE_TREASURY, + QRWA_VOTE_GOV_PARAMS, + QRWA_SET_POOL_A_ADDR, + QRWA_SET_POOL_D_ADDR, + QRWA_DEPOSIT_ASSET, + QRWA_REVOKE_ASSET_MGMT, ESCROW_CREATE_DEAL_CMD , ESCROW_GET_DEALS_CMD, ESCROW_ACCEPT_DEAL_CMD, diff --git a/submodules/core b/submodules/core index 127306f..57bf1ab 160000 --- a/submodules/core +++ b/submodules/core @@ -1 +1 @@ -Subproject commit 127306fe7abe1a9c334bfc6038198d0d86e18b68 +Subproject commit 57bf1abed06aba42f309ec8d8163e6f595c03782