#include <cstring> #include <cstdio> #include <ctime> #include "stdint.h" #include "quottery.h" #include "prompt.h" #include "structs.h" #include "keyUtils.h" #include "nodeUtils.h" #include "K12AndKeyUtil.h" #include "connection.h" #include "logger.h" #include "walletUtils.h" constexpr int QUOTTERY_CONTRACT_ID = 2; enum quotteryViewId { fee = 1, betInfo = 2, betDetail = 3, activeBet = 4, activeBetByCreator = 5 }; enum quotteryFuncId { issue = 1, join = 2, cancelBet = 3, publishResult = 4 }; void quotteryGetBasicInfo(QCPtr& qc, qtryBasicInfo_output& result) { struct { RequestResponseHeader header; RequestContractFunction rcf; } packet; packet.header.setSize(sizeof(packet)); packet.header.randomizeDejavu(); packet.header.setType(RequestContractFunction::type()); packet.rcf.inputSize = 0; packet.rcf.inputType = quotteryViewId::fee; packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; qc->sendData((uint8_t *) &packet, packet.header.size()); try { result = qc->receivePacketWithHeaderAs<qtryBasicInfo_output>(); } catch (std::logic_error& e) { memset(&result, 0, sizeof(qtryBasicInfo_output)); } } void quotteryPrintBasicInfo(const char* nodeIp, const int nodePort) { qtryBasicInfo_output result; memset(&result, 1, sizeof(qtryBasicInfo_output)); auto qc = make_qc(nodeIp, nodePort); quotteryGetBasicInfo(qc, result); LOG("Fee per slot per hour: %llu qu\n", result.feePerSlotPerHour); LOG("Minimum amount of qus per bet slot: %llu qu\n", result.minBetSlotAmount); LOG("Game operator Fee: %.2f%%\n", result.gameOperatorFee/100.0); LOG("Shareholders fee: %.2f%%\n", result.shareholderFee/100.0); LOG("Burn fee: %.2f%%\n", result.burnFee/100.0); LOG("================\n"); LOG("Number of issued bet: %lld\n", result.nIssuedBet); LOG("moneyFlow: %lld\n", result.moneyFlow); LOG("moneyFlow through issueBet: %lld\n", result.moneyFlowThroughIssueBet); LOG("moneyFlow through joinBet: %lld\n", result.moneyFlowThroughJoinBet); LOG("moneyFlow through finalizeBet: %lld\n", result.moneyFlowThroughFinalizeBet); LOG("================\n"); LOG("earned amount for shareholders: %lld\n", result.earnedAmountForShareHolder); LOG("earned amount for winners: %lld\n", result.earnedAmountForBetWinner); LOG("distributed amount: %lld\n", result.distributedAmount); LOG("burned amount: %lld\n", result.burnedAmount); char buf[64] = {0}; getIdentityFromPublicKey(result.gameOperator, buf, false); LOG("Game operator ID: %s\n", buf); } /** * @return pack qtry datetime data from year, month, day, hour, minute, second to a uint32_t * year is counted from 24 (2024) */ static void packQuotteryDate(uint32_t _year, uint32_t _month, uint32_t _day, uint32_t _hour, uint32_t _minute, uint32_t _second, uint32_t& res) { res = ((_year - 24) << 26) | (_month << 22) | (_day << 17) | (_hour << 12) | (_minute << 6) | (_second); } #define QTRY_GET_YEAR(data) ((data >> 26)+24) #define QTRY_GET_MONTH(data) ((data >> 22) & 0b1111) #define QTRY_GET_DAY(data) ((data >> 17) & 0b11111) #define QTRY_GET_HOUR(data) ((data >> 12) & 0b11111) #define QTRY_GET_MINUTE(data) ((data >> 6) & 0b111111) #define QTRY_GET_SECOND(data) ((data) & 0b111111) /** * @return unpack qtry datetime from uin32 to year, month, day, hour, minute, secon */ void unpackQuotteryDate(uint8_t& _year, uint8_t& _month, uint8_t& _day, uint8_t& _hour, uint8_t& _minute, uint8_t& _second, uint32_t data) { _year = QTRY_GET_YEAR(data); // 6 bits _month = QTRY_GET_MONTH(data); //4bits _day = QTRY_GET_DAY(data); //5bits _hour = QTRY_GET_HOUR(data); //5bits _minute = QTRY_GET_MINUTE(data); //6bits _second = QTRY_GET_SECOND(data); //6bits } static void accumulatedDay(int month, uint64_t& res) { switch (month) { case 1: res = 0; break; case 2: res = 31; break; case 3: res = 59; break; case 4: res = 90; break; case 5: res = 120; break; case 6: res = 151; break; case 7: res = 181; break; case 8: res = 212; break; case 9: res = 243; break; case 10:res = 273; break; case 11:res = 304; break; case 12:res = 334; break; } } static int dateCompare(uint32_t& A, uint32_t& B, int& i) { if (A == B) return 0; if (A < B) return -1; return 1; } // return diff in number of second, A must be smaller than or equal B to have valid value static void diffDate(uint32_t& A, uint32_t& B, int& i, uint64_t& dayA, uint64_t& dayB, uint64_t& res) { if (dateCompare(A, B, i) >= 0) { res = 0; return; } // TODO: convert local variables to locals struct when finalizing accumulatedDay(QTRY_GET_MONTH(A), dayA); dayA += QTRY_GET_DAY(A); accumulatedDay(QTRY_GET_MONTH(B), dayB); dayB += (QTRY_GET_YEAR(B) - QTRY_GET_YEAR(A)) * 365ULL + QTRY_GET_DAY(B); // handling leap-year: only store last 2 digits of year here, don't care about mod 100 & mod 400 case for (i = QTRY_GET_YEAR(A); i < QTRY_GET_YEAR(B); i++) { if (i%4 == 0) { dayB++; } } if (int(QTRY_GET_YEAR(A))% 4 == 0 && (QTRY_GET_MONTH(A) > 2)) dayA++; if (int(QTRY_GET_YEAR(B))% 4 == 0 && (QTRY_GET_MONTH(B) > 2)) dayB++; res = (dayB - dayA)*3600ULL*24; res += (QTRY_GET_HOUR(B) * 3600 + QTRY_GET_MINUTE(B) * 60 + QTRY_GET_SECOND(B)); res -= (QTRY_GET_HOUR(A) * 3600 + QTRY_GET_MINUTE(A) * 60 + QTRY_GET_SECOND(A)); } void quotteryIssueBet(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset) { uint8_t privateKey[32] = {0}; uint8_t sourcePublicKey[32] = {0}; uint8_t destPublicKey[32] = {0}; uint8_t subseed[32] = {0}; uint8_t digest[32] = {0}; uint8_t signature[64] = {0}; char publicIdentity[128] = {0}; char txHash[128] = {0}; getSubseedFromSeed((uint8_t*)seed, subseed); getPrivateKeyFromSubSeed(subseed, privateKey); getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); const bool isLowerCase = false; getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; ((uint64_t*)destPublicKey)[1] = 0; ((uint64_t*)destPublicKey)[2] = 0; ((uint64_t*)destPublicKey)[3] = 0; struct { RequestResponseHeader header; Transaction transaction; QuotteryissueBet_input ibi; unsigned char signature[64]; } packet; memset(&packet.ibi, 0, sizeof(QuotteryissueBet_input)); char buff[128] = {0}; promptStdin("Enter bet description (32 chars)", buff, 32); memcpy(packet.ibi.betDesc, buff, 32); promptStdin("Enter number of options (valid [2-8])", buff, 1); packet.ibi.numberOfOption = buff[0] - 48; for (int i = 0; i < packet.ibi.numberOfOption; i++) { char buff2[128] = {0}; sprintf(buff2, "Enter option #%d description (32 chars)", i); promptStdin(buff2, buff, 32); memcpy(packet.ibi.optionDesc + i*32, buff, 32); } promptStdin("Enter number of oracle provider (valid [1-8])", buff, 1); int numberOP = buff[0] - 48; for (int i = 0; i < numberOP; i++) { char buff2[128] = {0}; uint8_t buf3[32] = {0}; sprintf(buff2, "Enter oracle provider #%d ID (60 chars)", i); promptStdin(buff2, buff, 60); getPublicKeyFromIdentity(buff, buf3); memcpy(packet.ibi.oracleProviderId + i * 32, buf3, 32); } for (int i = 0; i < numberOP; i++) { char buff2[128] = {0}; sprintf(buff2, "Enter fee for oracle provider #%d ID [4 digits number, format ABCD (meaning AB.CD%%)]", i); promptStdin(buff2, buff, 4); uint32_t op_fee = std::atoi(buff); packet.ibi.oracleFees[i] = op_fee; } { promptStdin("Enter bet close date (stop receiving bet date) (Format: YY-MM-DD hh:mm:ss)", buff, 17); uint8_t year = (buff[0]-48)*10 + (buff[1]-48); uint8_t month = (buff[3]-48)*10 + (buff[4]-48); uint8_t day = (buff[6]-48)*10 + (buff[7]-48); uint8_t hour = (buff[9]-48)*10 + (buff[10]-48); uint8_t minute = (buff[12]-48)*10 + (buff[13]-48); uint8_t sec = (buff[15]-48)*10 + (buff[16]-48); packQuotteryDate(year, month, day, hour, minute, sec, packet.ibi.closeDate); } { promptStdin("Enter bet end date (finalize bet date) (Format: YY-MM-DD hh:mm:ss)", buff, 17); uint8_t year = (buff[0]-48)*10 + (buff[1]-48); uint8_t month = (buff[3]-48)*10 + (buff[4]-48); uint8_t day = (buff[6]-48)*10 + (buff[7]-48); uint8_t hour = (buff[9]-48)*10 + (buff[10]-48); uint8_t minute = (buff[12]-48)*10 + (buff[13]-48); uint8_t sec = (buff[15]-48)*10 + (buff[16]-48); packQuotteryDate(year, month, day, hour, minute, sec, packet.ibi.endDate); } { promptStdin("Enter amount of qus per bet slot", buff, 16); packet.ibi.amountPerSlot = std::atoi(buff); } { promptStdin("Enter max number of bet slot per option", buff, 16); packet.ibi.maxBetSlotPerOption = std::atoi(buff); } LOG("Crafting transaction...\n"); memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); auto qc = make_qc(nodeIp, nodePort); LOG("Established connection...\n"); { qtryBasicInfo_output quotteryBasicInfo; LOG("Getting QTRY info...\n"); quotteryGetBasicInfo(qc, quotteryBasicInfo); LOG("feePerSlotPerHour: %lld\n", quotteryBasicInfo.feePerSlotPerHour); std::time_t now = time(0); std::tm *gmtm = gmtime(&now); uint8_t year = gmtm->tm_year % 100; uint8_t month = gmtm->tm_mon + 1; // NOTE: tm_mon is zero-based [0-11] => [Jan-Dec]. Ref: https://cplusplus.com/reference/ctime/tm/ uint8_t day = gmtm->tm_mday; uint8_t hour = gmtm->tm_hour; uint8_t minute = gmtm->tm_min; uint8_t second = gmtm->tm_sec; uint32_t curDate; packQuotteryDate(year, month, day, hour, minute, second, curDate); uint64_t diffhour = 0, tmp0, tmp1; int tmp; diffDate(curDate, packet.ibi.endDate, tmp, tmp0, tmp1, diffhour); diffhour = (diffhour+3599)/3600; packet.transaction.amount = packet.ibi.maxBetSlotPerOption * packet.ibi.numberOfOption * quotteryBasicInfo.feePerSlotPerHour * diffhour; } uint32_t currentTick = getTickNumberFromNode(qc); LOG("Getting tick info, latest tick is: %u\n", currentTick); packet.transaction.tick = currentTick + scheduledTickOffset; packet.transaction.inputType = quotteryFuncId::issue; packet.transaction.inputSize = sizeof(QuotteryissueBet_input); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(QuotteryissueBet_input), digest, 32); LOG("Signing tx packet...\n"); sign(subseed, sourcePublicKey, digest, signature); memcpy(packet.signature, signature, 64); packet.header.setSize(sizeof(packet)); packet.header.zeroDejavu(); packet.header.setType(BROADCAST_TRANSACTION); LOG("Sending data...\n"); qc->sendData((uint8_t *) &packet, packet.header.size()); LOG("Sent data...\n"); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(QuotteryissueBet_input) + SIGNATURE_SIZE, digest, 32); // recompute digest for txhash getTxHashFromDigest(digest, txHash); LOG("Bet creation has been sent!\n"); printReceipt(packet.transaction, txHash, nullptr); LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); LOG("to check your tx confirmation status\n"); } void quotteryJoinBet(const char* nodeIp, int nodePort, const char* seed, uint32_t betId, int numberOfBetSlot, uint64_t amountPerSlot, uint8_t option, uint32_t scheduledTickOffset) { auto qc = make_qc(nodeIp, nodePort); uint8_t privateKey[32] = {0}; uint8_t sourcePublicKey[32] = {0}; uint8_t destPublicKey[32] = {0}; uint8_t subseed[32] = {0}; uint8_t digest[32] = {0}; uint8_t signature[64] = {0}; char publicIdentity[128] = {0}; char txHash[128] = {0}; getSubseedFromSeed((uint8_t*)seed, subseed); getPrivateKeyFromSubSeed(subseed, privateKey); getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); const bool isLowerCase = false; getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; ((uint64_t*)destPublicKey)[1] = 0; ((uint64_t*)destPublicKey)[2] = 0; ((uint64_t*)destPublicKey)[3] = 0; struct { RequestResponseHeader header; Transaction transaction; QuotteryjoinBet_input jbi; unsigned char signature[64]; } packet; memset(&packet.jbi, 0, sizeof(QuotteryjoinBet_input)); packet.jbi.betId = betId; packet.jbi.numberOfSlot = numberOfBetSlot; packet.jbi.option = option; memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); packet.transaction.amount = amountPerSlot*numberOfBetSlot; uint32_t currentTick = getTickNumberFromNode(qc); packet.transaction.tick = currentTick + scheduledTickOffset; packet.transaction.inputType = quotteryFuncId::join; packet.transaction.inputSize = sizeof(QuotteryjoinBet_input); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(QuotteryjoinBet_input), digest, 32); sign(subseed, sourcePublicKey, digest, signature); memcpy(packet.signature, signature, 64); packet.header.setSize(sizeof(packet)); packet.header.zeroDejavu(); packet.header.setType(BROADCAST_TRANSACTION); qc->sendData((uint8_t *) &packet, packet.header.size()); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(QuotteryjoinBet_input) + SIGNATURE_SIZE, digest, 32); // recompute digest for txhash getTxHashFromDigest(digest, txHash); LOG("Joining bet tx has been sent!\n"); printReceipt(packet.transaction, txHash, nullptr); LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); LOG("to check your tx confirmation status\n"); } void quotteryGetBetInfo(const char* nodeIp, const int nodePort, int betId, getBetInfo_output& result) { auto qc = make_qc(nodeIp, nodePort); struct { RequestResponseHeader header; RequestContractFunction rcf; getBetInfo_input input; } packet; packet.header.setSize(sizeof(packet)); packet.header.randomizeDejavu(); packet.header.setType(RequestContractFunction::type()); packet.rcf.inputSize = sizeof(getBetInfo_input); packet.rcf.inputType = quotteryViewId::betInfo; packet.rcf.contractIndex = 2; packet.input.betId = betId; qc->sendData((uint8_t *) &packet, packet.header.size()); try { result = qc->receivePacketWithHeaderAs<getBetInfo_output>(); } catch (std::logic_error& e) { memset(&result, 0, sizeof(getBetInfo_output)); } } void quotteryPrintBetInfo(const char* nodeIp, const int nodePort, int betId) { getBetInfo_output result; memset(&result, 0, sizeof(getBetInfo_output)); LOG("Getting betId #%d info...\n", betId); quotteryGetBetInfo(nodeIp, nodePort, betId, result); if (isArrayZero((uint8_t*)&result, sizeof(getBetInfo_output))) { LOG("Failed to get\n"); return; } if (result.betId == -1) { LOG("BetId #%d doesn't exist\n", betId); return; } char buf[128] = {0}; LOG("Bet Id: %u\n", result.betId); // uint32_t betId; LOG("Number of options: %u\n", result.nOption); // uint8_t nOption; // options number getIdentityFromPublicKey(result.creator, buf, false); LOG("Creator: %s\n", buf); { memset(buf, 0 , 128); memcpy(buf, result.betDesc, 32); LOG("Bet descriptions: %s\n", buf); } for (int i = 0; i < result.nOption; i++) { memset(buf, 0 , 128); memcpy(buf, result.optionDesc + i * 32, 32); LOG("Option #%d: %s\n", i, buf); } { LOG("Current state:\n"); for (int i = 0; i < result.nOption; i++) { LOG("Option #%d: %d | ", i, result.currentBetState[i]); } LOG("\n"); } { LOG("Minimum bet amount: %llu\n", result.minBetAmount); LOG("Maximum slot per option: %llu\n", result.maxBetSlotPerOption); uint8_t year, month, day, hour, minute, second; unpackQuotteryDate(year, month, day, hour, minute, second, result.openDate); LOG("OpenDate: %02u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); unpackQuotteryDate(year, month, day, hour, minute, second, result.closeDate); LOG("CloseDate: %02u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); unpackQuotteryDate(year, month, day, hour, minute, second, result.endDate); LOG("EndDate: %02u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); } LOG("Oracle IDs\n"); int nOP = 0; for (int i = 0; i < 8; i++) { if (!isZeroPubkey(result.oracleProviderId+i*32)) { nOP++; memset(buf, 0 , 128); getIdentityFromPublicKey(result.oracleProviderId+i*32, buf, false); uint32_t fee_u32 = result.oracleFees[i]; double fee = (fee_u32)/100.0; LOG("%s\tFee: %.2f%%\n", buf, fee); } } LOG("Current votes result:\n"); for (int i = 0; i < 8; i++) { if (result.betResultWonOption[i] != -1 && result.betResultOPId[i] != -1) { LOG("OP #%d: voted for option #%d\n", result.betResultOPId[i], result.betResultWonOption[i]); } } } // getBetOptionDetail 3 bool quotteryGetBetOptionDetail(const char* nodeIp, const int nodePort, uint32_t betId, uint32_t betOption, getBetOptionDetail_output& result) { auto qc = make_qc(nodeIp, nodePort); struct { RequestResponseHeader header; RequestContractFunction rcf; getBetOptionDetail_input bo_inp; } packet; packet.header.setSize(sizeof(packet)); packet.header.randomizeDejavu(); packet.header.setType(RequestContractFunction::type()); packet.rcf.inputSize = sizeof(getBetOptionDetail_input); packet.rcf.inputType = quotteryViewId::betDetail; packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; packet.bo_inp.betId = betId; packet.bo_inp.betOption = betOption; qc->sendData((uint8_t *) &packet, packet.header.size()); try { result = qc->receivePacketWithHeaderAs<getBetOptionDetail_output>(); return true; } catch (std::logic_error& e) { memset(&result, 0, sizeof(getBetOptionDetail_output)); return false; } } // showing which ID bet for an option void quotteryPrintBetOptionDetail(const char* nodeIp, const int nodePort, uint32_t betId, uint32_t betOption) { getBetOptionDetail_output result; memset(&result, 0, sizeof(getBetOptionDetail_output)); if (!quotteryGetBetOptionDetail(nodeIp, nodePort, betId, betOption, result)) { LOG("Failed to get\n"); return; } LOG("List of IDs bet option #%d on betID %d\n", betOption, betId); char buf[128] = {0}; for (int i = 0; i < 1024; i++) { if (!isZeroPubkey(result.bettor + i*32)) { memset(buf, 0, 128); getIdentityFromPublicKey(result.bettor + i * 32, buf, false); LOG("%s\n", buf); } } } // getActiveBet 4 void quotteryGetActiveBet(const char* nodeIp, const int nodePort, getActiveBet_output& result) { auto qc = make_qc(nodeIp, nodePort); struct { RequestResponseHeader header; RequestContractFunction rcf; } packet; packet.header.setSize(sizeof(packet)); packet.header.randomizeDejavu(); packet.header.setType(RequestContractFunction::type()); packet.rcf.inputSize = 0; packet.rcf.inputType = quotteryViewId::activeBet; packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; qc->sendData((uint8_t *) &packet, packet.header.size()); try { result = qc->receivePacketWithHeaderAs<getActiveBet_output>(); } catch (std::logic_error& e) { memset(&result, 0, sizeof(getActiveBet_output)); } } // showing which ID bet for an option void quotteryPrintActiveBet(const char* nodeIp, const int nodePort) { getActiveBet_output result; memset(&result, 0, sizeof(getActiveBet_output)); quotteryGetActiveBet(nodeIp, nodePort, result); LOG("List of active bet (%d):\n", result.count); for (int i = 0; i < result.count; i++) { LOG("%u, ", result.betId[i]); } LOG("\n"); } // getBetByCreator 5 void quotteryGetActiveBetByCreator(const char* nodeIp, const int nodePort, getActiveBetByCreator_output& result, const uint8_t* creator) { auto qc = make_qc(nodeIp, nodePort); struct { RequestResponseHeader header; RequestContractFunction rcf; getActiveBetByCreator_input abi; } packet; packet.header.setSize(sizeof(packet)); packet.header.randomizeDejavu(); packet.header.setType(RequestContractFunction::type()); packet.rcf.inputSize = sizeof(getActiveBetByCreator_input); packet.rcf.inputType = quotteryViewId::activeBetByCreator; packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; memcpy(packet.abi.creator, creator, 32); qc->sendData((uint8_t *) &packet, packet.header.size()); try { result = qc->receivePacketWithHeaderAs<getActiveBetByCreator_output>(); } catch (std::logic_error& e) { memset(&result, 0, sizeof(getActiveBetByCreator_output)); } } void quotteryPrintActiveBetByCreator(const char* nodeIp, const int nodePort, const char* identity) { uint8_t creatorPubkey[32] = {0}; getPublicKeyFromIdentity(identity, creatorPubkey); getActiveBetByCreator_output result; memset(&result, 0, sizeof(getActiveBet_output)); quotteryGetActiveBetByCreator(nodeIp, nodePort, result, creatorPubkey); LOG("List of active bet (%d):\n", result.count); for (int i = 0; i < result.count; i++) { LOG("%u, ", result.betId[i]); } LOG("\n"); } void quotteryCancelBet(const char* nodeIp, const int nodePort, const char* seed, const uint32_t betId, const uint32_t scheduledTickOffset) { auto qc = make_qc(nodeIp, nodePort); uint8_t privateKey[32] = {0}; uint8_t sourcePublicKey[32] = {0}; uint8_t destPublicKey[32] = {0}; uint8_t subseed[32] = {0}; uint8_t digest[32] = {0}; uint8_t signature[64] = {0}; char publicIdentity[128] = {0}; char txHash[128] = {0}; getSubseedFromSeed((uint8_t*)seed, subseed); getPrivateKeyFromSubSeed(subseed, privateKey); getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); const bool isLowerCase = false; getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; ((uint64_t*)destPublicKey)[1] = 0; ((uint64_t*)destPublicKey)[2] = 0; ((uint64_t*)destPublicKey)[3] = 0; struct { RequestResponseHeader header; Transaction transaction; cancelBet_input cbi; unsigned char signature[64]; } packet; packet.cbi.betId = betId; memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); packet.transaction.amount = 0; uint32_t currentTick = getTickNumberFromNode(qc); packet.transaction.tick = currentTick + scheduledTickOffset; packet.transaction.inputType = quotteryFuncId::cancelBet; packet.transaction.inputSize = sizeof(cancelBet_input); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(cancelBet_input), digest, 32); sign(subseed, sourcePublicKey, digest, signature); memcpy(packet.signature, signature, 64); packet.header.setSize(sizeof(packet)); packet.header.zeroDejavu(); packet.header.setType(BROADCAST_TRANSACTION); qc->sendData((uint8_t *) &packet, packet.header.size()); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(cancelBet_input) + SIGNATURE_SIZE, digest, 32); // recompute digest for txhash getTxHashFromDigest(digest, txHash); LOG("Cancel bet tx has been sent!\n"); printReceipt(packet.transaction, txHash, nullptr); LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); LOG("to check your tx confirmation status\n"); } void quotteryPublishResult(const char* nodeIp, const int nodePort, const char* seed, const uint32_t betId, const uint32_t winOption, const uint32_t scheduledTickOffset) { auto qc = make_qc(nodeIp, nodePort); uint8_t privateKey[32] = {0}; uint8_t sourcePublicKey[32] = {0}; uint8_t destPublicKey[32] = {0}; uint8_t subseed[32] = {0}; uint8_t digest[32] = {0}; uint8_t signature[64] = {0}; char publicIdentity[128] = {0}; char txHash[128] = {0}; getSubseedFromSeed((uint8_t*)seed, subseed); getPrivateKeyFromSubSeed(subseed, privateKey); getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); const bool isLowerCase = false; getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; ((uint64_t*)destPublicKey)[1] = 0; ((uint64_t*)destPublicKey)[2] = 0; ((uint64_t*)destPublicKey)[3] = 0; struct { RequestResponseHeader header; Transaction transaction; publishResult_input pri; unsigned char signature[64]; } packet; packet.pri.betId = betId; packet.pri.winOption = winOption; memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); packet.transaction.amount = 0; uint32_t currentTick = getTickNumberFromNode(qc); packet.transaction.tick = currentTick + scheduledTickOffset; packet.transaction.inputType = quotteryFuncId::publishResult; packet.transaction.inputSize = sizeof(publishResult_input); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(publishResult_input), digest, 32); sign(subseed, sourcePublicKey, digest, signature); memcpy(packet.signature, signature, 64); packet.header.setSize(sizeof(packet)); packet.header.zeroDejavu(); packet.header.setType(BROADCAST_TRANSACTION); qc->sendData((uint8_t *) &packet, packet.header.size()); KangarooTwelve((unsigned char*)&packet.transaction, sizeof(packet.transaction) + sizeof(publishResult_input) + SIGNATURE_SIZE, digest, 32); // recompute digest for txhash getTxHashFromDigest(digest, txHash); LOG("Publishing result tx has been sent!\n"); printReceipt(packet.transaction, txHash, nullptr); LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); LOG("to check your tx confirmation status\n"); }