├── .gitignore ├── mnemonic.h ├── base.h ├── Makefile ├── .env.sandbox ├── LICENSE ├── README.md ├── base.cpp ├── clients.cpp ├── algorand.h ├── algorand.cpp ├── example.cpp └── mnemonic.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | example 3 | .env.* 4 | -------------------------------------------------------------------------------- /mnemonic.h: -------------------------------------------------------------------------------- 1 | #ifndef MNEMONIC_H 2 | #define MNEMONIC_H 3 | 4 | #include 5 | #include 6 | 7 | typedef std::vector bytes; 8 | 9 | std::string mnemonic_from_seed(bytes seed); 10 | bytes seed_from_mnemonic(std::string); 11 | 12 | bytes sha512_256(bytes input); 13 | #endif 14 | -------------------------------------------------------------------------------- /base.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE_H 2 | #define BASE_H 3 | 4 | #include 5 | #include 6 | #include 7 | typedef std::vector bytes; 8 | 9 | std::string b64_encode(const bytes& in, bool padded = false); 10 | bytes b64_decode(const std::string& in); 11 | 12 | std::string b32_encode(const bytes& in); 13 | bytes b32_decode(const std::string& in); 14 | 15 | std::vector b2048_encode(const bytes& in); 16 | bytes b2048_decode(const std::vector &in); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCE := algorand.cpp clients.cpp base.cpp mnemonic.cpp 2 | 3 | OS := $(shell uname -s) 4 | 5 | ifeq ($(OS),Darwin) 6 | # On MacOS, brew installed openssl does not end up in system 7 | # locations, so we need to be explicit. 8 | IFLAGS += -I/usr/local/opt/openssl/include 9 | LFLAGS += -L/usr/local/opt/openssl/lib 10 | endif 11 | 12 | LIBS += -lcurl -lsodium -lcrypto 13 | 14 | CC = c++ 15 | CCFLAGS += -std=c++14 16 | 17 | .cpp.o: 18 | $(CC) $(CCFLAGS) $(IFLAGS) -c $< 19 | 20 | example: $(subst .cpp,.o,$(SOURCE)) example.o 21 | $(CC) $(CCFLAGS) $(LFLAGS) $^ $(LIBS) -o $@ 22 | 23 | # Build dependencies, expressed for brew on MacOS 24 | brew-deps: 25 | brew install libsodium msgpack openssl rapidjson 26 | 27 | clean: 28 | rm -f $(subst .cpp,.o,$(SOURCE)) example.o example 29 | 30 | algorand.o: algorand.h 31 | base.o: base.h 32 | mnemonic.o: mnemonic.h 33 | example.o: algorand.h 34 | -------------------------------------------------------------------------------- /.env.sandbox: -------------------------------------------------------------------------------- 1 | ALGOD_ADDRESS='http://localhost:4001' 2 | ALGOD_TOKEN='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 3 | INDEXER_ADDRESS='http://localhost:8980' 4 | INDEXER_TOKEN='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 5 | 6 | DEVA=GXBNLU4AXQABPLHXJDMTG2YXSDT4EWUZACT7KTPFXDQW52XPTIUS5OZ5HQ 7 | DEVAMN="ritual such year foil grow marble opinion sense arrow off busy liberty tennis merry dove quick cycle host segment style october furnace draft absent sample" 8 | 9 | DEVB=SRUOGD3JR7OA6MP2SKRROTQBDGFF7MV3OXISI3AGKAQVOEIH4BZEMMYUAE 10 | DEVBMN="robust tornado future shock hidden truck churn tennis inch grain advice gate scare cement credit fatal social arrow much doll palm motion reduce able pool" 11 | 12 | DEVC=WZ6J5IOJYMIWPD3A44VOSSMGCBVTLV4PRKH655SGRZ5CV33YWEPSLR6DMA 13 | DEVCMN="tiger walnut viable grace weasel swear nut fog trust slot earn measure gravity gallery drama awake spatial mandate genre sadness evoke true sound absorb source" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Algorand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an unofficial C++ library for calling v2 Algorand APIs. 2 | Inspired by Algoduino, but needed an implementation that was not tied 3 | to Arduino, and could use v2 APIs, which required msgpack of 4 | transactions, key handling, etc. 5 | 6 | # Getting started 7 | 8 | 1. Install dependencies. Use `make brew-deps` if you use `brew` on a 9 | Mac. Otherwise, check that rule in the Makefile for hints on what 10 | your system might require. Feel free to send PRs for `make 11 | ubuntu-deps` or similar. 12 | 13 | 2. Build. `make` should be sufficient 14 | 15 | 3. Obtain access to an algod (and perhaps indexer) to make your API 16 | calls to. For testing, the [Agorand 17 | Sandbox](https://github.com/algorand/sandbox) is excellent. After 18 | bringing up a sandbox using the defaults, the variables in 19 | `.env.sandbox` will allow testing algod and indexer APIs. The 20 | mnemonics and addresses in `.env.sandbox` are automatically setup 21 | by `sandbox` with plenty of algos. 22 | 23 | 4. You can use `./example` as a very simple exercise harness. 24 | 25 | 26 | # Complete 27 | 1. algod APIs 28 | 2. mnemonic/address/key handling 29 | 3. All transaction types (provided as static functions on a unified 30 | Transaction class) 31 | 4. Simple (single account) signatures 32 | 5. Logicsigs, including delegated logisigs. 33 | 6. Multisigs, contributed by @avislash 34 | 35 | # TODO 36 | 1. indexer APIs 37 | 2. kmd APIs 38 | 3. msgpack responses (currently always uses JSON) 39 | -------------------------------------------------------------------------------- /base.cpp: -------------------------------------------------------------------------------- 1 | #include "base.h" 2 | 3 | #define B64DIGITS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 4 | 5 | std::string b64_encode(const bytes& in, bool padded) { 6 | std::string out; 7 | out.reserve(4 + (4*in.size()/3)); 8 | 9 | int val = 0; 10 | int bits = 0; 11 | for (auto c : in) { 12 | val = (val << 8) + c; 13 | bits += 8; 14 | while (bits >= 6) { 15 | out.push_back(B64DIGITS[(val>>(bits-6))&0x3F]); 16 | bits -= 6; 17 | } 18 | } 19 | if (bits > 0) 20 | out.push_back(B64DIGITS[(val<<(6-bits))&0x3F]); 21 | while (padded && out.size() % 4) 22 | out.push_back('='); 23 | return out; 24 | } 25 | 26 | std::vector decode64_digits() { 27 | std::vector table(256, -1); 28 | for (int i=0; i<64; i++) 29 | table[B64DIGITS[i]] = i; 30 | return table; 31 | } 32 | 33 | 34 | bytes b64_decode(const std::string &in) { 35 | static std::vector dd = decode64_digits(); 36 | 37 | bytes out; 38 | out.reserve(3*in.size()/4); 39 | 40 | int val = 0; 41 | int bits = 0; 42 | for (auto c : in) { 43 | if (dd[c] == -1) // bail on any non-b64 digit 44 | break; 45 | val = (val << 6) + dd[c]; 46 | bits += 6; 47 | if (bits >= 8) { 48 | out.push_back(char((val>>(bits-8))&0xFF)); 49 | bits -= 8; 50 | } 51 | } 52 | return out; 53 | } 54 | 55 | // Surely I should write an implementation that avoids the almost 56 | // cutnpaste job here. 57 | 58 | 59 | #define B32DIGITS "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" 60 | 61 | std::string b32_encode(const bytes& in) { 62 | std::string out; 63 | out.reserve(8 + (8*in.size()/5)); 64 | 65 | int val = 0; 66 | int bits = 0; 67 | for (auto c : in) { 68 | val = (val << 8) + c; 69 | bits += 8; 70 | while (bits >= 5) { 71 | out.push_back(B32DIGITS[(val>>(bits-5))&0x1F]); 72 | bits -= 5; 73 | } 74 | } 75 | if (bits > 0) 76 | out.push_back(B32DIGITS[(val<<(5-bits))&0x1F]); 77 | return out; 78 | } 79 | 80 | std::vector decode32_digits() { 81 | std::vector table(256, -1); 82 | for (int i=0; i<32; i++) 83 | table[B32DIGITS[i]] = i; 84 | return table; 85 | } 86 | 87 | 88 | bytes b32_decode(const std::string &in) { 89 | static std::vector dd = decode32_digits(); 90 | 91 | bytes out; 92 | out.reserve(5*in.size()/8); 93 | 94 | int val = 0; 95 | int bits = 0; 96 | for (auto c : in) { 97 | if (dd[c] == -1) // bail on any non-b32 digit 98 | break; 99 | val = (val << 5) + dd[c]; 100 | bits += 5; 101 | if (bits >= 8) { 102 | out.push_back(char((val>>(bits-8))&0xFF)); 103 | bits -= 8; 104 | } 105 | } 106 | return out; 107 | } 108 | 109 | std::vector b2048_encode(const bytes& in) { 110 | std::vector out; 111 | out.reserve(1+8*in.size()/11); 112 | 113 | unsigned val = 0; 114 | int bits = 0; 115 | for (auto b : in) { 116 | val |= (b << bits); 117 | bits += 8; 118 | if (bits >= 11) { 119 | out.push_back(val & 0x7FF); 120 | val >>= 11; 121 | bits -= 11; 122 | } 123 | } 124 | if (bits > 0) 125 | out.push_back(val & 0x7FF); 126 | return out; 127 | } 128 | 129 | bytes b2048_decode(const std::vector &in) { 130 | bytes out; 131 | out.reserve(1+11*in.size()/8); 132 | 133 | int val = 0; 134 | int bits = 0; 135 | for (auto i : in) { 136 | val |= (i << bits); 137 | bits += 11; 138 | while (bits >= 8) { 139 | out.push_back(val & 0xFF); 140 | val >>= 8; 141 | bits -= 8; 142 | } 143 | } 144 | return out; 145 | } 146 | -------------------------------------------------------------------------------- /clients.cpp: -------------------------------------------------------------------------------- 1 | #include "algorand.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | typedef std::map string_map; 11 | 12 | static std::string 13 | url_escape(const std::string& s) { 14 | // passing nullptr is something that is done in the curl source for 15 | // this function, so it seems safe. 16 | auto encoded = curl_easy_escape(nullptr, s.c_str(), s.length()); 17 | auto as_string(encoded); 18 | curl_free(encoded); 19 | return as_string; 20 | } 21 | 22 | static std::string 23 | url_parameters(const string_map& map) { 24 | std::string params; 25 | for (auto const& kv : map) { 26 | params += (params.empty() ? "?" : "&"); 27 | params += url_escape(kv.first) + "=" + url_escape(kv.second); 28 | } 29 | return params; 30 | } 31 | 32 | static int 33 | curl_request(const std::string& url, 34 | const std::string& method = "GET", 35 | const std::vector& headers = {}, 36 | const std::string& request_body = "", 37 | const std::string* response_body = nullptr); 38 | 39 | std::string 40 | maybe_env(std::string name, std::string def) { 41 | const char* found = getenv(name.c_str()); 42 | if (found) 43 | return found; 44 | return def; 45 | } 46 | 47 | std::string 48 | require_env(std::string name) { 49 | const char* found = getenv(name.c_str()); 50 | if (!found) { 51 | std::cerr << name << " is not set in the environment." << std::endl; 52 | exit(1); 53 | } 54 | return found; 55 | } 56 | 57 | AlgodClient::AlgodClient() : 58 | AlgodClient(require_env("ALGOD_ADDRESS"), require_env("ALGOD_TOKEN")) { 59 | } 60 | 61 | AlgodClient::AlgodClient(std::string address, std::string token) : 62 | RestClient(address, "X-Algo-API-Token: "+token) { 63 | } 64 | 65 | bool 66 | AlgodClient::healthy(void) { 67 | auto resp(get("/health")); 68 | return resp.status == 200; 69 | } 70 | 71 | JsonResponse 72 | AlgodClient::genesis(void) { 73 | return get("/genesis"); 74 | } 75 | 76 | std::string 77 | AlgodClient::metrics(void) { 78 | // Candidate for refactoring to avoid repetition 79 | std::string response_body; 80 | int status = curl_request(prefix + "/metrics", "GET", 81 | {authorization}, 82 | "", &response_body); 83 | if (status == 200) 84 | return response_body; 85 | return ""; 86 | } 87 | 88 | std::string 89 | AlgodClient::account_url(std::string address) const { 90 | return "/v2/accounts/" + address + "?format=json"; 91 | } 92 | 93 | JsonResponse 94 | AlgodClient::account(std::string address) { 95 | assert(!address.empty()); 96 | return get(account_url(address)); 97 | } 98 | 99 | JsonResponse 100 | AlgodClient::transactions_pending(std::string address, unsigned max) { 101 | assert(!address.empty()); 102 | return get("/v2/accounts/" + address + 103 | "/transactions/pending?format=json&max=" + std::to_string(max)); 104 | } 105 | 106 | JsonResponse 107 | AlgodClient::application(std::string id) { 108 | assert(!id.empty()); 109 | return get("/v2/applications/" + id); 110 | } 111 | 112 | std::string 113 | AlgodClient::asset_url(std::string id) const { 114 | return "/v2/assets/" + id; 115 | } 116 | 117 | JsonResponse 118 | AlgodClient::asset(std::string id) { 119 | assert(!id.empty()); 120 | return get(asset_url(id)); 121 | } 122 | 123 | JsonResponse 124 | AlgodClient::block(uint64_t round) { 125 | return get("/v2/blocks/" + std::to_string(round) + "?format=json"); 126 | } 127 | 128 | JsonResponse 129 | AlgodClient::catchup(std::string catchpoint) { 130 | return post("/v2/catchup/" + catchpoint); 131 | } 132 | 133 | JsonResponse 134 | AlgodClient::abort_catchup(std::string catchpoint) { 135 | return api("/v2/catchup/" + catchpoint, "DELETE"); 136 | } 137 | 138 | JsonResponse AlgodClient::supply() { 139 | return get("/v2/ledger/supply"); 140 | } 141 | 142 | JsonResponse 143 | AlgodClient::register_participation_key(std::string address, 144 | uint64_t fee, 145 | uint64_t kd, 146 | bool nw, 147 | uint64_t lv) { 148 | string_map params = {{"fee", std::to_string(fee)}, 149 | {"key-dilution", std::to_string(kd)}, 150 | {"no-wait", std::to_string(nw)}, 151 | {"round-last-valid", std::to_string(lv)}}; 152 | return post("/v2/register-participation-keys/"+address+url_parameters(params)); 153 | } 154 | JsonResponse 155 | AlgodClient::status() { 156 | return get("/v2/status"); 157 | } 158 | JsonResponse 159 | AlgodClient::status_after(uint64_t block) { 160 | return get("/v2/status/wait-for-block-after/"+std::to_string(block)); 161 | } 162 | 163 | JsonResponse 164 | AlgodClient::teal_compile(std::string source) { 165 | return post("/v2/teal/compile", source); 166 | } 167 | JsonResponse 168 | AlgodClient::teal_dryrun(rapidjson::Value& request) { 169 | return post("/v2/teal/dryrun", json_to_string(request)); 170 | } 171 | 172 | std::string 173 | AlgodClient::submit_url() const { 174 | return "/v2/transactions"; 175 | } 176 | 177 | JsonResponse 178 | AlgodClient::submit(std::string rawtxn) const { 179 | return post(submit_url(), rawtxn); 180 | } 181 | 182 | JsonResponse 183 | AlgodClient::submit(const SignedTransaction& stxn) const { 184 | std::stringstream buffer; 185 | msgpack::pack(buffer, stxn); 186 | return submit(buffer.str()); 187 | } 188 | 189 | JsonResponse 190 | AlgodClient::submit(std::vector txgroup) const { 191 | std::stringstream buffer; 192 | for (auto& txn : txgroup) 193 | msgpack::pack(buffer, txn); 194 | return submit(buffer.str()); 195 | } 196 | 197 | std::string 198 | AlgodClient::params_url() const { 199 | return "/v2/transactions/params"; 200 | } 201 | 202 | JsonResponse 203 | AlgodClient::params() { 204 | return get(params_url()); 205 | } 206 | 207 | JsonResponse 208 | AlgodClient::transaction_pending(std::string txid) { 209 | if (txid.empty()) 210 | return get("/v2/transactions/pending"); 211 | return get("/v2/transactions/pending/"+txid); 212 | } 213 | 214 | std::ostream& 215 | operator<<(std::ostream& os, const rapidjson::Value& val) { 216 | rapidjson::OStreamWrapper osw(os); 217 | rapidjson::PrettyWriter writer(osw); 218 | val.Accept(writer); 219 | return os; 220 | } 221 | 222 | std::ostream& 223 | operator<<(std::ostream& os, const JsonResponse& jr) { 224 | return os << jr.status << std::endl << *jr.json; 225 | } 226 | 227 | std::string 228 | json_to_string(const rapidjson::Value& val) { 229 | std::stringstream ss; 230 | ss << val; 231 | return ss.str(); 232 | } 233 | 234 | 235 | static size_t 236 | accumulate_response(void *contents, size_t size, size_t nmemb, std::string *s) { 237 | size_t len = size*nmemb; 238 | s->append((char*)contents, len); 239 | return len; 240 | } 241 | 242 | static size_t 243 | dispense_request(char *dest, size_t size, size_t nmemb, std::string* s) { 244 | size_t len = std::min(s->size(), size*nmemb); 245 | if (!len) 246 | return 0; 247 | 248 | memcpy(dest, s->c_str(), len); 249 | s->erase(0, len); 250 | return len; 251 | } 252 | 253 | static int 254 | curl_request(const std::string& url, 255 | const std::string& method, 256 | const std::vector& headers, 257 | const std::string& request_body, 258 | const std::string* response_body) { 259 | CURL *curl = curl_easy_init(); 260 | if (!curl) 261 | return 200; 262 | 263 | curl_easy_setopt(curl, CURLOPT_VERBOSE, !!getenv("CURL_VERBOSE")); 264 | 265 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); 266 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str()); 267 | 268 | struct curl_slist *header_slist = NULL; 269 | for (auto header : headers) 270 | header_slist = curl_slist_append(header_slist, header.c_str()); 271 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_slist); 272 | 273 | if (request_body.size()) { 274 | // I'm not sure how CURLOPT_POST interacts with CUSTOMREQUEST, but 275 | // it's needed to send data. So if another method requires data...? 276 | curl_easy_setopt(curl, CURLOPT_POST, 1L); 277 | curl_easy_setopt(curl, CURLOPT_READFUNCTION, dispense_request); 278 | curl_easy_setopt(curl, CURLOPT_READDATA, &request_body); 279 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request_body.size()); 280 | } 281 | 282 | if (response_body) { 283 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, accumulate_response); 284 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_body); 285 | } 286 | 287 | CURLcode res = curl_easy_perform(curl); 288 | if (res != CURLE_OK) { 289 | std::cerr << "curl_easy_perform() failed: " 290 | << curl_easy_strerror(res) << std::endl; 291 | assert(false); 292 | } 293 | long http_code = 0; 294 | curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); 295 | 296 | curl_slist_free_all(header_slist); 297 | curl_easy_cleanup(curl); 298 | return http_code; 299 | } 300 | 301 | std::unique_ptr 302 | json_parse(std::string body) { 303 | auto doc = std::make_unique(); 304 | doc->Parse(body); 305 | return doc; 306 | } 307 | 308 | JsonResponse 309 | RestClient::api(const std::string& route, 310 | const std::string& method, 311 | const std::string& request_body) const { 312 | std::string response_body; 313 | int status = curl_request(prefix + route, method, 314 | {"Accept: application/json", 315 | authorization}, 316 | request_body, &response_body); 317 | if (response_body.empty()) 318 | return JsonResponse{status, nullptr}; 319 | return JsonResponse{status, json_parse(response_body)}; 320 | } 321 | 322 | JsonResponse RestClient::get(const std::string& route) const { 323 | return api(route, "GET", ""); 324 | } 325 | 326 | JsonResponse RestClient::post(const std::string& route, const std::string& body) const { 327 | return api(route, "POST", body); 328 | } 329 | 330 | IndexerClient::IndexerClient() : 331 | IndexerClient(require_env("INDEXER_ADDRESS"), require_env("INDEXER_TOKEN")) { 332 | } 333 | 334 | IndexerClient::IndexerClient(std::string address, std::string token) : 335 | RestClient(address, "X-Indexer-API-Token: " + token) { 336 | } 337 | 338 | JsonResponse 339 | IndexerClient::accounts(uint64_t limit, std::string next_page, 340 | uint64_t held_asset, uint64_t min_bal, uint64_t max_bal, 341 | uint64_t optedin_app, 342 | Address auth_addr, uint64_t as_of) { 343 | string_map params; 344 | if (limit != 0) 345 | params["limit"] = std::to_string(limit); 346 | if (next_page.size()) 347 | params["next"] = next_page; 348 | 349 | // asset related 350 | if (held_asset != 0) 351 | params["asset-id"] = std::to_string(held_asset); 352 | if (min_bal > 0) 353 | params["currency-greater-than"] = std::to_string(min_bal); 354 | if (max_bal > 0) 355 | params["currency-less-than"] = std::to_string(max_bal); 356 | 357 | // app related 358 | if (optedin_app != 0) 359 | params["application-id"] = std::to_string(optedin_app); 360 | 361 | // rekeying 362 | if (!auth_addr.is_zero()) 363 | params["auth-addr"] = auth_addr.as_string; 364 | 365 | // time travel 366 | if (as_of > 0) 367 | params["round"] = std::to_string(as_of); 368 | 369 | return get("/v2/accounts"+url_parameters(params)); 370 | } 371 | 372 | JsonResponse IndexerClient::account(Address addr, uint64_t round) { 373 | std::string url("/v2/accounts/"+addr.as_string); 374 | if (round > 0) 375 | url += "?round=" + std::to_string(round); 376 | return get(url); 377 | } 378 | 379 | JsonResponse IndexerClient::block(uint64_t round) { 380 | return get("/v2/blocks/"+std::to_string(round)); 381 | } 382 | 383 | bool 384 | IndexerClient::healthy(void) { 385 | auto resp(get("/health")); 386 | return resp.status == 200; 387 | } 388 | -------------------------------------------------------------------------------- /algorand.h: -------------------------------------------------------------------------------- 1 | #ifndef ALGORAND_H 2 | #define ALGORAND_H 3 | 4 | #include 5 | 6 | #define RAPIDJSON_HAS_STDSTRING 1 7 | #include "rapidjson/document.h" 8 | 9 | #include 10 | 11 | std::ostream& operator<<(std::ostream& os, const rapidjson::Value&); 12 | std::string json_to_string(const rapidjson::Value&); 13 | 14 | std::string maybe_env(std::string name, std::string def = ""); 15 | std::string require_env(std::string name); 16 | 17 | struct JsonResponse { 18 | int status; 19 | std::unique_ptr json; 20 | rapidjson::Value& operator[](const std::string& name) const { 21 | return (*json)[name]; 22 | } 23 | bool succeeded() const { return status == 200; } 24 | }; 25 | std::ostream& operator<<(std::ostream& os, const JsonResponse& jr); 26 | 27 | typedef std::vector bytes; 28 | 29 | class Address { 30 | public: 31 | Address(); // Constructs the ZERO address 32 | Address(std::string b32form); 33 | Address(bytes public_key); 34 | std::string as_string; 35 | bytes public_key; 36 | bool is_zero() const; 37 | private: 38 | Address(std::string s, bytes with_csum); 39 | Address(bytes public_key, bytes with_csum); 40 | }; 41 | inline bool operator==(const Address& lhs, const Address& rhs) { 42 | return lhs.as_string == rhs.as_string && lhs.public_key == rhs.public_key; 43 | } 44 | inline bool operator!=(const Address& lhs, const Address& rhs) { 45 | return !(lhs == rhs); 46 | } 47 | inline bool operator <(const Address& lhs, const Address& rhs ) { 48 | return lhs.as_string < rhs.as_string; 49 | } 50 | 51 | std::ostream& operator<<(std::ostream& os, const Address& addr); 52 | 53 | 54 | class Account { 55 | public: 56 | Account(std::string address); 57 | Account(Address address); 58 | Account(bytes public_key, bytes secret_key); 59 | Account(std::pair key_pair); 60 | 61 | static Account from_mnemonic(std::string mnemonic); 62 | static std::pair generate_keys(); 63 | static std::pair generate_keys(bytes seed); 64 | 65 | std::string mnemonic() const; 66 | bytes seed() const; 67 | bytes sign(std::string prefix, bytes msg) const; 68 | bytes sign(bytes msg) const; 69 | 70 | const bytes public_key() const { return address.public_key; } 71 | const Address address; 72 | const bytes secret_key; // empty() if created from an address, not key 73 | }; 74 | std::ostream& operator<<(std::ostream& os, const Account& acct); 75 | 76 | class SignedTransaction; 77 | 78 | class AssetParams { 79 | public: 80 | uint64_t total = 0; 81 | uint64_t decimals = 0; 82 | bool default_frozen = false; 83 | std::string unit_name; 84 | std::string asset_name; 85 | std::string url; 86 | bytes meta_data_hash; 87 | Address manager_addr; 88 | Address reserve_addr; 89 | Address freeze_addr; 90 | Address clawback_addr; 91 | 92 | template 93 | msgpack::packer& pack(msgpack::packer& o) const; 94 | 95 | int key_count() const; 96 | }; 97 | 98 | class StateSchema { 99 | public: 100 | StateSchema(int, int) : StateSchema() {} 101 | StateSchema() : ints(0), byte_slices(0) {} 102 | uint64_t ints = 0; 103 | uint64_t byte_slices = 0; 104 | 105 | template 106 | msgpack::packer& pack(msgpack::packer& o) const; 107 | 108 | int key_count() const; 109 | }; 110 | 111 | class LogicSig { 112 | public: 113 | LogicSig(bytes logic = {}, std::vector args = {}, bytes sig = {}) : 114 | logic(logic), args(args), sig(sig) {} 115 | bool is_delegated() const { return !logic.empty(); } 116 | 117 | template 118 | msgpack::packer& pack(msgpack::packer& o) const; 119 | 120 | /* Create a new logicsig with the same program, but delegated by the Account. */ 121 | LogicSig sign(Account) const; 122 | 123 | bytes logic; 124 | std::vector args; 125 | bytes sig; 126 | }; 127 | 128 | class Subsig { 129 | public: 130 | Subsig(bytes public_key); 131 | 132 | template 133 | msgpack::packer& pack(msgpack::packer& o) const; 134 | 135 | bytes public_key; 136 | bytes signature; 137 | }; 138 | 139 | class MultiSig { 140 | public: 141 | MultiSig(std::vector
addrs={}, uint8_t threshold=0, uint8_t version=1); 142 | 143 | template 144 | msgpack::packer& pack(msgpack::packer& o) const; 145 | 146 | /* Add signature to multisig if it is a valid signer. */ 147 | bool add_signature(const Account, bytes signature); 148 | bytes address(void) const; 149 | 150 | std::vector sigs; 151 | uint8_t threshold; 152 | uint8_t version; 153 | 154 | private: 155 | Address public_address; 156 | }; 157 | 158 | /* We use a single transaction class to represent all transaction 159 | types. While it might seem natural to have Payment, AssetCreate 160 | and so on as subclasses, it would complicate msgpacking. Standard 161 | msgpack does not "omitempty" (as we must to be compatible with 162 | algod/spec), so we need to use the lower-level packing routines. 163 | It seems easier to consolidate that in one place than to create an 164 | interface for subclasses that would allow them to a) report how 165 | many keys they intend to populate and b) return the pairs, and c) 166 | sort them before packing them from the top. 167 | 168 | We'd then also need some sort of "virtual constructor" pattern to 169 | unpack Transactions into the right subclass. 170 | 171 | PS. This more closely models the implementation in algod. 172 | */ 173 | class Transaction { 174 | Transaction(Address sender, std::string tx_type); 175 | public: 176 | static Transaction payment(Address sender, 177 | 178 | Address receiver, uint64_t amount, Address close_to, 179 | 180 | uint64_t fee, 181 | uint64_t first_valid, uint64_t last_valid, 182 | std::string genesis_id, bytes genesis_hash, 183 | bytes lease, bytes note, Address rekey_to); 184 | 185 | static Transaction key_registration(Address sender, 186 | 187 | bytes vote_pk, 188 | bytes selection_pk, 189 | uint64_t vote_first, 190 | uint64_t vote_last, 191 | uint64_t vote_key_dilution, 192 | bool nonparticipation, 193 | 194 | uint64_t fee, 195 | uint64_t first_valid, uint64_t last_valid, 196 | std::string genesis_id, bytes genesis_hash, 197 | bytes lease, bytes note, Address rekey_to); 198 | 199 | static Transaction asset_config(Address sender, 200 | 201 | uint64_t asset_id, AssetParams asset_params, 202 | 203 | uint64_t fee, 204 | uint64_t first_valid, uint64_t last_valid, 205 | std::string genesis_id, bytes genesis_hash, 206 | bytes lease, bytes note, Address rekey_to); 207 | 208 | static Transaction asset_transfer(Address sender, 209 | 210 | uint64_t asset_id, uint64_t asset_amount, 211 | Address asset_sender, 212 | Address asset_receiver, 213 | Address asset_close_to, 214 | 215 | uint64_t fee, 216 | uint64_t first_valid, uint64_t last_valid, 217 | std::string genesis_id, bytes genesis_hash, 218 | bytes lease, bytes note, Address rekey_to); 219 | 220 | static Transaction asset_freeze(Address sender, 221 | 222 | Address freeze_account, 223 | uint64_t freeze_asset, 224 | bool asset_frozen, 225 | 226 | uint64_t fee, 227 | uint64_t first_valid, uint64_t last_valid, 228 | std::string genesis_id, bytes genesis_hash, 229 | bytes lease, bytes note, Address rekey_to); 230 | 231 | static Transaction app_call(Address sender, 232 | 233 | uint64_t application_id, 234 | uint64_t on_complete, 235 | std::vector
accounts, 236 | bytes approval_program, bytes clear_state_program, 237 | std::vector app_arguments, 238 | std::vector foreign_apps, 239 | std::vector foreign_assets, 240 | StateSchema globals, StateSchema locals, 241 | 242 | uint64_t fee, 243 | uint64_t first_valid, uint64_t last_valid, 244 | std::string genesis_id, bytes genesis_hash, 245 | bytes lease, bytes note, Address rekey_to); 246 | 247 | 248 | SignedTransaction sign(Account) const; 249 | SignedTransaction sign(LogicSig) const; 250 | SignedTransaction sign(MultiSig, std::vector) const; 251 | // Field names and sections are taken from: 252 | // https://developer.algorand.org/docs/reference/transactions/ 253 | // Header 254 | uint64_t fee = 1000; // required parameter, but a nice safety 255 | uint64_t first_valid; 256 | bytes genesis_hash; 257 | uint64_t last_valid; 258 | Address sender; 259 | std::string tx_type; 260 | std::string genesis_id; 261 | bytes group; 262 | bytes lease; 263 | bytes note; 264 | Address rekey_to; 265 | 266 | // Payment 267 | Address receiver; 268 | uint64_t amount = 0; 269 | Address close_to; 270 | 271 | // Key Registration 272 | bytes vote_pk; 273 | bytes selection_pk; 274 | uint64_t vote_first = 0; 275 | uint64_t vote_last = 0; 276 | uint64_t vote_key_dilution = 0; 277 | bool nonparticipation = false; 278 | 279 | // Asset Config 280 | uint64_t config_asset = 0; 281 | AssetParams asset_params; 282 | 283 | // Asset Transfer 284 | uint64_t xfer_asset = 0; 285 | uint64_t asset_amount = 0; 286 | Address asset_sender; 287 | Address asset_receiver; 288 | Address asset_close_to; 289 | 290 | // Asset Freeze 291 | uint64_t freeze_asset = 0; 292 | Address freeze_account; 293 | bool asset_frozen = false; 294 | 295 | // Application Call 296 | uint64_t application_id = 0; 297 | uint64_t on_complete = 0; 298 | std::vector
accounts; 299 | bytes approval_program; 300 | bytes clear_state_program; 301 | std::vector app_arguments; 302 | std::vector foreign_apps; 303 | std::vector foreign_assets; 304 | StateSchema globals; 305 | StateSchema locals; 306 | 307 | template 308 | msgpack::packer& pack(msgpack::packer& o) const; 309 | 310 | int key_count() const; 311 | bytes encode() const; 312 | }; 313 | 314 | std::ostream& operator<<(std::ostream& os, const Transaction& txn); 315 | 316 | class SignedTransaction { 317 | public: 318 | SignedTransaction(const Transaction& txn, bytes signature); 319 | SignedTransaction(const Transaction& txn, LogicSig logic); 320 | SignedTransaction(const Transaction& txn, MultiSig multi); 321 | bytes encode() const; 322 | 323 | template 324 | msgpack::packer& pack(msgpack::packer& o) const; 325 | // Reconsider macro use once we do unpack for Transaction 326 | // MSGPACK_DEFINE_MAP(sig, txn); 327 | private: 328 | bytes sig; 329 | LogicSig lsig; 330 | MultiSig msig; 331 | Address signer; 332 | Transaction txn; 333 | }; 334 | 335 | namespace msgpack { 336 | MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) { 337 | namespace adaptor { 338 | template<> 339 | struct pack
{ 340 | template 341 | packer& 342 | operator()(msgpack::packer& o, Address const& v) const { 343 | // We can't use MSGPACK_DEFINE because we don't want to 344 | // encode an "outer" object here, we just want an Address to 345 | // encode the public_key as if that was the whole object. 346 | return o.pack(v.public_key); 347 | } 348 | }; 349 | 350 | template<> 351 | struct pack { 352 | template 353 | packer& 354 | operator()(msgpack::packer& o, LogicSig const& v) const { 355 | return v.pack(o); 356 | } 357 | }; 358 | 359 | template<> 360 | struct pack { 361 | template 362 | packer& 363 | operator()(msgpack::packer& o, Subsig const& v) const { 364 | return v.pack(o); 365 | } 366 | }; 367 | 368 | template<> 369 | struct pack { 370 | template 371 | packer& 372 | operator()(msgpack::packer& o, MultiSig const& v) const { 373 | return v.pack(o); 374 | } 375 | }; 376 | 377 | template<> 378 | struct pack { 379 | template 380 | packer& 381 | operator()(msgpack::packer& o, Transaction const& v) const { 382 | // We don't use the MSGPACK_DEFINE_MAP macro because we need 383 | // to "omitempty" for compatibility. That requires counting 384 | // keys first, to size the map, and then packing them (in 385 | // lexographical order). 386 | return v.pack(o); 387 | } 388 | }; 389 | 390 | template<> 391 | struct pack { 392 | template 393 | packer& 394 | operator()(msgpack::packer& o, SignedTransaction const& v) const { 395 | // We don't use the MSGPACK_DEFINE_MAP macro because 396 | // Transaction has no unpacking support yet. 397 | return v.pack(o); 398 | } 399 | }; 400 | 401 | template<> 402 | struct pack { 403 | template 404 | packer& 405 | operator()(msgpack::packer& o, AssetParams const& v) const { 406 | // "omitempty" problem, and special Address handling 407 | return v.pack(o); 408 | } 409 | }; 410 | 411 | template<> 412 | struct pack { 413 | template 414 | packer& 415 | operator()(msgpack::packer& o, StateSchema const& v) const { 416 | // "omitempty" problem 417 | return v.pack(o); 418 | } 419 | }; 420 | 421 | } // namespace adaptor 422 | } // MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) 423 | } 424 | 425 | class RestClient { 426 | public: 427 | RestClient(std::string prefix, std::string authorization) : 428 | prefix(prefix), authorization(authorization) { } 429 | /** 430 | * @brief Return the requested information from the API using method 431 | * @param route API route. 432 | * @param method HTTP method to make the request with 433 | * @param request_body raw bytes to be sent as body of request 434 | * @return JsonResponse with the status code and JSON value from response 435 | */ 436 | JsonResponse api(const std::string& route, 437 | const std::string& method, 438 | const std::string& request_body = "") const; 439 | 440 | /** 441 | * @brief Return the requested information from the API using a GET 442 | * @param route API route. 443 | * @return string containing the requested information. 444 | */ 445 | JsonResponse get(const std::string& route) const; 446 | 447 | /** 448 | * @brief Return the requested information from the API using a POST 449 | * @param route API route. 450 | * @param body Raw bytes to send as body. "" means no body. 451 | * @return string containing the requested information. 452 | */ 453 | JsonResponse post(const std::string& route, const std::string& body = "") const; 454 | 455 | protected: 456 | std::string prefix; 457 | std::string authorization; 458 | }; 459 | 460 | class AlgodClient : public RestClient { 461 | public: 462 | /** 463 | * @brief Initialize the client. Reads ALGOD_ADDRESS, ALGOD_TOKEN 464 | * from environment. 465 | */ 466 | AlgodClient(); 467 | 468 | /** 469 | * @brief Initialize the client with passed address for algod and API token. 470 | */ 471 | AlgodClient(std::string address, std::string token); 472 | 473 | JsonResponse genesis(void); 474 | bool healthy(void); 475 | std::string metrics(void); 476 | 477 | virtual std::string account_url(std::string address) const; 478 | JsonResponse account(std::string address); 479 | JsonResponse account(const Address& addr) { return account(addr.as_string); } 480 | JsonResponse account(const Account& acct) { return account(acct.address); } 481 | 482 | JsonResponse transactions_pending(std::string address, unsigned max = 0); 483 | JsonResponse application(std::string id); 484 | JsonResponse application(uint64_t id) { return asset(std::to_string(id)); } 485 | 486 | virtual std::string asset_url(std::string id) const; 487 | JsonResponse asset(std::string id); 488 | JsonResponse asset(uint64_t id) { return asset(std::to_string(id)); } 489 | 490 | JsonResponse block(uint64_t round); 491 | JsonResponse catchup(std::string catchpoint); 492 | JsonResponse abort_catchup(std::string catchpoint); 493 | JsonResponse supply(); 494 | JsonResponse register_participation_key(std::string address, uint64_t fee, uint64_t key_dilution, bool no_wait, uint64_t lv); 495 | JsonResponse status(); 496 | JsonResponse status_after(uint64_t block); 497 | 498 | JsonResponse teal_compile(std::string source); 499 | JsonResponse teal_dryrun(rapidjson::Value& request); 500 | 501 | virtual std::string submit_url() const; 502 | JsonResponse submit(std::string raw) const; 503 | JsonResponse submit(const SignedTransaction& stxn) const; 504 | JsonResponse submit(const std::vector stxn) const; 505 | JsonResponse transaction_pending(std::string txid = ""); 506 | 507 | virtual std::string params_url() const; 508 | JsonResponse params(); 509 | }; 510 | 511 | class IndexerClient : RestClient { 512 | public: 513 | /** 514 | * @brief Initialize the client. Reads INDEXER_ADDRESS, INDEXER_TOKEN 515 | * from environment. 516 | */ 517 | IndexerClient(); 518 | /** 519 | * @brief Initialize the client with passed address for indexer and API token. 520 | */ 521 | IndexerClient(std::string address, std::string token); 522 | 523 | bool healthy(void); 524 | JsonResponse accounts(uint64_t limit=20, std::string next_page="", 525 | uint64_t held_asset=0, uint64_t min_bal=0, uint64_t max_bal=0, 526 | uint64_t optedin_app=0, 527 | Address auth_addr=Address(), uint64_t as_of=0); 528 | JsonResponse account(Address addr, uint64_t round=0); 529 | JsonResponse block(uint64_t round=0); 530 | }; 531 | #endif 532 | -------------------------------------------------------------------------------- /algorand.cpp: -------------------------------------------------------------------------------- 1 | #include "algorand.h" 2 | #include "base.h" 3 | #include "mnemonic.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | Address::Address() : 13 | Address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ") { 14 | } 15 | 16 | // Address::Address(const Address& rhs) : 17 | // as_string(rhs.as_string), 18 | // public_key(rhs.public_key) { 19 | // } 20 | 21 | bool 22 | Address::is_zero() const { 23 | return public_key == bytes{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 24 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 25 | } 26 | 27 | Address::Address(std::string address) : 28 | Address(address, b32_decode(address)) { 29 | } 30 | 31 | Address::Address(std::string address, bytes with_checksum) : 32 | as_string(address), 33 | public_key(bytes{with_checksum.begin(), with_checksum.begin()+32}) { 34 | assert(as_string.size() == 58); 35 | assert(public_key.size() == 32); 36 | } 37 | 38 | std::string to_hex(const std::string& in) { 39 | std::stringstream ss; 40 | ss << std::hex << std::setfill('0'); 41 | for (size_t i = 0; in.size() > i; i++) { 42 | ss << std::setw(2) << (int)(unsigned char)in[i] << ':'; 43 | } 44 | return ss.str(); 45 | } 46 | std::string to_hex(const bytes& in) { 47 | std::stringstream ss; 48 | ss << std::hex << std::setfill('0'); 49 | for (size_t i = 0; in.size() > i; i++) { 50 | ss << std::setw(2) << (int)(unsigned char)in[i] << ':'; 51 | } 52 | return ss.str(); 53 | } 54 | 55 | static bytes 56 | checksummed(bytes public_key) { 57 | bytes copy(public_key); 58 | auto hash = sha512_256(public_key); 59 | copy.insert(copy.end(), hash.end()-4, hash.end()); 60 | return copy; 61 | } 62 | 63 | Address::Address(bytes public_key) : Address(public_key, checksummed(public_key)) { } 64 | 65 | Address::Address(bytes public_key, bytes checksummed) : 66 | as_string(b32_encode(checksummed)), 67 | public_key(public_key) { 68 | assert(as_string.size() == 58); 69 | assert(public_key.size() == 32); 70 | } 71 | 72 | std::ostream& 73 | operator<<(std::ostream& os, const Address& addr) { 74 | os << addr.as_string; 75 | return os; 76 | } 77 | 78 | std::pair 79 | Account::generate_keys(bytes seed) { 80 | assert(sodium_init() >= 0); 81 | unsigned char ed25519_pk[crypto_sign_ed25519_PUBLICKEYBYTES]; 82 | unsigned char ed25519_sk[crypto_sign_ed25519_SECRETKEYBYTES]; 83 | 84 | crypto_sign_ed25519_seed_keypair(ed25519_pk, ed25519_sk, seed.data()); 85 | auto pub = bytes{ed25519_pk, &ed25519_pk[sizeof(ed25519_pk)]}; 86 | auto sec = bytes{ed25519_sk, &ed25519_sk[sizeof(ed25519_sk)]}; 87 | return std::make_pair(pub, sec); 88 | } 89 | 90 | std::pair 91 | Account::generate_keys() { 92 | assert(sodium_init() >= 0); 93 | unsigned char ed25519_pk[crypto_sign_ed25519_PUBLICKEYBYTES]; 94 | unsigned char ed25519_sk[crypto_sign_ed25519_SECRETKEYBYTES]; 95 | 96 | crypto_sign_ed25519_keypair(ed25519_pk, ed25519_sk); 97 | auto pub = bytes{ed25519_pk, &ed25519_pk[sizeof(ed25519_pk)]}; 98 | auto sec = bytes{ed25519_sk, &ed25519_sk[sizeof(ed25519_sk)]}; 99 | return std::make_pair(pub, sec); 100 | } 101 | 102 | bytes 103 | Account::seed() const { 104 | unsigned char ed25519_seed[crypto_sign_ed25519_SEEDBYTES]; 105 | crypto_sign_ed25519_sk_to_seed(ed25519_seed, secret_key.data()); 106 | return bytes{ed25519_seed, &ed25519_seed[sizeof(ed25519_seed)]}; 107 | } 108 | 109 | bytes 110 | Account::sign(std::string prefix, bytes msg) const { 111 | bytes concat{prefix.begin(), prefix.end()}; 112 | concat.insert(concat.end(), msg.begin(), msg.end()); 113 | return sign(concat); 114 | } 115 | 116 | bytes 117 | Account::sign(bytes msg) const { 118 | assert(secret_key.size() == crypto_sign_ed25519_SECRETKEYBYTES); 119 | unsigned char sig[crypto_sign_ed25519_BYTES]; 120 | crypto_sign_ed25519_detached(sig, 0, msg.data(), msg.size(), secret_key.data()); 121 | auto s = bytes{sig, &sig[sizeof(sig)]}; 122 | return s; 123 | 124 | } 125 | 126 | Account::Account(std::string address) 127 | : address(Address(address)) { 128 | } 129 | 130 | Account::Account(Address address) 131 | : address(address) { 132 | } 133 | 134 | Account::Account(bytes public_key, bytes secret_key) 135 | : address(Address(public_key)), secret_key(secret_key) { 136 | assert(public_key.size() == crypto_sign_ed25519_PUBLICKEYBYTES); 137 | assert(secret_key.size() == crypto_sign_ed25519_SECRETKEYBYTES); 138 | } 139 | 140 | Account::Account(std::pair key_pair) : 141 | Account(key_pair.first, key_pair.second) { 142 | } 143 | 144 | Account Account::from_mnemonic(std::string m) { 145 | auto seed = seed_from_mnemonic(m); 146 | auto keys = generate_keys(seed); 147 | return Account(keys.first, keys.second); 148 | } 149 | 150 | std::string Account::mnemonic() const { 151 | return mnemonic_from_seed(seed()); 152 | } 153 | 154 | std::ostream& 155 | operator<<(std::ostream& os, const Account& acct) { 156 | os << acct.address; 157 | return os; 158 | } 159 | 160 | bool is_present(bool b) { 161 | return b; 162 | } 163 | bool is_present(uint8_t u) { 164 | return u != 0; 165 | } 166 | bool is_present(uint64_t u) { 167 | return u != 0; 168 | } 169 | bool is_present(std::string s) { 170 | return !s.empty(); 171 | } 172 | bool is_present(bytes b) { 173 | return !b.empty(); 174 | } 175 | bool is_present(Address a) { 176 | return !a.is_zero(); 177 | } 178 | bool is_present(LogicSig lsig) { 179 | return is_present(lsig.logic); 180 | }; 181 | bool is_present(Subsig subsig) { 182 | return is_present(subsig.public_key); 183 | }; 184 | bool is_present(MultiSig msig) { 185 | return msig.threshold > 0; 186 | }; 187 | bool is_present(AssetParams ap) { 188 | return ap.key_count() > 0; 189 | }; 190 | bool is_present(StateSchema schema) { 191 | return schema.ints > 0 || schema.byte_slices > 0; 192 | }; 193 | bool is_present(Transaction) { 194 | return true; 195 | }; 196 | 197 | template 198 | bool is_present(std::vector list) { 199 | for (const auto& e : list) 200 | if (is_present(e)) return true; 201 | return false; 202 | } 203 | 204 | template 205 | int kv_pack(msgpack::packer& o, const char* key, V value) { 206 | if (!is_present(value)) 207 | return 0; 208 | o.pack(key); 209 | o.pack(value); 210 | return 1; 211 | } 212 | 213 | LogicSig LogicSig::sign(Account acct) const { 214 | auto sig = acct.sign("Program", logic); 215 | return LogicSig{logic, args, sig}; 216 | } 217 | 218 | template 219 | msgpack::packer& LogicSig::pack(msgpack::packer& o) const { 220 | o.pack_map(1 + is_present(args) + is_present(sig)); 221 | kv_pack(o, "arg", args); 222 | kv_pack(o, "l", logic); 223 | kv_pack(o, "sig", sig); 224 | return o; 225 | } 226 | 227 | Subsig::Subsig(bytes public_key) 228 | : public_key(public_key) { } 229 | 230 | template 231 | msgpack::packer& Subsig::pack(msgpack::packer& o) const { 232 | o.pack_map(1 + is_present(signature)); 233 | kv_pack(o, "pk", public_key); 234 | kv_pack(o, "s", signature); 235 | return o; 236 | } 237 | 238 | MultiSig::MultiSig(std::vector
addrs, uint8_t threshold, uint8_t version) : 239 | threshold{threshold ? threshold : static_cast(addrs.size())}, 240 | version{version} { 241 | for (const auto& addr : addrs) { 242 | sigs.push_back(Subsig(addr.public_key)); 243 | } 244 | 245 | const std::string msig_header = "MultisigAddr"; 246 | bytes msig_bytes{msig_header.begin(), msig_header.end()}; 247 | msig_bytes.push_back(this->version); 248 | msig_bytes.push_back(this->threshold); 249 | 250 | for(const auto& sig: this->sigs) { 251 | msig_bytes.insert(msig_bytes.end(), sig.public_key.begin(), sig.public_key.end()); 252 | } 253 | 254 | this->public_address = Address{sha512_256(msig_bytes)}; 255 | } 256 | 257 | //Note: msig address is part of the transaction and gets packed 258 | //as part of the transaction 259 | template 260 | msgpack::packer& MultiSig::pack(msgpack::packer& o) const { 261 | o.pack_map(3); 262 | kv_pack(o, "subsig", sigs); 263 | kv_pack(o, "thr", threshold); 264 | kv_pack(o, "v", version); 265 | return o; 266 | } 267 | 268 | bool MultiSig::add_signature(const Account account, bytes signature) { 269 | for (auto& sig: sigs) { 270 | if (sig.public_key == account.public_key()) { 271 | sig.signature = signature; 272 | return true; 273 | } 274 | } 275 | 276 | return false; 277 | } 278 | 279 | bytes MultiSig::address(void) const { 280 | return this->public_address.public_key; 281 | } 282 | 283 | 284 | SignedTransaction::SignedTransaction(const Transaction& txn, bytes signature) : 285 | sig(signature), txn(txn) { } 286 | 287 | SignedTransaction::SignedTransaction(const Transaction& txn, LogicSig logic) : 288 | lsig(logic), txn(txn) { } 289 | 290 | SignedTransaction::SignedTransaction(const Transaction& txn, MultiSig multi) : 291 | msig(multi), txn(txn) { } 292 | 293 | template 294 | msgpack::packer& SignedTransaction::pack(msgpack::packer& o) const { 295 | o.pack_map(2 + is_present(signer)); // one of the sig types, txn, and maybe sgnr 296 | kv_pack(o, "lsig", lsig); 297 | kv_pack(o, "msig", msig); 298 | kv_pack(o, "sgnr", signer); 299 | kv_pack(o, "sig", sig); 300 | kv_pack(o, "txn", txn); 301 | return o; 302 | } 303 | 304 | bytes SignedTransaction::encode() const { 305 | std::stringstream buffer; 306 | msgpack::pack(buffer, *this); 307 | std::string const& s = buffer.str(); 308 | bytes data{s.begin(), s.end()}; 309 | return data; 310 | } 311 | 312 | 313 | int AssetParams::key_count() const { 314 | /* count the non-empty fields, for msgpack */ 315 | int keys = 0; 316 | keys += is_present(total); 317 | keys += is_present(decimals); 318 | keys += is_present(default_frozen); 319 | keys += is_present(unit_name); 320 | keys += is_present(asset_name); 321 | keys += is_present(url); 322 | keys += is_present(meta_data_hash); 323 | keys += is_present(manager_addr); 324 | keys += is_present(reserve_addr); 325 | keys += is_present(freeze_addr); 326 | keys += is_present(clawback_addr); 327 | return keys; 328 | } 329 | 330 | template 331 | msgpack::packer& AssetParams::pack(msgpack::packer& o) const { 332 | o.pack_map(key_count()); 333 | /* ordering is semantically ugly, but must be lexicographic */ 334 | kv_pack(o, "an", asset_name); 335 | kv_pack(o, "au", url); 336 | kv_pack(o, "c", clawback_addr.public_key); 337 | kv_pack(o, "dc", decimals); 338 | kv_pack(o, "df", default_frozen); 339 | kv_pack(o, "f", freeze_addr); 340 | kv_pack(o, "m", manager_addr); 341 | kv_pack(o, "r", reserve_addr); 342 | kv_pack(o, "t", total); 343 | kv_pack(o, "un", unit_name); 344 | return o; 345 | } 346 | 347 | int StateSchema::key_count() const { 348 | /* count the non-empty fields, for msgpack */ 349 | int keys = 0; 350 | keys += is_present(ints); 351 | keys += is_present(byte_slices); 352 | return keys; 353 | } 354 | 355 | template 356 | msgpack::packer& StateSchema::pack(msgpack::packer& o) const { 357 | o.pack_map(key_count()); 358 | kv_pack(o, "nui", ints); 359 | kv_pack(o, "nbs", byte_slices); 360 | return o; 361 | } 362 | 363 | Transaction::Transaction(Address sender, std::string tx_type) : 364 | sender(sender), tx_type(tx_type) { } 365 | 366 | Transaction 367 | Transaction::payment(Address sender, 368 | Address receiver, uint64_t amount, Address close_to, 369 | uint64_t fee, 370 | uint64_t first_valid, uint64_t last_valid, 371 | std::string genesis_id, bytes genesis_hash, 372 | bytes lease, bytes note, Address rekey_to) { 373 | Transaction t = Transaction(sender, "pay"); 374 | 375 | t.receiver = receiver; 376 | t.amount = amount; 377 | t.close_to = close_to; 378 | 379 | t.fee = fee; 380 | t.first_valid = first_valid; 381 | t.last_valid = last_valid; 382 | 383 | t.genesis_id = genesis_id; 384 | t.genesis_hash = genesis_hash; 385 | t.lease = lease; 386 | t.note = note; 387 | t.rekey_to = rekey_to; 388 | return t; 389 | } 390 | 391 | Transaction 392 | Transaction::asset_config(Address sender, 393 | 394 | uint64_t asset_id, AssetParams asset_params, 395 | 396 | uint64_t fee, 397 | uint64_t first_valid, uint64_t last_valid, 398 | std::string genesis_id, bytes genesis_hash, 399 | bytes lease, bytes note, Address rekey_to) { 400 | Transaction t = Transaction(sender, "acfg"); 401 | 402 | t.config_asset = asset_id; 403 | t.asset_params = asset_params; 404 | 405 | t.fee = fee; 406 | t.first_valid = first_valid; 407 | t.last_valid = last_valid; 408 | 409 | t.genesis_id = genesis_id; 410 | t.genesis_hash = genesis_hash; 411 | t.lease = lease; 412 | t.note = note; 413 | t.rekey_to = rekey_to; 414 | return t; 415 | } 416 | 417 | Transaction Transaction::asset_transfer(Address sender, 418 | 419 | uint64_t asset_id, uint64_t asset_amount, 420 | Address asset_sender, 421 | Address asset_receiver, 422 | Address asset_close_to, 423 | 424 | uint64_t fee, 425 | uint64_t first_valid, uint64_t last_valid, 426 | std::string genesis_id, bytes genesis_hash, 427 | bytes lease, bytes note, Address rekey_to) { 428 | Transaction t = Transaction(sender, "axfer"); 429 | 430 | t.xfer_asset = asset_id; 431 | t.asset_amount = asset_amount; 432 | t.asset_sender = asset_sender; 433 | t.asset_receiver = asset_receiver; 434 | t.asset_close_to = asset_close_to; 435 | 436 | t.fee = fee; 437 | t.first_valid = first_valid; 438 | t.last_valid = last_valid; 439 | 440 | t.genesis_id = genesis_id; 441 | t.genesis_hash = genesis_hash; 442 | t.lease = lease; 443 | t.note = note; 444 | t.rekey_to = rekey_to; 445 | return t; 446 | } 447 | 448 | Transaction Transaction::asset_freeze(Address sender, 449 | 450 | Address freeze_account, 451 | uint64_t freeze_asset, 452 | bool asset_frozen, 453 | 454 | uint64_t fee, 455 | uint64_t first_valid, uint64_t last_valid, 456 | std::string genesis_id, bytes genesis_hash, 457 | bytes lease, bytes note, Address rekey_to) { 458 | Transaction t = Transaction(sender, "afrz"); 459 | 460 | t.freeze_account = freeze_account; 461 | t.freeze_asset = freeze_asset; 462 | t.asset_frozen = asset_frozen; 463 | 464 | t.fee = fee; 465 | t.first_valid = first_valid; 466 | t.last_valid = last_valid; 467 | 468 | t.genesis_id = genesis_id; 469 | t.genesis_hash = genesis_hash; 470 | t.lease = lease; 471 | t.note = note; 472 | t.rekey_to = rekey_to; 473 | return t; 474 | } 475 | 476 | Transaction 477 | Transaction::app_call(Address sender, 478 | 479 | uint64_t application_id, 480 | uint64_t on_complete, 481 | std::vector
accounts, 482 | bytes approval_program, bytes clear_state_program, 483 | std::vector app_arguments, 484 | std::vector foreign_apps, 485 | std::vector foreign_assets, 486 | StateSchema globals, StateSchema locals, 487 | 488 | uint64_t fee, 489 | uint64_t first_valid, uint64_t last_valid, 490 | std::string genesis_id, bytes genesis_hash, 491 | bytes lease, bytes note, Address rekey_to) { 492 | Transaction t = Transaction(sender, "appl"); 493 | 494 | t.application_id = application_id; 495 | t.on_complete = on_complete; 496 | t.accounts = accounts; 497 | t.approval_program = approval_program; 498 | t.clear_state_program = clear_state_program; 499 | t.app_arguments = app_arguments; 500 | t.foreign_apps = foreign_apps; 501 | t.foreign_assets = foreign_assets; 502 | t.globals = globals; 503 | t.locals = locals; 504 | 505 | t.fee = fee; 506 | t.first_valid = first_valid; 507 | t.last_valid = last_valid; 508 | 509 | t.genesis_id = genesis_id; 510 | t.genesis_hash = genesis_hash; 511 | t.lease = lease; 512 | t.note = note; 513 | t.rekey_to = rekey_to; 514 | return t; 515 | } 516 | 517 | SignedTransaction Transaction::sign(Account acct) const { 518 | auto sig = acct.sign("TX", encode()); 519 | return SignedTransaction{*this, sig}; 520 | } 521 | 522 | SignedTransaction Transaction::sign(LogicSig logic) const { 523 | return SignedTransaction{*this, logic}; 524 | } 525 | 526 | SignedTransaction 527 | Transaction::sign(MultiSig no_sigs, std::vector accounts) const { 528 | MultiSig with_sigs{no_sigs}; 529 | for (auto acct : accounts) { 530 | auto sig = acct.sign("TX", encode()); 531 | with_sigs.add_signature(acct, sig); 532 | } 533 | return SignedTransaction{*this, with_sigs}; 534 | } 535 | 536 | int Transaction::key_count() const { 537 | /* count the non-empty fields, for msgpack */ 538 | int keys = 0; 539 | keys += is_present(fee); 540 | keys += is_present(first_valid); 541 | keys += is_present(genesis_hash); 542 | keys += is_present(last_valid); 543 | keys += is_present(sender); 544 | keys += is_present(tx_type); 545 | keys += is_present(genesis_id); 546 | keys += is_present(group); 547 | keys += is_present(lease); 548 | keys += is_present(note); 549 | keys += is_present(rekey_to); 550 | 551 | // PaymentTxnFields 552 | keys += is_present(receiver); 553 | keys += is_present(amount); 554 | keys += is_present(close_to); 555 | 556 | // KeyregTxnFields 557 | keys += is_present(vote_pk); 558 | keys += is_present(selection_pk); 559 | keys += is_present(vote_first); 560 | keys += is_present(vote_last); 561 | keys += is_present(vote_key_dilution); 562 | keys += is_present(nonparticipation); 563 | 564 | // AssetConfigTxnFields 565 | keys += is_present(config_asset); 566 | keys += is_present(asset_params); 567 | 568 | // AssetTransferTxnFields 569 | keys += is_present(xfer_asset); 570 | keys += is_present(asset_amount); 571 | keys += is_present(asset_sender); 572 | keys += is_present(asset_receiver); 573 | keys += is_present(asset_close_to); 574 | 575 | // AssetFreezeTxnFields 576 | keys += is_present(freeze_account); 577 | keys += is_present(freeze_asset); 578 | keys += is_present(asset_frozen); 579 | 580 | // ApplicationCallTxnFields 581 | keys += is_present(application_id); 582 | keys += is_present(on_complete); 583 | keys += is_present(accounts); 584 | keys += is_present(approval_program); 585 | keys += is_present(clear_state_program); 586 | keys += is_present(app_arguments); 587 | keys += is_present(foreign_apps); 588 | keys += is_present(foreign_assets); 589 | keys += is_present(globals); 590 | keys += is_present(locals); 591 | 592 | return keys; 593 | } 594 | 595 | template 596 | msgpack::packer& Transaction::pack(msgpack::packer& o) const { 597 | /* 598 | Canonical Msgpack: maps must contain keys in lexicographic order; 599 | maps must omit key-value pairs where the value is a zero-value; 600 | positive integer values must be encoded as "unsigned" in msgpack, 601 | regardless of whether the value space is semantically signed or 602 | unsigned; integer values must be represented in the shortest 603 | possible encoding; binary arrays must be represented using the 604 | "bin" format family (that is, use the most recent version of 605 | msgpack rather than the older msgpack version that had no "bin" 606 | family). 607 | */ 608 | 609 | // Remember, sort these by the key name, not the variable name! 610 | // kv_pack exists so that these lines can be sorted directly. 611 | o.pack_map(key_count()); 612 | kv_pack(o, "aamt", asset_amount); 613 | kv_pack(o, "aclose", asset_close_to); 614 | kv_pack(o, "afrz", asset_frozen); 615 | kv_pack(o, "amt", amount); 616 | kv_pack(o, "apaa", app_arguments); 617 | kv_pack(o, "apan", on_complete); 618 | kv_pack(o, "apap", approval_program); 619 | kv_pack(o, "apar", asset_params); 620 | kv_pack(o, "apas", foreign_assets); 621 | kv_pack(o, "apat", accounts); 622 | kv_pack(o, "apfa", foreign_apps); 623 | kv_pack(o, "apgs", globals); 624 | kv_pack(o, "apid", application_id); 625 | kv_pack(o, "apls", locals); 626 | kv_pack(o, "apsu", clear_state_program); 627 | kv_pack(o, "arcv", asset_receiver); 628 | kv_pack(o, "asnd", asset_sender); 629 | kv_pack(o, "caid", config_asset); 630 | kv_pack(o, "close", close_to); 631 | kv_pack(o, "fadd", freeze_account); 632 | kv_pack(o, "faid", freeze_asset); 633 | kv_pack(o, "fee", fee); 634 | kv_pack(o, "fv", first_valid); 635 | kv_pack(o, "gen", genesis_id); 636 | kv_pack(o, "gh", genesis_hash); 637 | kv_pack(o, "grp", group); 638 | kv_pack(o, "lv", last_valid); 639 | kv_pack(o, "lx", lease); 640 | kv_pack(o, "nonpart", nonparticipation); 641 | kv_pack(o, "note", note); 642 | kv_pack(o, "rcv", receiver); 643 | kv_pack(o, "rekey", rekey_to); 644 | kv_pack(o, "selkey", selection_pk); 645 | kv_pack(o, "snd", sender); 646 | kv_pack(o, "type", tx_type); 647 | kv_pack(o, "votefst", vote_first); 648 | kv_pack(o, "votekd", vote_key_dilution); 649 | kv_pack(o, "votekey", vote_pk); 650 | kv_pack(o, "votelst", vote_last); 651 | kv_pack(o, "xaid", xfer_asset); 652 | 653 | 654 | return o; 655 | } 656 | 657 | template msgpack::packer& 658 | Transaction::pack(msgpack::packer& o) const; 659 | 660 | bytes Transaction::encode() const { 661 | std::stringstream buffer; 662 | msgpack::pack(buffer, *this); 663 | std::string const& s = buffer.str(); 664 | bytes data{s.begin(), s.end()}; 665 | return data; 666 | } 667 | 668 | std::ostream& 669 | operator<<(std::ostream& os, const Transaction& txn) { 670 | os << "{" << std::endl; 671 | os << " aamt: " << txn.asset_amount << std::endl; 672 | os << " aclose: " << txn.asset_close_to << std::endl; 673 | os << " afrz: " << txn.asset_frozen << std::endl; 674 | os << " amt: " << txn.amount << std::endl; 675 | // os << " apar: " << txn.asset_params << std::endl; 676 | os << " arcv: " << txn.asset_receiver << std::endl; 677 | os << " asnd: " << txn.asset_sender << std::endl; 678 | os << " caid: " << txn.config_asset << std::endl; 679 | os << " close: " << txn.close_to << std::endl; 680 | os << " fadd: " << txn.freeze_account << std::endl; 681 | os << " faid: " << txn.freeze_asset << std::endl; 682 | os << " fee: " << txn.fee << std::endl; 683 | os << " fv: " << txn.first_valid << std::endl; 684 | os << " gen: " << txn.genesis_id << std::endl; 685 | os << " gh: " << b64_encode(txn.genesis_hash) << std::endl; 686 | os << " grp: " << b64_encode(txn.group) << std::endl; 687 | os << " lv: " << txn.last_valid << std::endl; 688 | os << " lx: " << b64_encode(txn.lease) << std::endl; 689 | os << " nonpart: " << txn.nonparticipation << std::endl; 690 | os << " note: " << b64_encode(txn.note) << std::endl; 691 | os << " rcv: " << txn.receiver << std::endl; 692 | os << " rekey: " << txn.rekey_to << std::endl; 693 | os << " selkey: " << b64_encode(txn.selection_pk) << std::endl; 694 | os << " snd: " << txn.sender << std::endl; 695 | os << " type: " << txn.tx_type << std::endl; 696 | os << " votefst: " << txn.vote_first << std::endl; 697 | os << " votekd: " << txn.vote_key_dilution << std::endl; 698 | os << " votekey: " << b64_encode(txn.vote_pk) << std::endl; 699 | os << " votelst: " << txn.vote_last << std::endl; 700 | os << " xaid: " << txn.xfer_asset << std::endl; 701 | 702 | os << " apid: " << txn.application_id << std::endl; 703 | os << " apan: " << txn.on_complete << std::endl; 704 | // os << " apat: " << txn.accounts << std::endl; 705 | os << " apap: " << b64_encode(txn.approval_program) << std::endl; 706 | os << " apsu: " << b64_encode(txn.clear_state_program) << std::endl; 707 | // os << " apaa: " << txn.app_arguments << std::endl; 708 | // os << " apfa: " << txn.foreign_apps << std::endl; 709 | // os << " apas: " << txn.foreign_assets << std::endl; 710 | // os << " apgs: " << txn.globals << std::endl; 711 | // os << " apls: " << txn.locals << std::endl; 712 | os << "}" << std::endl; 713 | return os; 714 | } 715 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include "algorand.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "base.h" 9 | #include "mnemonic.h" 10 | 11 | 12 | void debug(std::string str, std::string file) { 13 | std::ofstream out(file); 14 | out << str; 15 | out.close(); 16 | } 17 | 18 | void base() { 19 | bytes hello = {'h', 'e', 'l', 'l', 'o'}; 20 | assert("aGVsbG8" == b64_encode(hello)); 21 | assert(hello == b64_decode(b64_encode(hello))); 22 | 23 | assert("NBSWY3DP" == b32_encode(hello)); 24 | assert(hello == b32_decode(b32_encode(hello))); 25 | 26 | std::vector encoded{1384, 1420, 1457, 55}; 27 | assert(encoded == b2048_encode(hello)); 28 | assert(hello == b2048_decode(b2048_encode(hello))); 29 | std::cout << "bases passed" << std::endl; 30 | } 31 | 32 | void algod_basics() { 33 | AlgodClient client; 34 | 35 | auto resp = client.genesis(); 36 | assert(resp["alloc"].IsArray()); 37 | 38 | assert(client.healthy()); 39 | auto metrics = client.metrics(); 40 | assert(metrics.find("ledger_accountsonlinetop_count")); 41 | assert(metrics.find("algod_ledger_round")); 42 | 43 | resp = client.status(); 44 | assert(resp.status == 200); 45 | assert(resp["last-round"].GetUint64() > 1); 46 | 47 | resp = client.supply(); 48 | assert(resp.status == 200); 49 | assert(resp["online-money"].GetUint64() > 1); 50 | assert(resp["total-money"].GetUint64() >= resp["online-money"].GetUint64()); 51 | 52 | resp = client.teal_compile("#pragma version 2\nint 1"); 53 | if (resp.status != 404) { 54 | // some algods don't have it configured on 55 | assert(resp.status == 200); 56 | assert(!strcmp(resp["hash"].GetString(), 57 | "YOE6C22GHCTKAN3HU4SE5PGIPN5UKXAJTXCQUPJ3KKF5HOAH646MKKCPDA")); 58 | assert(!strcmp(resp["result"].GetString(), "AiABASI=")); 59 | } 60 | 61 | resp = client.params(); 62 | assert(resp.status == 200); 63 | assert(resp["min-fee"].GetUint64() == 1000); 64 | 65 | resp = client.transaction_pending(); 66 | assert(resp.status == 200); 67 | resp = client.transaction_pending("junk"); 68 | assert(resp.status != 200); 69 | 70 | std::cout << "algod pass" << std::endl; 71 | } 72 | 73 | void account(std::string addr) { 74 | AlgodClient client; 75 | auto resp = client.account(addr); 76 | if (!resp.succeeded()) { 77 | std::cerr << resp["message"].GetString() << std::endl; 78 | return; 79 | } 80 | 81 | std::cout << *resp.json << std::endl; 82 | resp = client.transactions_pending(addr); 83 | assert(resp.succeeded()); 84 | } 85 | 86 | void application(std::string id) { 87 | AlgodClient client; 88 | auto resp = client.application(id); 89 | if (!resp.succeeded()) { 90 | std::cerr << resp["message"].GetString() << std::endl; 91 | return; 92 | } 93 | 94 | std::cout << *resp.json << std::endl; 95 | } 96 | 97 | void asset(std::string id) { 98 | AlgodClient client; 99 | auto resp = client.asset(id); 100 | if (!resp.succeeded()) { 101 | std::cerr << resp["message"].GetString() << std::endl; 102 | return; 103 | } 104 | 105 | std::cout << *resp.json << std::endl; 106 | } 107 | 108 | struct Holding { 109 | uint64_t amount; 110 | uint64_t assetId; 111 | Address creator; 112 | bool frozen; 113 | }; 114 | std::unique_ptr holding(std::string addr, std::string asa) { 115 | auto id = std::strtoull(asa.c_str(), 0, 10); 116 | AlgodClient client; 117 | auto resp = client.account(addr); 118 | if (!resp.succeeded()) { 119 | std::cerr << resp["message"].GetString() << std::endl; 120 | return 0; 121 | } 122 | auto& assets = resp["assets"]; 123 | for (auto asset = assets.Begin(); asset != assets.End(); asset++) { 124 | auto aid = (*asset)["asset-id"].GetUint64(); 125 | if (aid == id) 126 | return std::make_unique(Holding{ 127 | (*asset)["amount"].GetUint64(), 128 | (*asset)["asset-id"].GetUint64(), 129 | Address{(*asset)["creator"].GetString()}, 130 | (*asset)["is-frozen"].GetBool()}); 131 | } 132 | return 0; 133 | } 134 | 135 | bool opted_in(std::string addr, std::string asa) { 136 | std::unique_ptr h = holding(addr, asa); 137 | return h ? true : false; 138 | } 139 | 140 | bool frozen(std::string addr, std::string asa) { 141 | std::unique_ptr h = holding(addr, asa); 142 | return h->frozen; 143 | } 144 | 145 | void end_to_end() { 146 | AlgodClient client; 147 | auto resp = client.params(); 148 | assert(resp.status == 200); 149 | const auto& suggested = *resp.json; 150 | std::cout << suggested << std::endl; 151 | 152 | Account from{"LCKVRVM2MJ7RAJZKPAXUCEC4GZMYNTFMLHJTV2KF6UGNXUFQFIIMSXRVM4"}; 153 | std::cout << from.address << std::endl; 154 | 155 | auto keys = Account::generate_keys(); 156 | Account to{keys.first, keys.second}; 157 | std::cout << to.address << std::endl; 158 | 159 | Transaction t = Transaction::payment(from.public_key(), 160 | to.public_key(), 12345, {}, 161 | suggested["min-fee"].GetUint64(), 162 | suggested["last-round"].GetUint64()+1, 163 | suggested["last-round"].GetUint64()+1001, 164 | suggested["genesis-id"].GetString(), 165 | b64_decode(suggested["genesis-hash"].GetString()), 166 | {}, {}, {}); 167 | std::stringstream buffer; 168 | msgpack::pack(buffer, t); 169 | std::string s = buffer.str(); 170 | 171 | { 172 | std::ofstream ofs("pay.txn"); 173 | ofs << s; 174 | } 175 | 176 | //auto handle = msgpack::unpack(s.c_str(), s.length()); 177 | 178 | //PaymentTx t3 = handle.get().as(); 179 | //std::cout << b64_encode(t3.rcv) << std::endl; 180 | } 181 | 182 | static 183 | std::string to_hex(const bytes& in) { 184 | std::stringstream ss; 185 | ss << std::hex << std::setfill('0'); 186 | for (size_t i = 0; in.size() > i; i++) { 187 | ss << std::setw(2) << (int)(unsigned char)in[i] << ':'; 188 | } 189 | return ss.str(); 190 | } 191 | 192 | void address() { 193 | // Address alice("BX65TTOF324PU3IU5IXZ6VFUX3M33ZQ5NOHGLAEBHF5ECHKAWSQWOZXL4I"); 194 | // std::cout << alice << std::endl; 195 | // Address bob("TDCYVRHYNTAMZVEOIIGWQPU6GYVWOH5JGYBRFM63MALVKMJQXT66IY3RAE"); 196 | // std::cout << bob << std::endl; 197 | 198 | // Address valid("MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJI"); 199 | // Address invalid("MO2H6ZU47Q36GJ6GVHUKGEBEQINN7ZWVACMWZQGIYUOE3RBSRVYHV4ACJG"); 200 | 201 | bytes zero = bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 202 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 203 | Address by_key(zero); 204 | 205 | bytes one = bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; 207 | Address one_key(one); 208 | 209 | Address by_str("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"); 210 | 211 | assert(by_key.public_key == by_str.public_key); 212 | assert(by_key.as_string == by_str.as_string); 213 | assert(by_key == by_str); 214 | 215 | assert(one_key.public_key != by_str.public_key); 216 | assert(one_key.as_string != by_str.as_string); 217 | assert(one_key != by_str); 218 | std::cout << "address pass" << std::endl; 219 | } 220 | 221 | void mnemonic() { 222 | /* mnemonics are only about encoding seeds, not keys. */ 223 | bytes zero = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 224 | std::string mnemonic = R"(abandon abandon abandon abandon abandon abandon 225 | abandon abandon abandon abandon abandon abandon 226 | abandon abandon abandon abandon abandon abandon 227 | abandon abandon abandon abandon abandon abandon 228 | invest)"; 229 | assert(zero.size() == 32); 230 | assert(seed_from_mnemonic(mnemonic).size() == 32); 231 | assert(zero == seed_from_mnemonic(mnemonic)); 232 | 233 | auto zmnemonic = mnemonic_from_seed(zero); 234 | std::regex spaces("\\s+"); 235 | assert(zmnemonic == std::regex_replace(mnemonic, spaces, " ")); 236 | 237 | std::string non_zero = R"(abandon abandon abandon abandon abandon abandon 238 | abandon abandon abandon abandon abandon abandon 239 | abandon abandon abandon abandon abandon abandon 240 | abandon abandon abandon abandon zoo abandon 241 | mom)"; 242 | assert(zero != seed_from_mnemonic(non_zero)); 243 | 244 | std::cout << "mnemonic pass" << std::endl; 245 | } 246 | 247 | void account() { 248 | auto mnemonic = R"(base giraffe believe make tone transfer wrap attend 249 | typical dirt grocery distance outside horn also abstract 250 | slim ecology island alter daring equal boil absent 251 | carpet)"; 252 | Account account = Account::from_mnemonic(mnemonic); 253 | Address address("LCKVRVM2MJ7RAJZKPAXUCEC4GZMYNTFMLHJTV2KF6UGNXUFQFIIMSXRVM4"); 254 | 255 | assert(account.address == address); 256 | 257 | std::cout << "account pass" << std::endl; 258 | } 259 | 260 | void transaction() { 261 | Address address("7ZUECA7HFLZTXENRV24SHLU4AVPUTMTTDUFUBNBD64C73F3UHRTHAIOF6Q"); 262 | Address receiver("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"); 263 | auto gh = b64_decode("JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI="); 264 | Transaction pay = Transaction::payment(address, 265 | receiver, 1000, {}, 266 | 1000, 267 | 1, 100, 268 | "", gh, 269 | {}, bytes{1, 32, 200}, Address{}); 270 | 271 | auto golden = 272 | "iKNhbXTNA+ijZmVlzQPoomZ2AaJnaMQgJgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMe" 273 | "K+wRSaQ7dKibHZkpG5vdGXEAwEgyKNzbmTEIP5oQQPnKvM7kbGuuSOunAVfSbJzHQ" 274 | "tAtCP3Bf2XdDxmpHR5cGWjcGF5"; 275 | 276 | assert(golden == b64_encode(pay.encode())); 277 | 278 | std::cout << "transaction pass" << std::endl; 279 | } 280 | 281 | void signing() { 282 | auto mn = "advice pudding treat near rule blouse same whisper inner electric " 283 | "quit surface sunny dismiss leader blood seat clown cost exist " 284 | "hospital century reform able sponsor"; 285 | Account acct = Account::from_mnemonic(mn); 286 | Address to{"PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI"}; 287 | auto fee = 1176; // make an interface for fee calculation 288 | auto first_round = 12466; 289 | auto last_round = 13466; 290 | auto gh = b64_decode("JgsgCaCTqIaLeVhyL6XlRu3n7Rfk2FxMeK+wRSaQ7dI="); 291 | auto gen_id = "devnet-v33.0"; 292 | auto note = b64_decode("6gAVR0Nsv5Y="); 293 | Address close{"IDUTJEUIEVSMXTU4LGTJWZ2UE2E6TIODUKU6UW3FU3UKIQQ77RLUBBBFLA"}; 294 | auto amount = 1000; 295 | Transaction pay = Transaction::payment(acct.address, 296 | to, amount, close, 297 | fee, 298 | first_round, last_round, 299 | gen_id, gh, 300 | {}, note, {}); 301 | SignedTransaction stxn = pay.sign(acct); 302 | 303 | auto golden = 304 | "gqNzaWfEQPhUAZ3xkDDcc8FvOVo6UinzmKBCqs0woYSfodlmBMfQvGbeUx3Srxy3d" 305 | "yJDzv7rLm26BRv9FnL2/AuT7NYfiAWjdHhui6NhbXTNA+ilY2xvc2XEIEDpNJKIJW" 306 | "TLzpxZpptnVCaJ6aHDoqnqW2Wm6KRCH/xXo2ZlZc0EmKJmds0wsqNnZW6sZGV2bmV" 307 | "0LXYzMy4womdoxCAmCyAJoJOohot5WHIvpeVG7eftF+TYXEx4r7BFJpDt0qJsds00" 308 | "mqRub3RlxAjqABVHQ2y/lqNyY3bEIHts4k/rW6zAsWTinCIsV/X2PcOH1DkEglhBH" 309 | "F/hD3wCo3NuZMQg5/D4TQaBHfnzHI2HixFV9GcdUaGFwgCQhmf0SVhwaKGkdHlwZa" 310 | "NwYXk"; 311 | 312 | assert(golden == b64_encode(stxn.encode())); 313 | std::cout << "signing pass" << std::endl; 314 | } 315 | 316 | void logicsig() { 317 | bytes program{0x01, 0x20, 0x01, 0x01, 0x22}; // int 1 318 | Address hash("6Z3C3LDVWGMX23BMSYMANACQOSINPFIRF77H7N3AWJZYV6OH6GWTJKVMXY"); 319 | auto public_key = hash.public_key; 320 | 321 | LogicSig lsig(program); 322 | // assert(lsig.verify(public_key)) 323 | // assert(lsig.address() == hash) 324 | 325 | std::vector args{{0x01, 0x02, 0x03}, {0x04, 0x05, 0x06}}; 326 | lsig = LogicSig(program, args); 327 | // assert(lsig.verify(public_key)) 328 | 329 | 330 | Address from("47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU"); 331 | Address to("PNWOET7LLOWMBMLE4KOCELCX6X3D3Q4H2Q4QJASYIEOF7YIPPQBG3YQ5YI"); 332 | 333 | std::string mn = "advice pudding treat near rule blouse same whisper inner " 334 | "electric quit surface sunny dismiss leader blood seat clown " 335 | "cost exist hospital century reform able sponsor"; 336 | auto fee = 1000; 337 | auto amount = 2000; 338 | auto fv = 2063137; 339 | 340 | auto gh = b64_decode("sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E="); 341 | auto note = b64_decode("8xMCTuLQ810="); 342 | 343 | Transaction pay = Transaction::payment(from, 344 | to, amount, {}, 345 | fee, 346 | fv, fv+1000, 347 | "devnet-v1.0", gh, 348 | {}, note, {}); 349 | auto golden = 350 | "gqRsc2lng6NhcmeSxAMxMjPEAzQ1NqFsxAUBIAEBIqNzaWfEQE6HXaI5K0lcq50o/" 351 | "y3bWOYsyw9TLi/oorZB4xaNdn1Z14351u2f6JTON478fl+JhIP4HNRRAIh/I8EWXB" 352 | "PpJQ2jdHhuiqNhbXTNB9CjZmVlzQPoomZ2zgAfeyGjZ2Vuq2Rldm5ldC12MS4womd" 353 | "oxCCwLc/t7ZJ1uookrS1uIJ0r211Klt7pd4IYp2g3OaWPQaJsds4AH38JpG5vdGXE" 354 | "CPMTAk7i0PNdo3JjdsQge2ziT+tbrMCxZOKcIixX9fY9w4fUOQSCWEEcX+EPfAKjc" 355 | "25kxCDn8PhNBoEd+fMcjYeLEVX0Zx1RoYXCAJCGZ/RJWHBooaR0eXBlo3BheQ"; 356 | 357 | args = {{'1','2','3'}, {'4','5','6'}}; 358 | auto acct = Account::from_mnemonic(mn); 359 | lsig = LogicSig(program, args); 360 | auto lstx = pay.sign(lsig.sign(acct)); 361 | 362 | assert(golden == b64_encode(lstx.encode())); 363 | 364 | std::cout << "logicsig pass" << std::endl; 365 | } 366 | 367 | void multisig() { 368 | 369 | //These mnemonics generate the msig_address below. 370 | //Only use this on the testnet 371 | 372 | auto mnemonic1 = R"(base giraffe believe make tone transfer wrap attend 373 | typical dirt grocery distance outside horn also abstract 374 | slim ecology island alter daring equal boil absent 375 | carpet)"; 376 | 377 | auto mnemonic2 = R"(use animal lonely tragic style wealth uniform poverty 378 | idle sail rice dutch patient sport start shine 379 | stem path client plunge mutual achieve border absent 380 | aspect)"; 381 | 382 | //Txn Parameters used to generate transaction and compare against 383 | //known SDK implementation 384 | const auto fee = 1000; 385 | const auto amount = 12345; 386 | const auto fv = 2063137; 387 | const auto gh = b64_decode("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="); 388 | uint8_t thr = 2; 389 | //Compare C++ msig adress against golden address generated with the 390 | //Python SDK using the mnemonics above 391 | const std::string msig_address = "MZVMI5WFABIGZKZEBEWIWEEFQCUE4HVOBUTTYFOYGXGZVZAJNFD6PVQHX4"; 392 | //Golden Signed Txn string to compare the C++ Multisig implementation against 393 | //The Signed Txn string was generated using the Python SDK 394 | //This is to validate checking that a msig txn that has 2 accounts and threshold set to 2 395 | const auto golden = "gqRtc2lng6ZzdWJzaWeSgqJwa8QgWJVY1ZpifxAnKngvQRBcNlmGzKxZ0zrpRfUM29CwKhChc8RAlKXNe/" 396 | "LoBO/5dYck3nFxCIQEMi59iXNQw3I97aZom17puDaU1PV4YfZCKHshh3B8bpTrthULL3RJNaBHFctMCYKi" 397 | "cGvEIO8aieWQj80Zyht42hSihjo/HIhlwi3A1lS0vVXSsIDKoXPEQDheWFHzJ7PM3SUUMY0lfue1ZVyKyOS2" 398 | "kUJDnHdJ0sf/0SxyqyOnD1M6GffezoqCPSfSh3V9pXVg238PWHjxqQWjdGhyAqF2AaN0eG6Jo2FtdM0wOaNmZ" 399 | "WXNA+iiZnbOAB97IaNnZW6sdGVzdG5ldC12MS4womdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk" 400 | "6IqJsds4AH38Jo3JjdsQgFfpW8g15Q1QuU2BB+kc3hB79ozYEOuARp4KQ9GH8ESSjc25kxCBmasR2xQBQbKskC" 401 | "SyLEIWAqE4erg0nPBXYNc2a5AlpR6R0eXBlo3BheQ"; 402 | 403 | std::vector
addresses; 404 | std::vector accounts; 405 | accounts.push_back(Account::from_mnemonic(mnemonic1)); 406 | accounts.push_back(Account::from_mnemonic(mnemonic2)); 407 | addresses.push_back(accounts[0].public_key()); 408 | addresses.push_back(accounts[1].public_key()); 409 | MultiSig msig{addresses, thr}; 410 | 411 | //Verify Multisig Public Address is expected Address 412 | assert(msig_address == msig.address()); 413 | 414 | //Verify Multisig Txn can be signed and sent to the "to" address 415 | Address to{"CX5FN4QNPFBVILSTMBA7URZXQQPP3IZWAQ5OAENHQKIPIYP4CESAQ77PJA"}; 416 | 417 | Transaction t = Transaction::payment(msig.address(), 418 | to, 12345, {}, 419 | fee, 420 | fv, 421 | fv+1000, 422 | "testnet-v1.0", 423 | gh, 424 | {}, {}, {} 425 | ); 426 | 427 | //Create signed Multisig Txn, signed by accounts 428 | auto smsig = t.sign(msig, accounts); 429 | //Compare signed, encoded, txn string against 430 | //Golden String generated with the Python SDK 431 | assert(golden == b64_encode(smsig.encode())); 432 | 433 | 434 | /*** Test partial MSIG. 3 Address, signature threshold 2 ***/ 435 | //Golden Signed Txn string to compare the C++ Multisig implementation against 436 | //The Signed Txn string was generated using the Python SDK 437 | //This is to validate checking that a msig txn that has 2 accounts and threshold set to 2 438 | const auto golden_partial = "gqRtc2lng6ZzdWJzaWeTgqJwa8QgWJVY1ZpifxAnKngvQRBcNlmGzKxZ0zrpRfUM2" 439 | "9CwKhChc8RA4Y4ADq8wac/03+cDOT0seLGsbP22LOtsfTaIG31LfJIFE2M02j71GR" 440 | "vFE10W3E7hTKAqVK2DCpIJZC2FpNgNA4KicGvEIO8aieWQj80Zyht42hSihjo/HIhl" 441 | "wi3A1lS0vVXSsIDKoXPEQCeuZ6FWvPUBh+aBfjFoYuzQknXAx4HMxh5p5gXgbjDw99" 442 | "p5t606gFr74RLs95UoKqqyEjlXj3u6N4sGaXqpcQWBonBrxCAV+lbyDXlDVC5TYEH6" 443 | "RzeEHv2jNgQ64BGngpD0YfwRJKN0aHICoXYBo3R4bomjYW10zTA5o2ZlZc0D6KJmds" 444 | "4AH3sho2dlbqx0ZXN0bmV0LXYxLjCiZ2jEIEhjtRiks8hOyBDyLU8QgcsPcfBZp6wg" 445 | "3sYvf3DlCToiomx2zgAffwmjcmN2xCAV+lbyDXlDVC5TYEH6RzeEHv2jNgQ64BGngp" 446 | "D0YfwRJKNzbmTEIDw/L2BRPw5UV+lNcm7T5QyVsu/+iemhRSSMgsgVjWxwpHR5cGWjcGF5"; 447 | 448 | //Compare C++ msig adress against golden address generated with the 449 | //Python SDK using the mnemonics above 450 | const std::string msig_address_partial = "HQ7S6YCRH4HFIV7JJVZG5U7FBSK3F376RHU2CRJERSBMQFMNNRYGPBZRMQ"; 451 | 452 | addresses.push_back(to); 453 | MultiSig msig_partial{addresses, thr}; 454 | //Verify Multisig Public Address is expected Address 455 | assert(msig_address_partial == msig_partial.address()); 456 | 457 | //Verify Multisig Txn can be signed and sent to the "to" address 458 | Transaction t_partial = Transaction::payment(msig_partial.address(), 459 | to, 12345, {}, 460 | fee, 461 | fv, 462 | fv+1000, 463 | "testnet-v1.0", 464 | gh, 465 | {}, {}, {} 466 | ); 467 | 468 | //Create signed Multisig Txn using a collection of secret_keys. 469 | //Note this a collection of 2 secret keys 3 can be used with this 470 | //account but the threshold is only 2 471 | auto smsig_partial = t_partial.sign(msig_partial, accounts); 472 | 473 | //Compare signed, encoded, txn string against 474 | //Golden String generated with the Python SDK 475 | assert(golden_partial == b64_encode(smsig_partial.encode())); 476 | 477 | std::cout << "multisig pass" << std::endl; 478 | } 479 | 480 | void indexer_basics() { 481 | IndexerClient client; 482 | auto resp = client.accounts(); 483 | std::cout << resp << std::endl; 484 | Address addr("CKNVTB7DPRZO3MB64RQFPZIHCHCC4GBSTAAJKVQ2SLYNKVYPK4EJFBCQKM"); 485 | resp = client.account(addr, 2); 486 | std::cout << resp << std::endl; 487 | resp = client.block(2); 488 | std::cout << resp << std::endl; 489 | std::cout << "indexer pass" << std::endl; 490 | } 491 | 492 | void sign_send_save(std::string name, const Transaction& txn, const Account& signer, const AlgodClient& client) { 493 | { 494 | std::stringstream buffer; 495 | msgpack::pack(buffer, txn); 496 | std::ofstream ofs(name+".txn"); 497 | ofs << buffer.str(); 498 | } 499 | 500 | SignedTransaction stxn = txn.sign(signer); 501 | 502 | std::stringstream sbuffer; 503 | msgpack::pack(sbuffer, stxn); 504 | std::ofstream sofs(name+".stxn"); 505 | sofs << sbuffer.str(); 506 | auto resp = client.submit(sbuffer.str()); 507 | std::cout << resp << std::endl; 508 | assert(resp.status == 200); 509 | } 510 | 511 | int main(int argc, char** argv) { 512 | if (argc > 2) { 513 | int arg = 1; 514 | auto cmd = std::string(argv[arg++]); 515 | if (cmd == "account") { 516 | account(argv[arg++]); 517 | } 518 | if (cmd == "asset" || cmd == "asa") { 519 | asset(argv[arg++]); 520 | } 521 | if (cmd == "app" || cmd == "application") { 522 | application(argv[arg++]); 523 | } 524 | if (cmd == "opted-in") { 525 | assert(argc == 4); 526 | std::cout << opted_in(argv[arg], argv[arg+1]) << std::endl; 527 | arg += 2; 528 | } 529 | if (cmd == "asset-balance") { 530 | assert(argc == 4); 531 | std::unique_ptr h = holding(argv[arg], argv[arg+1]); 532 | arg += 2; 533 | std::cout << h->amount << std::endl; 534 | } 535 | if (cmd == "mnemonic") { 536 | Account acct = Account::from_mnemonic(argv[arg++]); 537 | std::cout << acct.address << std::endl; 538 | account(acct.address.as_string); 539 | } 540 | 541 | if (cmd == "pay") { 542 | AlgodClient client; 543 | auto resp = client.params(); 544 | assert(resp.status == 200); 545 | const auto& suggested = *resp.json; 546 | 547 | Account snd = Account::from_mnemonic(argv[arg++]); 548 | Account rcv = Account(argv[arg++]); 549 | uint64_t amt = std::stol(argv[arg++]); 550 | Transaction pay = 551 | Transaction::payment(snd.public_key(), rcv.public_key(), 552 | amt, {}, 553 | suggested["min-fee"].GetUint64(), 554 | suggested["last-round"].GetUint64()+1, 555 | suggested["last-round"].GetUint64()+1001, 556 | suggested["genesis-id"].GetString(), 557 | b64_decode(suggested["genesis-hash"].GetString()), 558 | {}, {}, {}); 559 | 560 | sign_send_save("pay", pay, snd, client); 561 | } 562 | 563 | if (cmd == "axfer") { 564 | AlgodClient client; 565 | auto resp = client.params(); 566 | assert(resp.status == 200); 567 | const auto& suggested = *resp.json; 568 | 569 | Account snd = Account::from_mnemonic(argv[arg++]); 570 | Account rcv = Account(argv[arg++]); 571 | uint64_t asset_id = std::stol(argv[arg++]); 572 | uint64_t asset_amount = std::stol(argv[arg++]); 573 | Transaction axfer = 574 | Transaction::asset_transfer(snd.public_key(), 575 | 576 | asset_id, 577 | asset_amount, 578 | {}, // asset_sender 579 | rcv.public_key(), 580 | {}, // asset_close_to, 581 | 582 | suggested["min-fee"].GetUint64(), 583 | suggested["last-round"].GetUint64()+1, 584 | suggested["last-round"].GetUint64()+1001, 585 | suggested["genesis-id"].GetString(), 586 | b64_decode(suggested["genesis-hash"].GetString()), 587 | {}, {}, {}); 588 | 589 | sign_send_save("axfer", axfer, snd, client); 590 | } 591 | 592 | if (cmd == "call") { 593 | AlgodClient client; 594 | auto resp = client.params(); 595 | assert(resp.status == 200); 596 | const auto& suggested = *resp.json; 597 | 598 | Account snd = Account::from_mnemonic(argv[arg++]); 599 | uint64_t app = std::stol(argv[arg++]); 600 | Transaction call = 601 | Transaction::app_call(snd.public_key(), 602 | app, 603 | 0, // on complete 604 | {}, // accounts 605 | {}, {}, // approval, clear 606 | {}, // arguments 607 | {}, // apps 608 | {}, // assets 609 | {}, {}, // globals locals 610 | suggested["min-fee"].GetUint64(), 611 | suggested["last-round"].GetUint64()+1, 612 | suggested["last-round"].GetUint64()+1001, 613 | suggested["genesis-id"].GetString(), 614 | b64_decode(suggested["genesis-hash"].GetString()), 615 | {}, {}, {}); 616 | 617 | sign_send_save("call", call, snd, client); 618 | } 619 | 620 | } else { 621 | base(); 622 | address(); 623 | mnemonic(); 624 | account(); 625 | transaction(); 626 | signing(); 627 | logicsig(); 628 | multisig(); 629 | algod_basics(); 630 | indexer_basics(); 631 | } 632 | } 633 | -------------------------------------------------------------------------------- /mnemonic.cpp: -------------------------------------------------------------------------------- 1 | #include "mnemonic.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "base.h" 13 | 14 | extern std::map word_map; 15 | extern std::vector word_vec; 16 | 17 | std::map make_word_map(std::string s) { 18 | std::map map; 19 | 20 | auto iss = std::istringstream{s}; 21 | std::string word; 22 | 23 | int i = 0; 24 | while (iss >> word) { 25 | map[word] = i; 26 | i++; 27 | } 28 | 29 | return map; 30 | } 31 | 32 | std::vector make_word_vector(std::string s) { 33 | std::vector vec; 34 | 35 | auto iss = std::istringstream{s}; 36 | std::string word; 37 | 38 | while (iss >> word) { 39 | vec.push_back(word); 40 | } 41 | 42 | return vec; 43 | } 44 | 45 | int word_lookup(std::string word) { 46 | auto entry = word_map.find(word); 47 | if (entry == word_map.end()) 48 | throw std::invalid_argument(word.c_str()); 49 | return entry->second; 50 | } 51 | 52 | uint16_t checksum(bytes b) { 53 | auto hash = sha512_256(b); 54 | auto ints = b2048_encode(bytes{hash.begin(), hash.begin()+2}); 55 | return ints[0]; 56 | } 57 | 58 | std::string mnemonic_from_seed(bytes seed) { 59 | std::string mnemonic; 60 | for (const int i : b2048_encode(seed)) { 61 | mnemonic.append(word_vec[i]+" "); 62 | } 63 | mnemonic.append(word_vec[checksum(seed)]); 64 | return mnemonic; 65 | } 66 | 67 | 68 | 69 | 70 | bytes seed_from_mnemonic(std::string mnemonic) { 71 | std::vector words = make_word_vector(mnemonic); 72 | auto checkword = words.back(); 73 | words.pop_back(); 74 | if (words.size() != 24) 75 | throw std::invalid_argument(std::to_string(words.size()+1) + " words"); 76 | auto checkval = word_lookup(checkword); 77 | bytes seed; 78 | // this part is base conversion, 11 to 8. see base.cpp 79 | unsigned val = 0; 80 | int bits = 0; 81 | for (const auto& word : words) { 82 | val |= word_lookup(word) << bits; 83 | bits += 11; 84 | while (bits >= 8) { 85 | seed.push_back(val & 0xFF); 86 | val >>= 8; 87 | bits -= 8; 88 | } 89 | } 90 | if (bits > 0) 91 | seed.push_back(val & 0xFF); 92 | 93 | assert(!*(seed.end()-1)); // last byte is supposed to be zero 94 | seed.pop_back(); // and unneeded 95 | auto check = checksum(seed); 96 | if (check != checkval) { 97 | throw std::invalid_argument(word_vec[check] + " != " + word_vec[checkval]); 98 | } 99 | return seed; 100 | } 101 | 102 | // libsodium/nacl does not implement this truncated form of sha512, 103 | // which is NOT simply the first 256 bits of the sha512 hash. 104 | bytes sha512_256(bytes input) { 105 | unsigned char result[32]; 106 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); 107 | bool success = ctx != NULL 108 | && EVP_DigestInit_ex(ctx, EVP_sha512_256(), NULL) == 1 109 | && EVP_DigestUpdate(ctx, input.data(), input.size()) == 1 110 | && EVP_DigestFinal_ex(ctx, result, NULL) == 1; 111 | if (! success) throw std::runtime_error("openssl sha512_256 EVP_Digest runtime error"); 112 | EVP_MD_CTX_free(ctx); 113 | return bytes(result, result+sizeof(result)); 114 | } 115 | 116 | // This wordlist was taken from https://git.io/fhZUO 117 | const std::string english = R"( 118 | abandon 119 | ability 120 | able 121 | about 122 | above 123 | absent 124 | absorb 125 | abstract 126 | absurd 127 | abuse 128 | access 129 | accident 130 | account 131 | accuse 132 | achieve 133 | acid 134 | acoustic 135 | acquire 136 | across 137 | act 138 | action 139 | actor 140 | actress 141 | actual 142 | adapt 143 | add 144 | addict 145 | address 146 | adjust 147 | admit 148 | adult 149 | advance 150 | advice 151 | aerobic 152 | affair 153 | afford 154 | afraid 155 | again 156 | age 157 | agent 158 | agree 159 | ahead 160 | aim 161 | air 162 | airport 163 | aisle 164 | alarm 165 | album 166 | alcohol 167 | alert 168 | alien 169 | all 170 | alley 171 | allow 172 | almost 173 | alone 174 | alpha 175 | already 176 | also 177 | alter 178 | always 179 | amateur 180 | amazing 181 | among 182 | amount 183 | amused 184 | analyst 185 | anchor 186 | ancient 187 | anger 188 | angle 189 | angry 190 | animal 191 | ankle 192 | announce 193 | annual 194 | another 195 | answer 196 | antenna 197 | antique 198 | anxiety 199 | any 200 | apart 201 | apology 202 | appear 203 | apple 204 | approve 205 | april 206 | arch 207 | arctic 208 | area 209 | arena 210 | argue 211 | arm 212 | armed 213 | armor 214 | army 215 | around 216 | arrange 217 | arrest 218 | arrive 219 | arrow 220 | art 221 | artefact 222 | artist 223 | artwork 224 | ask 225 | aspect 226 | assault 227 | asset 228 | assist 229 | assume 230 | asthma 231 | athlete 232 | atom 233 | attack 234 | attend 235 | attitude 236 | attract 237 | auction 238 | audit 239 | august 240 | aunt 241 | author 242 | auto 243 | autumn 244 | average 245 | avocado 246 | avoid 247 | awake 248 | aware 249 | away 250 | awesome 251 | awful 252 | awkward 253 | axis 254 | baby 255 | bachelor 256 | bacon 257 | badge 258 | bag 259 | balance 260 | balcony 261 | ball 262 | bamboo 263 | banana 264 | banner 265 | bar 266 | barely 267 | bargain 268 | barrel 269 | base 270 | basic 271 | basket 272 | battle 273 | beach 274 | bean 275 | beauty 276 | because 277 | become 278 | beef 279 | before 280 | begin 281 | behave 282 | behind 283 | believe 284 | below 285 | belt 286 | bench 287 | benefit 288 | best 289 | betray 290 | better 291 | between 292 | beyond 293 | bicycle 294 | bid 295 | bike 296 | bind 297 | biology 298 | bird 299 | birth 300 | bitter 301 | black 302 | blade 303 | blame 304 | blanket 305 | blast 306 | bleak 307 | bless 308 | blind 309 | blood 310 | blossom 311 | blouse 312 | blue 313 | blur 314 | blush 315 | board 316 | boat 317 | body 318 | boil 319 | bomb 320 | bone 321 | bonus 322 | book 323 | boost 324 | border 325 | boring 326 | borrow 327 | boss 328 | bottom 329 | bounce 330 | box 331 | boy 332 | bracket 333 | brain 334 | brand 335 | brass 336 | brave 337 | bread 338 | breeze 339 | brick 340 | bridge 341 | brief 342 | bright 343 | bring 344 | brisk 345 | broccoli 346 | broken 347 | bronze 348 | broom 349 | brother 350 | brown 351 | brush 352 | bubble 353 | buddy 354 | budget 355 | buffalo 356 | build 357 | bulb 358 | bulk 359 | bullet 360 | bundle 361 | bunker 362 | burden 363 | burger 364 | burst 365 | bus 366 | business 367 | busy 368 | butter 369 | buyer 370 | buzz 371 | cabbage 372 | cabin 373 | cable 374 | cactus 375 | cage 376 | cake 377 | call 378 | calm 379 | camera 380 | camp 381 | can 382 | canal 383 | cancel 384 | candy 385 | cannon 386 | canoe 387 | canvas 388 | canyon 389 | capable 390 | capital 391 | captain 392 | car 393 | carbon 394 | card 395 | cargo 396 | carpet 397 | carry 398 | cart 399 | case 400 | cash 401 | casino 402 | castle 403 | casual 404 | cat 405 | catalog 406 | catch 407 | category 408 | cattle 409 | caught 410 | cause 411 | caution 412 | cave 413 | ceiling 414 | celery 415 | cement 416 | census 417 | century 418 | cereal 419 | certain 420 | chair 421 | chalk 422 | champion 423 | change 424 | chaos 425 | chapter 426 | charge 427 | chase 428 | chat 429 | cheap 430 | check 431 | cheese 432 | chef 433 | cherry 434 | chest 435 | chicken 436 | chief 437 | child 438 | chimney 439 | choice 440 | choose 441 | chronic 442 | chuckle 443 | chunk 444 | churn 445 | cigar 446 | cinnamon 447 | circle 448 | citizen 449 | city 450 | civil 451 | claim 452 | clap 453 | clarify 454 | claw 455 | clay 456 | clean 457 | clerk 458 | clever 459 | click 460 | client 461 | cliff 462 | climb 463 | clinic 464 | clip 465 | clock 466 | clog 467 | close 468 | cloth 469 | cloud 470 | clown 471 | club 472 | clump 473 | cluster 474 | clutch 475 | coach 476 | coast 477 | coconut 478 | code 479 | coffee 480 | coil 481 | coin 482 | collect 483 | color 484 | column 485 | combine 486 | come 487 | comfort 488 | comic 489 | common 490 | company 491 | concert 492 | conduct 493 | confirm 494 | congress 495 | connect 496 | consider 497 | control 498 | convince 499 | cook 500 | cool 501 | copper 502 | copy 503 | coral 504 | core 505 | corn 506 | correct 507 | cost 508 | cotton 509 | couch 510 | country 511 | couple 512 | course 513 | cousin 514 | cover 515 | coyote 516 | crack 517 | cradle 518 | craft 519 | cram 520 | crane 521 | crash 522 | crater 523 | crawl 524 | crazy 525 | cream 526 | credit 527 | creek 528 | crew 529 | cricket 530 | crime 531 | crisp 532 | critic 533 | crop 534 | cross 535 | crouch 536 | crowd 537 | crucial 538 | cruel 539 | cruise 540 | crumble 541 | crunch 542 | crush 543 | cry 544 | crystal 545 | cube 546 | culture 547 | cup 548 | cupboard 549 | curious 550 | current 551 | curtain 552 | curve 553 | cushion 554 | custom 555 | cute 556 | cycle 557 | dad 558 | damage 559 | damp 560 | dance 561 | danger 562 | daring 563 | dash 564 | daughter 565 | dawn 566 | day 567 | deal 568 | debate 569 | debris 570 | decade 571 | december 572 | decide 573 | decline 574 | decorate 575 | decrease 576 | deer 577 | defense 578 | define 579 | defy 580 | degree 581 | delay 582 | deliver 583 | demand 584 | demise 585 | denial 586 | dentist 587 | deny 588 | depart 589 | depend 590 | deposit 591 | depth 592 | deputy 593 | derive 594 | describe 595 | desert 596 | design 597 | desk 598 | despair 599 | destroy 600 | detail 601 | detect 602 | develop 603 | device 604 | devote 605 | diagram 606 | dial 607 | diamond 608 | diary 609 | dice 610 | diesel 611 | diet 612 | differ 613 | digital 614 | dignity 615 | dilemma 616 | dinner 617 | dinosaur 618 | direct 619 | dirt 620 | disagree 621 | discover 622 | disease 623 | dish 624 | dismiss 625 | disorder 626 | display 627 | distance 628 | divert 629 | divide 630 | divorce 631 | dizzy 632 | doctor 633 | document 634 | dog 635 | doll 636 | dolphin 637 | domain 638 | donate 639 | donkey 640 | donor 641 | door 642 | dose 643 | double 644 | dove 645 | draft 646 | dragon 647 | drama 648 | drastic 649 | draw 650 | dream 651 | dress 652 | drift 653 | drill 654 | drink 655 | drip 656 | drive 657 | drop 658 | drum 659 | dry 660 | duck 661 | dumb 662 | dune 663 | during 664 | dust 665 | dutch 666 | duty 667 | dwarf 668 | dynamic 669 | eager 670 | eagle 671 | early 672 | earn 673 | earth 674 | easily 675 | east 676 | easy 677 | echo 678 | ecology 679 | economy 680 | edge 681 | edit 682 | educate 683 | effort 684 | egg 685 | eight 686 | either 687 | elbow 688 | elder 689 | electric 690 | elegant 691 | element 692 | elephant 693 | elevator 694 | elite 695 | else 696 | embark 697 | embody 698 | embrace 699 | emerge 700 | emotion 701 | employ 702 | empower 703 | empty 704 | enable 705 | enact 706 | end 707 | endless 708 | endorse 709 | enemy 710 | energy 711 | enforce 712 | engage 713 | engine 714 | enhance 715 | enjoy 716 | enlist 717 | enough 718 | enrich 719 | enroll 720 | ensure 721 | enter 722 | entire 723 | entry 724 | envelope 725 | episode 726 | equal 727 | equip 728 | era 729 | erase 730 | erode 731 | erosion 732 | error 733 | erupt 734 | escape 735 | essay 736 | essence 737 | estate 738 | eternal 739 | ethics 740 | evidence 741 | evil 742 | evoke 743 | evolve 744 | exact 745 | example 746 | excess 747 | exchange 748 | excite 749 | exclude 750 | excuse 751 | execute 752 | exercise 753 | exhaust 754 | exhibit 755 | exile 756 | exist 757 | exit 758 | exotic 759 | expand 760 | expect 761 | expire 762 | explain 763 | expose 764 | express 765 | extend 766 | extra 767 | eye 768 | eyebrow 769 | fabric 770 | face 771 | faculty 772 | fade 773 | faint 774 | faith 775 | fall 776 | false 777 | fame 778 | family 779 | famous 780 | fan 781 | fancy 782 | fantasy 783 | farm 784 | fashion 785 | fat 786 | fatal 787 | father 788 | fatigue 789 | fault 790 | favorite 791 | feature 792 | february 793 | federal 794 | fee 795 | feed 796 | feel 797 | female 798 | fence 799 | festival 800 | fetch 801 | fever 802 | few 803 | fiber 804 | fiction 805 | field 806 | figure 807 | file 808 | film 809 | filter 810 | final 811 | find 812 | fine 813 | finger 814 | finish 815 | fire 816 | firm 817 | first 818 | fiscal 819 | fish 820 | fit 821 | fitness 822 | fix 823 | flag 824 | flame 825 | flash 826 | flat 827 | flavor 828 | flee 829 | flight 830 | flip 831 | float 832 | flock 833 | floor 834 | flower 835 | fluid 836 | flush 837 | fly 838 | foam 839 | focus 840 | fog 841 | foil 842 | fold 843 | follow 844 | food 845 | foot 846 | force 847 | forest 848 | forget 849 | fork 850 | fortune 851 | forum 852 | forward 853 | fossil 854 | foster 855 | found 856 | fox 857 | fragile 858 | frame 859 | frequent 860 | fresh 861 | friend 862 | fringe 863 | frog 864 | front 865 | frost 866 | frown 867 | frozen 868 | fruit 869 | fuel 870 | fun 871 | funny 872 | furnace 873 | fury 874 | future 875 | gadget 876 | gain 877 | galaxy 878 | gallery 879 | game 880 | gap 881 | garage 882 | garbage 883 | garden 884 | garlic 885 | garment 886 | gas 887 | gasp 888 | gate 889 | gather 890 | gauge 891 | gaze 892 | general 893 | genius 894 | genre 895 | gentle 896 | genuine 897 | gesture 898 | ghost 899 | giant 900 | gift 901 | giggle 902 | ginger 903 | giraffe 904 | girl 905 | give 906 | glad 907 | glance 908 | glare 909 | glass 910 | glide 911 | glimpse 912 | globe 913 | gloom 914 | glory 915 | glove 916 | glow 917 | glue 918 | goat 919 | goddess 920 | gold 921 | good 922 | goose 923 | gorilla 924 | gospel 925 | gossip 926 | govern 927 | gown 928 | grab 929 | grace 930 | grain 931 | grant 932 | grape 933 | grass 934 | gravity 935 | great 936 | green 937 | grid 938 | grief 939 | grit 940 | grocery 941 | group 942 | grow 943 | grunt 944 | guard 945 | guess 946 | guide 947 | guilt 948 | guitar 949 | gun 950 | gym 951 | habit 952 | hair 953 | half 954 | hammer 955 | hamster 956 | hand 957 | happy 958 | harbor 959 | hard 960 | harsh 961 | harvest 962 | hat 963 | have 964 | hawk 965 | hazard 966 | head 967 | health 968 | heart 969 | heavy 970 | hedgehog 971 | height 972 | hello 973 | helmet 974 | help 975 | hen 976 | hero 977 | hidden 978 | high 979 | hill 980 | hint 981 | hip 982 | hire 983 | history 984 | hobby 985 | hockey 986 | hold 987 | hole 988 | holiday 989 | hollow 990 | home 991 | honey 992 | hood 993 | hope 994 | horn 995 | horror 996 | horse 997 | hospital 998 | host 999 | hotel 1000 | hour 1001 | hover 1002 | hub 1003 | huge 1004 | human 1005 | humble 1006 | humor 1007 | hundred 1008 | hungry 1009 | hunt 1010 | hurdle 1011 | hurry 1012 | hurt 1013 | husband 1014 | hybrid 1015 | ice 1016 | icon 1017 | idea 1018 | identify 1019 | idle 1020 | ignore 1021 | ill 1022 | illegal 1023 | illness 1024 | image 1025 | imitate 1026 | immense 1027 | immune 1028 | impact 1029 | impose 1030 | improve 1031 | impulse 1032 | inch 1033 | include 1034 | income 1035 | increase 1036 | index 1037 | indicate 1038 | indoor 1039 | industry 1040 | infant 1041 | inflict 1042 | inform 1043 | inhale 1044 | inherit 1045 | initial 1046 | inject 1047 | injury 1048 | inmate 1049 | inner 1050 | innocent 1051 | input 1052 | inquiry 1053 | insane 1054 | insect 1055 | inside 1056 | inspire 1057 | install 1058 | intact 1059 | interest 1060 | into 1061 | invest 1062 | invite 1063 | involve 1064 | iron 1065 | island 1066 | isolate 1067 | issue 1068 | item 1069 | ivory 1070 | jacket 1071 | jaguar 1072 | jar 1073 | jazz 1074 | jealous 1075 | jeans 1076 | jelly 1077 | jewel 1078 | job 1079 | join 1080 | joke 1081 | journey 1082 | joy 1083 | judge 1084 | juice 1085 | jump 1086 | jungle 1087 | junior 1088 | junk 1089 | just 1090 | kangaroo 1091 | keen 1092 | keep 1093 | ketchup 1094 | key 1095 | kick 1096 | kid 1097 | kidney 1098 | kind 1099 | kingdom 1100 | kiss 1101 | kit 1102 | kitchen 1103 | kite 1104 | kitten 1105 | kiwi 1106 | knee 1107 | knife 1108 | knock 1109 | know 1110 | lab 1111 | label 1112 | labor 1113 | ladder 1114 | lady 1115 | lake 1116 | lamp 1117 | language 1118 | laptop 1119 | large 1120 | later 1121 | latin 1122 | laugh 1123 | laundry 1124 | lava 1125 | law 1126 | lawn 1127 | lawsuit 1128 | layer 1129 | lazy 1130 | leader 1131 | leaf 1132 | learn 1133 | leave 1134 | lecture 1135 | left 1136 | leg 1137 | legal 1138 | legend 1139 | leisure 1140 | lemon 1141 | lend 1142 | length 1143 | lens 1144 | leopard 1145 | lesson 1146 | letter 1147 | level 1148 | liar 1149 | liberty 1150 | library 1151 | license 1152 | life 1153 | lift 1154 | light 1155 | like 1156 | limb 1157 | limit 1158 | link 1159 | lion 1160 | liquid 1161 | list 1162 | little 1163 | live 1164 | lizard 1165 | load 1166 | loan 1167 | lobster 1168 | local 1169 | lock 1170 | logic 1171 | lonely 1172 | long 1173 | loop 1174 | lottery 1175 | loud 1176 | lounge 1177 | love 1178 | loyal 1179 | lucky 1180 | luggage 1181 | lumber 1182 | lunar 1183 | lunch 1184 | luxury 1185 | lyrics 1186 | machine 1187 | mad 1188 | magic 1189 | magnet 1190 | maid 1191 | mail 1192 | main 1193 | major 1194 | make 1195 | mammal 1196 | man 1197 | manage 1198 | mandate 1199 | mango 1200 | mansion 1201 | manual 1202 | maple 1203 | marble 1204 | march 1205 | margin 1206 | marine 1207 | market 1208 | marriage 1209 | mask 1210 | mass 1211 | master 1212 | match 1213 | material 1214 | math 1215 | matrix 1216 | matter 1217 | maximum 1218 | maze 1219 | meadow 1220 | mean 1221 | measure 1222 | meat 1223 | mechanic 1224 | medal 1225 | media 1226 | melody 1227 | melt 1228 | member 1229 | memory 1230 | mention 1231 | menu 1232 | mercy 1233 | merge 1234 | merit 1235 | merry 1236 | mesh 1237 | message 1238 | metal 1239 | method 1240 | middle 1241 | midnight 1242 | milk 1243 | million 1244 | mimic 1245 | mind 1246 | minimum 1247 | minor 1248 | minute 1249 | miracle 1250 | mirror 1251 | misery 1252 | miss 1253 | mistake 1254 | mix 1255 | mixed 1256 | mixture 1257 | mobile 1258 | model 1259 | modify 1260 | mom 1261 | moment 1262 | monitor 1263 | monkey 1264 | monster 1265 | month 1266 | moon 1267 | moral 1268 | more 1269 | morning 1270 | mosquito 1271 | mother 1272 | motion 1273 | motor 1274 | mountain 1275 | mouse 1276 | move 1277 | movie 1278 | much 1279 | muffin 1280 | mule 1281 | multiply 1282 | muscle 1283 | museum 1284 | mushroom 1285 | music 1286 | must 1287 | mutual 1288 | myself 1289 | mystery 1290 | myth 1291 | naive 1292 | name 1293 | napkin 1294 | narrow 1295 | nasty 1296 | nation 1297 | nature 1298 | near 1299 | neck 1300 | need 1301 | negative 1302 | neglect 1303 | neither 1304 | nephew 1305 | nerve 1306 | nest 1307 | net 1308 | network 1309 | neutral 1310 | never 1311 | news 1312 | next 1313 | nice 1314 | night 1315 | noble 1316 | noise 1317 | nominee 1318 | noodle 1319 | normal 1320 | north 1321 | nose 1322 | notable 1323 | note 1324 | nothing 1325 | notice 1326 | novel 1327 | now 1328 | nuclear 1329 | number 1330 | nurse 1331 | nut 1332 | oak 1333 | obey 1334 | object 1335 | oblige 1336 | obscure 1337 | observe 1338 | obtain 1339 | obvious 1340 | occur 1341 | ocean 1342 | october 1343 | odor 1344 | off 1345 | offer 1346 | office 1347 | often 1348 | oil 1349 | okay 1350 | old 1351 | olive 1352 | olympic 1353 | omit 1354 | once 1355 | one 1356 | onion 1357 | online 1358 | only 1359 | open 1360 | opera 1361 | opinion 1362 | oppose 1363 | option 1364 | orange 1365 | orbit 1366 | orchard 1367 | order 1368 | ordinary 1369 | organ 1370 | orient 1371 | original 1372 | orphan 1373 | ostrich 1374 | other 1375 | outdoor 1376 | outer 1377 | output 1378 | outside 1379 | oval 1380 | oven 1381 | over 1382 | own 1383 | owner 1384 | oxygen 1385 | oyster 1386 | ozone 1387 | pact 1388 | paddle 1389 | page 1390 | pair 1391 | palace 1392 | palm 1393 | panda 1394 | panel 1395 | panic 1396 | panther 1397 | paper 1398 | parade 1399 | parent 1400 | park 1401 | parrot 1402 | party 1403 | pass 1404 | patch 1405 | path 1406 | patient 1407 | patrol 1408 | pattern 1409 | pause 1410 | pave 1411 | payment 1412 | peace 1413 | peanut 1414 | pear 1415 | peasant 1416 | pelican 1417 | pen 1418 | penalty 1419 | pencil 1420 | people 1421 | pepper 1422 | perfect 1423 | permit 1424 | person 1425 | pet 1426 | phone 1427 | photo 1428 | phrase 1429 | physical 1430 | piano 1431 | picnic 1432 | picture 1433 | piece 1434 | pig 1435 | pigeon 1436 | pill 1437 | pilot 1438 | pink 1439 | pioneer 1440 | pipe 1441 | pistol 1442 | pitch 1443 | pizza 1444 | place 1445 | planet 1446 | plastic 1447 | plate 1448 | play 1449 | please 1450 | pledge 1451 | pluck 1452 | plug 1453 | plunge 1454 | poem 1455 | poet 1456 | point 1457 | polar 1458 | pole 1459 | police 1460 | pond 1461 | pony 1462 | pool 1463 | popular 1464 | portion 1465 | position 1466 | possible 1467 | post 1468 | potato 1469 | pottery 1470 | poverty 1471 | powder 1472 | power 1473 | practice 1474 | praise 1475 | predict 1476 | prefer 1477 | prepare 1478 | present 1479 | pretty 1480 | prevent 1481 | price 1482 | pride 1483 | primary 1484 | print 1485 | priority 1486 | prison 1487 | private 1488 | prize 1489 | problem 1490 | process 1491 | produce 1492 | profit 1493 | program 1494 | project 1495 | promote 1496 | proof 1497 | property 1498 | prosper 1499 | protect 1500 | proud 1501 | provide 1502 | public 1503 | pudding 1504 | pull 1505 | pulp 1506 | pulse 1507 | pumpkin 1508 | punch 1509 | pupil 1510 | puppy 1511 | purchase 1512 | purity 1513 | purpose 1514 | purse 1515 | push 1516 | put 1517 | puzzle 1518 | pyramid 1519 | quality 1520 | quantum 1521 | quarter 1522 | question 1523 | quick 1524 | quit 1525 | quiz 1526 | quote 1527 | rabbit 1528 | raccoon 1529 | race 1530 | rack 1531 | radar 1532 | radio 1533 | rail 1534 | rain 1535 | raise 1536 | rally 1537 | ramp 1538 | ranch 1539 | random 1540 | range 1541 | rapid 1542 | rare 1543 | rate 1544 | rather 1545 | raven 1546 | raw 1547 | razor 1548 | ready 1549 | real 1550 | reason 1551 | rebel 1552 | rebuild 1553 | recall 1554 | receive 1555 | recipe 1556 | record 1557 | recycle 1558 | reduce 1559 | reflect 1560 | reform 1561 | refuse 1562 | region 1563 | regret 1564 | regular 1565 | reject 1566 | relax 1567 | release 1568 | relief 1569 | rely 1570 | remain 1571 | remember 1572 | remind 1573 | remove 1574 | render 1575 | renew 1576 | rent 1577 | reopen 1578 | repair 1579 | repeat 1580 | replace 1581 | report 1582 | require 1583 | rescue 1584 | resemble 1585 | resist 1586 | resource 1587 | response 1588 | result 1589 | retire 1590 | retreat 1591 | return 1592 | reunion 1593 | reveal 1594 | review 1595 | reward 1596 | rhythm 1597 | rib 1598 | ribbon 1599 | rice 1600 | rich 1601 | ride 1602 | ridge 1603 | rifle 1604 | right 1605 | rigid 1606 | ring 1607 | riot 1608 | ripple 1609 | risk 1610 | ritual 1611 | rival 1612 | river 1613 | road 1614 | roast 1615 | robot 1616 | robust 1617 | rocket 1618 | romance 1619 | roof 1620 | rookie 1621 | room 1622 | rose 1623 | rotate 1624 | rough 1625 | round 1626 | route 1627 | royal 1628 | rubber 1629 | rude 1630 | rug 1631 | rule 1632 | run 1633 | runway 1634 | rural 1635 | sad 1636 | saddle 1637 | sadness 1638 | safe 1639 | sail 1640 | salad 1641 | salmon 1642 | salon 1643 | salt 1644 | salute 1645 | same 1646 | sample 1647 | sand 1648 | satisfy 1649 | satoshi 1650 | sauce 1651 | sausage 1652 | save 1653 | say 1654 | scale 1655 | scan 1656 | scare 1657 | scatter 1658 | scene 1659 | scheme 1660 | school 1661 | science 1662 | scissors 1663 | scorpion 1664 | scout 1665 | scrap 1666 | screen 1667 | script 1668 | scrub 1669 | sea 1670 | search 1671 | season 1672 | seat 1673 | second 1674 | secret 1675 | section 1676 | security 1677 | seed 1678 | seek 1679 | segment 1680 | select 1681 | sell 1682 | seminar 1683 | senior 1684 | sense 1685 | sentence 1686 | series 1687 | service 1688 | session 1689 | settle 1690 | setup 1691 | seven 1692 | shadow 1693 | shaft 1694 | shallow 1695 | share 1696 | shed 1697 | shell 1698 | sheriff 1699 | shield 1700 | shift 1701 | shine 1702 | ship 1703 | shiver 1704 | shock 1705 | shoe 1706 | shoot 1707 | shop 1708 | short 1709 | shoulder 1710 | shove 1711 | shrimp 1712 | shrug 1713 | shuffle 1714 | shy 1715 | sibling 1716 | sick 1717 | side 1718 | siege 1719 | sight 1720 | sign 1721 | silent 1722 | silk 1723 | silly 1724 | silver 1725 | similar 1726 | simple 1727 | since 1728 | sing 1729 | siren 1730 | sister 1731 | situate 1732 | six 1733 | size 1734 | skate 1735 | sketch 1736 | ski 1737 | skill 1738 | skin 1739 | skirt 1740 | skull 1741 | slab 1742 | slam 1743 | sleep 1744 | slender 1745 | slice 1746 | slide 1747 | slight 1748 | slim 1749 | slogan 1750 | slot 1751 | slow 1752 | slush 1753 | small 1754 | smart 1755 | smile 1756 | smoke 1757 | smooth 1758 | snack 1759 | snake 1760 | snap 1761 | sniff 1762 | snow 1763 | soap 1764 | soccer 1765 | social 1766 | sock 1767 | soda 1768 | soft 1769 | solar 1770 | soldier 1771 | solid 1772 | solution 1773 | solve 1774 | someone 1775 | song 1776 | soon 1777 | sorry 1778 | sort 1779 | soul 1780 | sound 1781 | soup 1782 | source 1783 | south 1784 | space 1785 | spare 1786 | spatial 1787 | spawn 1788 | speak 1789 | special 1790 | speed 1791 | spell 1792 | spend 1793 | sphere 1794 | spice 1795 | spider 1796 | spike 1797 | spin 1798 | spirit 1799 | split 1800 | spoil 1801 | sponsor 1802 | spoon 1803 | sport 1804 | spot 1805 | spray 1806 | spread 1807 | spring 1808 | spy 1809 | square 1810 | squeeze 1811 | squirrel 1812 | stable 1813 | stadium 1814 | staff 1815 | stage 1816 | stairs 1817 | stamp 1818 | stand 1819 | start 1820 | state 1821 | stay 1822 | steak 1823 | steel 1824 | stem 1825 | step 1826 | stereo 1827 | stick 1828 | still 1829 | sting 1830 | stock 1831 | stomach 1832 | stone 1833 | stool 1834 | story 1835 | stove 1836 | strategy 1837 | street 1838 | strike 1839 | strong 1840 | struggle 1841 | student 1842 | stuff 1843 | stumble 1844 | style 1845 | subject 1846 | submit 1847 | subway 1848 | success 1849 | such 1850 | sudden 1851 | suffer 1852 | sugar 1853 | suggest 1854 | suit 1855 | summer 1856 | sun 1857 | sunny 1858 | sunset 1859 | super 1860 | supply 1861 | supreme 1862 | sure 1863 | surface 1864 | surge 1865 | surprise 1866 | surround 1867 | survey 1868 | suspect 1869 | sustain 1870 | swallow 1871 | swamp 1872 | swap 1873 | swarm 1874 | swear 1875 | sweet 1876 | swift 1877 | swim 1878 | swing 1879 | switch 1880 | sword 1881 | symbol 1882 | symptom 1883 | syrup 1884 | system 1885 | table 1886 | tackle 1887 | tag 1888 | tail 1889 | talent 1890 | talk 1891 | tank 1892 | tape 1893 | target 1894 | task 1895 | taste 1896 | tattoo 1897 | taxi 1898 | teach 1899 | team 1900 | tell 1901 | ten 1902 | tenant 1903 | tennis 1904 | tent 1905 | term 1906 | test 1907 | text 1908 | thank 1909 | that 1910 | theme 1911 | then 1912 | theory 1913 | there 1914 | they 1915 | thing 1916 | this 1917 | thought 1918 | three 1919 | thrive 1920 | throw 1921 | thumb 1922 | thunder 1923 | ticket 1924 | tide 1925 | tiger 1926 | tilt 1927 | timber 1928 | time 1929 | tiny 1930 | tip 1931 | tired 1932 | tissue 1933 | title 1934 | toast 1935 | tobacco 1936 | today 1937 | toddler 1938 | toe 1939 | together 1940 | toilet 1941 | token 1942 | tomato 1943 | tomorrow 1944 | tone 1945 | tongue 1946 | tonight 1947 | tool 1948 | tooth 1949 | top 1950 | topic 1951 | topple 1952 | torch 1953 | tornado 1954 | tortoise 1955 | toss 1956 | total 1957 | tourist 1958 | toward 1959 | tower 1960 | town 1961 | toy 1962 | track 1963 | trade 1964 | traffic 1965 | tragic 1966 | train 1967 | transfer 1968 | trap 1969 | trash 1970 | travel 1971 | tray 1972 | treat 1973 | tree 1974 | trend 1975 | trial 1976 | tribe 1977 | trick 1978 | trigger 1979 | trim 1980 | trip 1981 | trophy 1982 | trouble 1983 | truck 1984 | true 1985 | truly 1986 | trumpet 1987 | trust 1988 | truth 1989 | try 1990 | tube 1991 | tuition 1992 | tumble 1993 | tuna 1994 | tunnel 1995 | turkey 1996 | turn 1997 | turtle 1998 | twelve 1999 | twenty 2000 | twice 2001 | twin 2002 | twist 2003 | two 2004 | type 2005 | typical 2006 | ugly 2007 | umbrella 2008 | unable 2009 | unaware 2010 | uncle 2011 | uncover 2012 | under 2013 | undo 2014 | unfair 2015 | unfold 2016 | unhappy 2017 | uniform 2018 | unique 2019 | unit 2020 | universe 2021 | unknown 2022 | unlock 2023 | until 2024 | unusual 2025 | unveil 2026 | update 2027 | upgrade 2028 | uphold 2029 | upon 2030 | upper 2031 | upset 2032 | urban 2033 | urge 2034 | usage 2035 | use 2036 | used 2037 | useful 2038 | useless 2039 | usual 2040 | utility 2041 | vacant 2042 | vacuum 2043 | vague 2044 | valid 2045 | valley 2046 | valve 2047 | van 2048 | vanish 2049 | vapor 2050 | various 2051 | vast 2052 | vault 2053 | vehicle 2054 | velvet 2055 | vendor 2056 | venture 2057 | venue 2058 | verb 2059 | verify 2060 | version 2061 | very 2062 | vessel 2063 | veteran 2064 | viable 2065 | vibrant 2066 | vicious 2067 | victory 2068 | video 2069 | view 2070 | village 2071 | vintage 2072 | violin 2073 | virtual 2074 | virus 2075 | visa 2076 | visit 2077 | visual 2078 | vital 2079 | vivid 2080 | vocal 2081 | voice 2082 | void 2083 | volcano 2084 | volume 2085 | vote 2086 | voyage 2087 | wage 2088 | wagon 2089 | wait 2090 | walk 2091 | wall 2092 | walnut 2093 | want 2094 | warfare 2095 | warm 2096 | warrior 2097 | wash 2098 | wasp 2099 | waste 2100 | water 2101 | wave 2102 | way 2103 | wealth 2104 | weapon 2105 | wear 2106 | weasel 2107 | weather 2108 | web 2109 | wedding 2110 | weekend 2111 | weird 2112 | welcome 2113 | west 2114 | wet 2115 | whale 2116 | what 2117 | wheat 2118 | wheel 2119 | when 2120 | where 2121 | whip 2122 | whisper 2123 | wide 2124 | width 2125 | wife 2126 | wild 2127 | will 2128 | win 2129 | window 2130 | wine 2131 | wing 2132 | wink 2133 | winner 2134 | winter 2135 | wire 2136 | wisdom 2137 | wise 2138 | wish 2139 | witness 2140 | wolf 2141 | woman 2142 | wonder 2143 | wood 2144 | wool 2145 | word 2146 | work 2147 | world 2148 | worry 2149 | worth 2150 | wrap 2151 | wreck 2152 | wrestle 2153 | wrist 2154 | write 2155 | wrong 2156 | yard 2157 | year 2158 | yellow 2159 | you 2160 | young 2161 | youth 2162 | zebra 2163 | zero 2164 | zone 2165 | zoo 2166 | )"; 2167 | std::map word_map = make_word_map(english); 2168 | std::vector word_vec = make_word_vector(english); 2169 | --------------------------------------------------------------------------------