diff --git a/apiary.apib b/apiary.apib new file mode 100644 index 0000000000..44b1ee448e --- /dev/null +++ b/apiary.apib @@ -0,0 +1,4033 @@ +FORMAT: 1A +HOST: https://api.counterparty.io:4000 + +# Counterparty Core API + +The Counterparty Core API is the recommended (and only supported) way to query the state of a Counterparty node. + +API routes are divided into 11 groups: +- [`/blocks`](#/reference/blocks) +- [`/transactions`](#/reference/transactions) +- [`/addresses`](#/reference/addresses) +- [`/assets`](#/reference/assets) +- [`/orders`](#/reference/orders) +- [`/bets`](#/reference/bets) +- [`/dispensers`](#/reference/dispensers) +- [`/burns`](#/reference/burns) +- [`/events`](#/reference/events) +- [`/mempool`](#/reference/mempool) +- [`/backend`](#/reference/backend) + +Notes: + +- When the server is not ready, that is to say when all the blocks are not yet parsed, all routes return a 503 error except `/` and those in the `/blocks`, `/transactions` and `/backend` groups which always return a result. + +- All API responses contain the following 3 headers: + + * `X-COUNTERPARTY-HEIGHT` contains the last block parsed by Counterparty + * `X-BACKEND-HEIGHT` contains the last block known to Bitcoin Core + * `X-COUNTERPARTY-READY` contains true if `X-COUNTERPARTY-HEIGHT` >= `X-BACKEND-HEIGHT` - 1 + +- All API responses follow the following format: + + ``` + { + "error": , + "result": + } + ``` + +- Routes in the `/backend` group serve as a proxy to make requests to AddrindexRS. + +# Counterparty API Root [/] + +### Get Server Info [GET] + +Returns server information and the list of documented routes in JSON format. + ++ Response 200 (application/json) + + ``` + { + "server_ready": true, + "network": "mainnet", + "version": "10.1.1", + "backend_height": 840796, + "counterparty_height": 840796, + "routes": [ + + ] + } + ``` + + +## Group Blocks + +### Get Blocks [GET /blocks{?last}{?limit}] + +Returns the list of the last ten blocks + ++ Parameters + + last: `840000` (int, optional) - The index of the most recent block to return + + Default: `None` + + limit: `2` (int, optional) - The number of blocks to return + + Default: `10` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 840000, + "block_hash": "0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5", + "block_time": 1713571767, + "previous_block_hash": "0000000000000000000172014ba58d66455762add0512355ad651207918494ab", + "difficulty": 86388558925171.02, + "ledger_hash": "b91dd54cfbd3aff07b358a038bf6174ddc06f36bd00cdccf048e8281bcd56224", + "txlist_hash": "b641c3e190b9941fcd5c84a7c07e66c03559ef26dcea892e2db1cf1d8392a4f2", + "messages_hash": "5c5de34009839ee66ebc3097ecd28bd5deee9553966b3ee39e8a08e123ac9adc" + }, + { + "block_index": 839999, + "block_hash": "0000000000000000000172014ba58d66455762add0512355ad651207918494ab", + "block_time": 1713571533, + "previous_block_hash": "00000000000000000001dcce6ce7c8a45872cafd1fb04732b447a14a91832591", + "difficulty": 86388558925171.02, + "ledger_hash": "e2b2e23c2ac1060dafe2395da01fe5907f323b5a644816f45f003411c612ac30", + "txlist_hash": "f33f800ef166e6ef5b3df15a0733f9fd3ebb0b799f39ef1951e6709118b7c0fd", + "messages_hash": "16b7d40543b7b80587f4d98c84fcdfdceb2d1c18abba82c7064c09c2795b7ab2" + } + ] + } + ``` + +### Get Block [GET /blocks/{block_index}] + +Return the information of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": { + "block_index": 840464, + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_time": 1713852783, + "previous_block_hash": "00000000000000000002db1e5aa19784eec3de949f98ec757e7a7f2fc392079d", + "difficulty": 86388558925171.02, + "ledger_hash": "b3f8cbb50b0705a5c4a8495f8b5128de13a32daebd8ac5e8316a010f0d203584", + "txlist_hash": "84bdc5b9073f775a2b65de7da2b10b89a2235f3501883b0a836e41e68cd00d46", + "messages_hash": "801d961c45a257f85ef0f10a6a8fdf048a520ae4861c0903f26365b3eaaaf540" + } + } + ``` + +### Get Transactions By Block [GET /blocks/{block_index}/transactions] + +Returns the transactions of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "block_index": 840464, + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_time": 1713852783, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "destination": "", + "btc_amount": 0, + "fee": 56565, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "supported": 1 + } + ] + } + ``` + +### Get Events By Block [GET /blocks/{block_index}/events] + +Returns the events of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event_index": 14194760, + "event": "BLOCK_PARSED", + "bindings": { + "block_index": 840464, + "ledger_hash": "b3f8cbb50b0705a5c4a8495f8b5128de13a32daebd8ac5e8316a010f0d203584", + "messages_hash": "801d961c45a257f85ef0f10a6a8fdf048a520ae4861c0903f26365b3eaaaf540", + "txlist_hash": "84bdc5b9073f775a2b65de7da2b10b89a2235f3501883b0a836e41e68cd00d46" + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194759, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194758, + "event": "CREDIT", + "bindings": { + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "block_index": 840464, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "quantity": 1, + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194757, + "event": "ASSET_ISSUANCE", + "bindings": { + "asset": "UNNEGOTIABLE", + "asset_longname": null, + "block_index": 840464, + "call_date": 0, + "call_price": 0.0, + "callable": false, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "divisible": false, + "fee_paid": 50000000, + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "locked": false, + "quantity": 1, + "reset": false, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "status": "valid", + "transfer": false, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194756, + "event": "ASSET_CREATION", + "bindings": { + "asset_id": "75313533584419238", + "asset_longname": null, + "asset_name": "UNNEGOTIABLE", + "block_index": 840464 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194755, + "event": "DEBIT", + "bindings": { + "action": "issuance fee", + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "XCP", + "block_index": 840464, + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "quantity": 50000000, + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194754, + "event": "NEW_TRANSACTION", + "bindings": { + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_index": 840464, + "block_time": 1713852783, + "btc_amount": 0, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "destination": "", + "fee": 56565, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852779 + }, + { + "event_index": 14194753, + "event": "NEW_BLOCK", + "bindings": { + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_index": 840464, + "block_time": 1713852783, + "difficulty": 86388558925171.02, + "previous_block_hash": "00000000000000000002db1e5aa19784eec3de949f98ec757e7a7f2fc392079d" + }, + "block_index": 840464, + "timestamp": 1713852779 + } + ] + } + ``` + +### Get Events Counts By Block [GET /blocks/{block_index}/events/counts] + +Returns the event counts of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event": "ASSET_CREATION", + "event_count": 1 + }, + { + "event": "ASSET_ISSUANCE", + "event_count": 1 + }, + { + "event": "BLOCK_PARSED", + "event_count": 1 + }, + { + "event": "CREDIT", + "event_count": 1 + }, + { + "event": "DEBIT", + "event_count": 1 + }, + { + "event": "NEW_BLOCK", + "event_count": 1 + }, + { + "event": "NEW_TRANSACTION", + "event_count": 1 + }, + { + "event": "TRANSACTION_PARSED", + "event_count": 1 + } + ] + } + ``` + +### Get Events By Block And Event [GET /blocks/{block_index}/events/{event}] + +Returns the events of a block filtered by event + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + + event: `CREDIT` (str, required) - The event to filter by + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event_index": 14194758, + "event": "CREDIT", + "bindings": { + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "block_index": 840464, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "quantity": 1, + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + } + ] + } + ``` + +### Get Credits By Block [GET /blocks/{block_index}/credits] + +Returns the credits of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 840464, + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "quantity": 1, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + } + ] + } + ``` + +### Get Debits By Block [GET /blocks/{block_index}/debits] + +Returns the debits of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 840464, + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + } + ] + } + ``` + +### Get Expirations [GET /blocks/{block_index}/expirations] + +Returns the expirations of a block + ++ Parameters + + block_index: `840356` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "type": "order", + "object_id": "533d5c0ecd8ca9c2946d3298cc5e570eee55b62b887dd85c95de6de4fdc7f441" + }, + { + "type": "order", + "object_id": "b048661afeee3f266792481168024abc0d7648fe0e019e4a1e0fd9867c2c0ffc" + } + ] + } + ``` + +### Get Cancels [GET /blocks/{block_index}/cancels] + +Returns the cancels of a block + ++ Parameters + + block_index: `839746` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2725738, + "tx_hash": "793af9129c7368f974c3ea0c87ad38131f0d82d19fbaf1adf8aaf2e657ec42b8", + "block_index": 839746, + "source": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "offer_hash": "04b258ac37f73e3b9a8575110320d67c752e1baace0f516da75845f388911735", + "status": "valid" + }, + { + "tx_index": 2725739, + "tx_hash": "2071e8a6fbc0c443b152d513c754356f8f962db2fa694de8c6826b57413cc190", + "block_index": 839746, + "source": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "offer_hash": "b1622dbe4f0ce740cb6c18f6f136876bc4949c40a62bc8cceefa81fd6679a57f", + "status": "valid" + } + ] + } + ``` + +### Get Destructions [GET /blocks/{block_index}/destructions] + +Returns the destructions of a block + ++ Parameters + + block_index: `839988` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726496, + "tx_hash": "f5609facc8dac6cdf70b15c514ea15a9acc24a9bd86dcac2b845d5740fbcc50b", + "block_index": 839988, + "source": "1FpLAtreZjTVCMcj1pq1AHWuqcs3n7obMm", + "asset": "COBBEE", + "quantity": 50000, + "tag": "", + "status": "valid" + } + ] + } + ``` + +### Get Issuances By Block [GET /blocks/{block_index}/issuances] + +Returns the issuances of a block + ++ Parameters + + block_index: `840464` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "msg_index": 0, + "block_index": 840464, + "asset": "UNNEGOTIABLE", + "quantity": 1, + "divisible": 0, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "transfer": 0, + "callable": 0, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "fee_paid": 50000000, + "locked": 0, + "status": "valid", + "asset_longname": null, + "reset": 0 + } + ] + } + ``` + +### Get Sends Or Receives By Block [GET /blocks/{block_index}/sends{?limit}{?offset}] + +Returns the sends of a block + ++ Parameters + + block_index: `840459` (int, required) - The index of the block to return + + limit (int, optional) - + + Default: `100` + + offset (int, optional) - + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726604, + "tx_hash": "b4bbb14c99dd260eb634243e5c595e1b7213459979857a32850de84989bb71ec", + "block_index": 840459, + "source": "13Hnmhs5gy2yXKVBx4wSM5HCBdKnaSBZJH", + "destination": "1LfT83WAxbN9qKhtrXxcQA6xgdhfZk21Hz", + "asset": "GAMESOFTRUMP", + "quantity": 1, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + } + ``` + +### Get Dispenses By Block [GET /blocks/{block_index}/dispenses] + +Returns the dispenses of a block + ++ Parameters + + block_index: `840322` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726580, + "dispense_index": 0, + "tx_hash": "e7f0f2c9bef7a492b714a5952ec61b283be344419c5bc33f405f9af41ebfa48b", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "bc1qzcdkhnexpjc8wvkyrpyrsn0f5xzcpu877mjmgj", + "asset": "FLOCK", + "dispense_quantity": 90000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + } + ] + } + ``` + +### Get Sweeps By Block [GET /blocks/{block_index}/sweeps] + +Returns the sweeps of a block + ++ Parameters + + block_index: `836519` (int, required) - The index of the block to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2720536, + "tx_hash": "9309a4c0aed426e281a52e5d48acadd1464999269a5e75cf2293edd0277d743d", + "block_index": 836519, + "source": "1DMVnJuqBobXA9xYioabBsR4mN8bvVtCAW", + "destination": "1HC2q92SfH1ZHzS4CrDwp6KAipV4FqUL4T", + "flags": 3, + "status": "valid", + "memo": null, + "fee_paid": 1400000 + }, + { + "tx_index": 2720537, + "tx_hash": "d8db6281abffdbf6c320d5ade06aeb6fad2f7bfa1a2c2243c6726020a27107d3", + "block_index": 836519, + "source": "18szqTVJUWwYrtRHq98Wn4DhCGGiy3jZ87", + "destination": "1HC2q92SfH1ZHzS4CrDwp6KAipV4FqUL4T", + "flags": 3, + "status": "valid", + "memo": null, + "fee_paid": 1400000 + } + ] + } + ``` + +## Group Transactions + +### Info [GET /transactions/info{?rawtransaction}{?block_index}] + +Returns Counterparty information from a raw transaction in hex format. + ++ Parameters + + rawtransaction: `01000000017828697743c03aef6a3a8ba54b22bf579ffcab8161faf20e7b20c4ecd75cc986010000006b483045022100d1bd0531bb1ed2dd2cbf77d6933273e792a3dbfa84327d419169850ddd5976f502205d1ab0f7bcbf1a0cc183f0520c9aa8f711d41cb790c0c4ac39da6da4a093d798012103d3b1f711e907acb556e239f6cafb6a4f7fe40d8dd809b0e06e739c2afd73f202ffffffff0200000000000000004d6a4bf29880b93b0711524c7ef9c76835752088db8bd4113a3daf41fc45ffdc8867ebdbf26817fae377696f36790e52f51005806e9399a427172fedf348cf798ed86e548002ee96909eef0775ec3c2b0100000000001976a91443434cf159cc585fbd74daa9c4b833235b19761b88ac00000000` (str, required) - Raw transaction in hex format + + block_index (int, optional) - Block index mandatory for transactions before block 335000 + + Default: `None` + ++ Response 200 (application/json) + + ``` + { + "result": { + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "destination": "", + "btc_amount": 0, + "fee": 56565, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "unpacked_data": { + "message_type": "issuance", + "message_type_id": 22, + "message_data": { + "asset_id": 75313533584419238, + "asset": "UNNEGOTIABLE", + "subasset_longname": null, + "quantity": 1, + "divisible": false, + "lock": false, + "reset": false, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "status": "valid" + } + } + } + } + ``` + +### Unpack [GET /transactions/unpack{?datahex}{?block_index}] + +Unpacks Counterparty data in hex format and returns the message type and data. + ++ Parameters + + datahex: `16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245` (str, required) - Data in hex format + + block_index (int, optional) - Block index of the transaction containing this data + + Default: `None` + ++ Response 200 (application/json) + + ``` + { + "result": { + "message_type": "issuance", + "message_type_id": 22, + "message_data": { + "asset_id": 75313533584419238, + "asset": "UNNEGOTIABLE", + "subasset_longname": null, + "quantity": 1, + "divisible": false, + "lock": false, + "reset": false, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "status": "valid" + } + } + } + ``` + +### Get Transaction By Hash [GET /transactions/{tx_hash}] + +Returns a transaction by its hash. + ++ Parameters + + tx_hash: `876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5` (str, required) - The hash of the transaction + ++ Response 200 (application/json) + + ``` + { + "result": { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "block_index": 840464, + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_time": 1713852783, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "destination": "", + "btc_amount": 0, + "fee": 56565, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "supported": 1, + "unpacked_data": { + "message_type": "issuance", + "message_type_id": 22, + "message_data": { + "asset_id": 75313533584419238, + "asset": "UNNEGOTIABLE", + "subasset_longname": null, + "quantity": 1, + "divisible": false, + "lock": false, + "reset": false, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "status": "valid" + } + } + } + } + ``` + +## Group Addresses + +### Get Address Balances [GET /addresses/{address}/balances] + +Returns the balances of an address + ++ Parameters + + address: `1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - The address to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000 + } + ] + } + ``` + +### Get Balance By Address And Asset [GET /addresses/{address}/balances/{asset}] + +Returns the balance of an address and asset + ++ Parameters + + address: `1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - The address to return + + asset: `XCP` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": { + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000 + } + } + ``` + +### Get Credits By Address [GET /addresses/{address}/credits{?limit}{?offset}] + +Returns the credits of an address + ++ Parameters + + address: `1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - The address to return + + limit: `5` (int, optional) - The maximum number of credits to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the credits to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 830981, + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000, + "calling_function": "send", + "event": "7e4fbb0a1eeeee34bf499955f1027fb78c514d63a3c8ff2e28c6dad005e4d850", + "tx_index": 2677412 + } + ] + } + ``` + +### Get Debits By Address [GET /addresses/{address}/debits{?limit}{?offset}] + +Returns the debits of an address + ++ Parameters + + address: `bc1q7787j6msqczs58asdtetchl3zwe8ruj57p9r9y` (str, required) - The address to return + + limit: `5` (int, optional) - The maximum number of debits to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the debits to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 836949, + "address": "bc1q7787j6msqczs58asdtetchl3zwe8ruj57p9r9y", + "asset": "XCP", + "quantity": 40000000000, + "action": "open dispenser", + "event": "53ed08176d3479f49986e9282293da85cebc03835b128d8e790ee587f9f1c750", + "tx_index": 2721524 + }, + { + "block_index": 840388, + "address": "bc1q7787j6msqczs58asdtetchl3zwe8ruj57p9r9y", + "asset": "XCP", + "quantity": 250000000000, + "action": "send", + "event": "bc54968ba7d0a59a47b276602e2dbdcf01b14009742e0d7b50272cbae529a9a4", + "tx_index": 2726594 + } + ] + } + ``` + +### Get Bet By Feed [GET /addresses/{address}/bets{?status}] + +Returns the bets of a feed + ++ Parameters + + address: `1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk` (str, required) - The address of the feed + + status: `filled` (str, optional) - The status of the bet + + Default: `open` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 15106, + "tx_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "block_index": 304063, + "source": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 3, + "deadline": 1401828300, + "wager_quantity": 50000000, + "wager_remaining": 0, + "counterwager_quantity": 50000000, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 11, + "expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "filled" + }, + { + "tx_index": 61338, + "tx_hash": "0fcc7f5190c028f6c5534554d10ec5b4a9246d63826421cd58be2d572d11f088", + "block_index": 320704, + "source": "1Ew38GxczvV1KxjzZsq9f8UuRzHkHQrL5C", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 2, + "deadline": 1410728400, + "wager_quantity": 1000000, + "wager_remaining": 0, + "counterwager_quantity": 1999991, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 13, + "expire_index": 320715, + "fee_fraction_int": 1000000, + "status": "filled" + } + ] + } + ``` + +### Get Broadcasts By Source [GET /addresses/{address}/broadcasts{?status}{?order_by}] + +Returns the broadcasts of a source + ++ Parameters + + address: `1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk` (str, required) - The address to return + + status: `valid` (str, optional) - The status of the broadcasts to return + + Default: `valid` + + order_by: `ASC` (str, optional) - The order of the broadcasts to return + + Default: `DESC` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 15055, + "tx_hash": "774887e555a6ae5a8c058ebc0185058307977f01a2d4d326e71f37d6dd977154", + "block_index": 304048, + "source": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "timestamp": 1401815290, + "value": -1.0, + "fee_fraction_int": 1000000, + "text": "xbet.io/feed/1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "locked": 0, + "status": "valid" + }, + { + "tx_index": 61477, + "tx_hash": "5d49993bec727622c7b41c84e2b1e65c368f33390d633d217131ffcc5b592f0d", + "block_index": 320718, + "source": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "timestamp": 1410732503, + "value": 1.0, + "fee_fraction_int": 1000000, + "text": "xbet.io/feed/1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "locked": 0, + "status": "valid" + } + ] + } + ``` + +### Get Burns By Address [GET /addresses/{address}/burns] + +Returns the burns of an address + ++ Parameters + + address: `1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W` (str, required) - The address to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 3070, + "tx_hash": "4560d0e3d04927108b615ab106040489aca9c4aceedcf69d2b71f63b3139c7ae", + "block_index": 283810, + "source": "1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W", + "burned": 10000000, + "earned": 10000000000, + "status": "valid" + } + ] + } + ``` + +### Get Send By Address [GET /addresses/{address}/sends{?limit}{?offset}] + +Returns the sends of an address + ++ Parameters + + address: `1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W` (str, required) - The address to return + + limit: `5` (int, optional) - The maximum number of sends to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the sends to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 163106, + "tx_hash": "1c447b41816f1cfbb83f125c8e05faeaae70dbf27255745ba7393f809bd388eb", + "block_index": 343049, + "source": "1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W", + "destination": "16cRBUNnTWiUh2sXWNn1P7KHyJUmyMkdfH", + "asset": "XCP", + "quantity": 10000000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + } + ``` + +### Get Receive By Address [GET /addresses/{address}/receives{?limit}{?offset}] + +Returns the receives of an address + ++ Parameters + + address: `1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - The address to return + + limit: `5` (int, optional) - The maximum number of receives to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the receives to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2677412, + "tx_hash": "7e4fbb0a1eeeee34bf499955f1027fb78c514d63a3c8ff2e28c6dad005e4d850", + "block_index": 830981, + "source": "bc1qqxr9grqw73dm95cen3g56mzswuj6eqjedu6csx", + "destination": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + } + ``` + +### Get Send By Address And Asset [GET /addresses/{address}/sends/{asset}] + +Returns the sends of an address and asset + ++ Parameters + + address: `1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W` (str, required) - The address to return + + asset: `XCP` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 163106, + "tx_hash": "1c447b41816f1cfbb83f125c8e05faeaae70dbf27255745ba7393f809bd388eb", + "block_index": 343049, + "source": "1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W", + "destination": "16cRBUNnTWiUh2sXWNn1P7KHyJUmyMkdfH", + "asset": "XCP", + "quantity": 10000000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + } + ``` + +### Get Receive By Address And Asset [GET /addresses/{address}/receives/{asset}{?limit}{?offset}] + +Returns the receives of an address and asset + ++ Parameters + + address: `1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - The address to return + + asset: `XCP` (str, required) - The asset to return + + limit: `5` (int, optional) - The maximum number of receives to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the receives to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2677412, + "tx_hash": "7e4fbb0a1eeeee34bf499955f1027fb78c514d63a3c8ff2e28c6dad005e4d850", + "block_index": 830981, + "source": "bc1qqxr9grqw73dm95cen3g56mzswuj6eqjedu6csx", + "destination": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + } + ``` + +### Get Dispensers By Address [GET /addresses/{address}/dispensers{?status}] + +Returns the dispensers of an address + ++ Parameters + + address: `bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz` (str, required) - The address to return + + status (int, optional) - + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + } + ``` + +### Get Dispensers By Address And Asset [GET /addresses/{address}/dispensers/{asset}{?status}] + +Returns the dispensers of an address and an asset + ++ Parameters + + address: `bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz` (str, required) - The address to return + + asset: `ERYKAHPEPU` (str, required) - The asset to return + + status (int, optional) - + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + } + ``` + +### Get Sweeps By Address [GET /addresses/{address}/sweeps] + +Returns the sweeps of an address + ++ Parameters + + address: `18szqTVJUWwYrtRHq98Wn4DhCGGiy3jZ87` (str, required) - The address to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2720537, + "tx_hash": "d8db6281abffdbf6c320d5ade06aeb6fad2f7bfa1a2c2243c6726020a27107d3", + "block_index": 836519, + "source": "18szqTVJUWwYrtRHq98Wn4DhCGGiy3jZ87", + "destination": "1HC2q92SfH1ZHzS4CrDwp6KAipV4FqUL4T", + "flags": 3, + "status": "valid", + "memo": null, + "fee_paid": 1400000 + } + ] + } + ``` + +### Compose Bet [GET /addresses/{address}/compose/bet{?feed_address}{?bet_type}{?deadline}{?wager_quantity}{?counterwager_quantity}{?expiration}{?leverage}{?target_value}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to issue a bet against a feed. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will make the bet + + feed_address: `1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev` (str, required) - The address that hosts the feed to be bet on + + bet_type: `2` (int, required) - Bet 0 for Bullish CFD (deprecated), 1 for Bearish CFD (deprecated), 2 for Equal, 3 for NotEqual + + deadline: `3000000000` (int, required) - The time at which the bet should be decided/settled, in Unix time (seconds since epoch) + + wager_quantity: `1000` (int, required) - The quantities of XCP to wager (in satoshis, hence integer) + + counterwager_quantity: `1000` (int, required) - The minimum quantities of XCP to be wagered against, for the bets to match + + expiration: `100` (int, required) - The number of blocks after which the bet expires if it remains unmatched + + leverage (int, optional) - Leverage, as a fraction of 5040 + + Default: `5040` + + target_value: `1000` (int, optional) - Target value for Equal/NotEqual bet + + Default: `None` + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0322020000000000001976a914bce6191bf2fd5981313cae869e9fafe164f7dbaf88ac0000000000000000316a2f0d1e454cefefcbe14dffa4c01ecd608ec45d2594e5d27c699f4ef2725648c509bf828ec195ee18f83e052061236deff2db0306000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "feed_address": "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + "bet_type": 2, + "deadline": 3000000000, + "wager_quantity": 1000, + "counterwager_quantity": 1000, + "target_value": 1000, + "leverage": 5040, + "expiration": 100 + }, + "name": "bet" + } + } + ``` + +### Compose Broadcast [GET /addresses/{address}/compose/broadcast{?timestamp}{?value}{?fee_fraction}{?text}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to broadcast textual and numerical information to the network. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be sending (must have the necessary quantity of the specified asset) + + timestamp: `4003903983` (int, required) - The timestamp of the broadcast, in Unix time + + value: `100` (float, required) - Numerical value of the broadcast + + fee_fraction: `0.05` (float, required) - How much of every bet on this feed should go to its operator; a fraction of 1, (i.e. 0.05 is five percent) + + text: `"Hello, world!"` (str, required) - The textual part of the broadcast + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0200000000000000002b6a290d1e454cefefcbe17b1100cb21d3398ec45d2594e5d1d822df41d03a332741261ce2f9aee7827cd91c340c0406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "timestamp": 4003903983, + "value": 100.0, + "fee_fraction": 0.05, + "text": "\"Hello, world!\"" + }, + "name": "broadcast" + } + } + ``` + +### Compose Btcpay [GET /addresses/{address}/compose/btcpay{?order_match_id}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to pay for a BTC order match. + ++ Parameters + + address: `bc1qsteve3tfxfg9pcmvzw645sr9zy7es5rx645p6l` (str, required) - The address that will be sending the payment + + order_match_id: `e470416a9500fb046835192da013f48e6468a07dba1bede4a0b68e666ed23c8d_4953bde3d9417b103615c2d3d4b284d4fcf7cbd820e5dd19ac0084e9ebd090b2` (str, required) - The ID of the order match to pay for + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "0200000000010161101e1990879ee64168cce92c9caf338bb571e9cb246b1c2ab87124b95091900200000016001482f2ccc569325050e36c13b55a4065113d985066ffffffff0383c3040000000000160014a9943f67bcd30331d5a4ec6d902cbe03789a1b9700000000000000004b6a49aae396d448ed266a7785be1f6fcfa38dbe3e6e043e3d67691f678d6aa3b30e423f66ffad71eaf3231ef8f05dd5cc2f5b1ea14d33274b9cddacca5bd816a1ce6d5b4d498eb66a981db7add758000000000016001482f2ccc569325050e36c13b55a4065113d98506602000000000000", + "params": { + "source": "bc1qsteve3tfxfg9pcmvzw645sr9zy7es5rx645p6l", + "order_match_id": "e470416a9500fb046835192da013f48e6468a07dba1bede4a0b68e666ed23c8d_4953bde3d9417b103615c2d3d4b284d4fcf7cbd820e5dd19ac0084e9ebd090b2" + }, + "name": "btcpay" + } + } + ``` + +### Compose Burn [GET /addresses/{address}/compose/burn{?quantity}{?overburn}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to burn a given quantity of BTC for XCP (on mainnet, possible between blocks 278310 and 283810; on testnet it is still available). + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address with the BTC to burn + + quantity: `1000` (int, required) - The quantities of BTC to burn (1 BTC maximum burn per address) + + overburn (bool, optional) - Whether to allow the burn to exceed 1 BTC for the address + + Default: `False` + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff02e8030000000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ace61b0406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "quantity": 1000, + "overburn": false + }, + "name": "burn" + } + } + ``` + +### Compose Cancel [GET /addresses/{address}/compose/cancel{?offer_hash}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to cancel an open order or bet. + ++ Parameters + + address: `15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA` (str, required) - The address that placed the order/bet to be cancelled + + offer_hash: `8ce3335391bf71f8f12c0573b4f85b9adc4882a9955d9f8e5ababfdd0060279a` (str, required) - The hash of the order/bet to be cancelled + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000014709bd6af5d4d7f518f80539d4fe9acd5220a520a7b4287416a7379af9e66154020000001976a91432dff6deb7ca3bbc14f7037fa6ef8a8cf8e39fb988acffffffff0200000000000000002b6a292f3720d2b8ae7343c6d0456802c531e1216f466ceb12b96c6fbe417a97291a0660e51fc47fcc1ee1a878667900000000001976a91432dff6deb7ca3bbc14f7037fa6ef8a8cf8e39fb988ac00000000", + "params": { + "source": "15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA", + "offer_hash": "8ce3335391bf71f8f12c0573b4f85b9adc4882a9955d9f8e5ababfdd0060279a" + }, + "name": "cancel" + } + } + ``` + +### Compose Destroy [GET /addresses/{address}/compose/destroy{?asset}{?quantity}{?tag}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to destroy a quantity of an asset. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be sending the asset to be destroyed + + asset: `XCP` (str, required) - The asset to be destroyed + + quantity: `1000` (int, required) - The quantity of the asset to be destroyed + + tag: `"bugs!"` (str, required) - A tag for the destruction + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000226a200d1e454cefefcbe10bffa672ce93608ec55d2594e5d1946a776c900731380c6b94160406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "asset": "XCP", + "quantity": 1000, + "tag": "\"bugs!\"" + }, + "name": "destroy" + } + } + ``` + +### Compose Dispenser [GET /addresses/{address}/compose/dispenser{?asset}{?give_quantity}{?escrow_quantity}{?mainchainrate}{?status}{?open_address}{?oracle_address}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Opens or closes a dispenser for a given asset at a given rate of main chain asset (BTC). Escrowed quantity on open must be equal or greater than give_quantity. It is suggested that you escrow multiples of give_quantity to ease dispenser operation. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be dispensing (must have the necessary escrow_quantity of the specified asset) + + asset: `XCP` (str, required) - The asset or subasset to dispense + + give_quantity: `1000` (int, required) - The quantity of the asset to dispense + + escrow_quantity: `1000` (int, required) - The quantity of the asset to reserve for this dispenser + + mainchainrate: `100` (int, required) - The quantity of the main chain asset (BTC) per dispensed portion + + status: `0` (int, required) - The state of the dispenser. 0 for open, 1 for open using open_address, 10 for closed + + open_address (str, optional) - The address that you would like to open the dispenser on + + Default: `None` + + oracle_address (str, optional) - The address that you would like to use as a price oracle for this dispenser + + Default: `None` + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0200000000000000002c6a2a0d1e454cefefcbe169ffa672ce93608ec55d2594e5d1946a774ef272564b2d4ad8c28ec195ee18f85a160c0b0406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "asset": "XCP", + "give_quantity": 1000, + "escrow_quantity": 1000, + "mainchainrate": 100, + "status": 0, + "open_address": null, + "oracle_address": null + }, + "name": "dispenser" + } + } + ``` + +### Compose Dividend [GET /addresses/{address}/compose/dividend{?quantity_per_unit}{?asset}{?dividend_asset}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to issue a dividend to holders of a given asset. + ++ Parameters + + address: `1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD` (str, required) - The address that will be issuing the dividend (must have the ownership of the asset which the dividend is being issued on) + + quantity_per_unit: `1` (int, required) - The amount of dividend_asset rewarded + + asset: `PEPECASH` (str, required) - The asset or subasset that the dividends are being rewarded on + + dividend_asset: `XCP` (str, required) - The asset or subasset that the dividends are paid in + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000010af94458ae5aa794c49cd27f7b800a7c68c8dd4f59ff66c99db4e9e353c06d93010000001976a914a9055398b92818794b38b15794096f752167e25f88acffffffff020000000000000000236a21068a00268d252c3a8ed0bddb5ef79f823894aa7de1e196c005510f4d787c936a979b230000000000001976a914a9055398b92818794b38b15794096f752167e25f88ac00000000", + "params": { + "source": "1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD", + "quantity_per_unit": 1, + "asset": "PEPECASH", + "dividend_asset": "XCP" + }, + "name": "dividend" + } + } + ``` + +### Compose Issuance [GET /addresses/{address}/compose/issuance{?asset}{?quantity}{?transfer_destination}{?divisible}{?lock}{?reset}{?description}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to Issue a new asset, issue more of an existing asset, lock an asset, reset existing supply, or transfer the ownership of an asset. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be issuing or transfering the asset + + asset: `XCPTEST` (str, required) - The assets to issue or transfer. This can also be a subasset longname for new subasset issuances + + quantity: `1000` (int, required) - The quantity of the asset to issue (set to 0 if transferring an asset) + + transfer_destination: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, optional) - The address to receive the asset + + Default: `None` + + divisible (bool, optional) - Whether this asset is divisible or not (if a transfer, this value must match the value specified when the asset was originally issued) + + Default: `True` + + lock (bool, optional) - Whether this issuance should lock supply of this asset forever + + Default: `False` + + reset (bool, optional) - Wether this issuance should reset any existing supply + + Default: `False` + + description (str, optional) - A textual description for the asset + + Default: `None` + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0322020000000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac0000000000000000236a210d1e454cefefcbe173ffa672cf3a36751b5d2594e5d1946a774ff272960578057c17ec0306000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "asset": "XCPTEST", + "quantity": 1000, + "transfer_destination": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "divisible": true, + "lock": false, + "reset": false, + "description": null + }, + "name": "issuance" + } + } + ``` + +### Compose Mpma [GET /addresses/{address}/compose/mpma{?assets}{?destinations}{?quantities}{?memo}{?memo_is_hex}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to send multiple payments to multiple addresses. + ++ Parameters + + address: `1Fv87qmdtjQDP9d4p9E5ncBQvYB4a3Rhy6` (str, required) - The address that will be sending (must have the necessary quantity of the specified asset) + + assets: `BAABAABLKSHP,BADHAIRDAY,BADWOJAK` (str, required) - comma-separated list of assets to send + + destinations: `1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev,1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD,1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - comma-separated list of addresses to send to + + quantities: `1,2,3` (str, required) - comma-separated list of quantities to send + + memo: `"Hello, world!"` (str, required) - The Memo associated with this transaction + + memo_is_hex: `False` (bool, required) - Whether the memo field is a hexadecimal string + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "0100000001fc9b7b3a0552bdfc3c62096e9d7669fb72d5482c7b4f9618138fdffdc831d60b000000001976a914a39dbfab6f1da182af53a4d14799ee545a6176be88acffffffff04e80300000000000069512103ce014780415d0eafbdadfacfa0cf2604a005a87157042f277627c952eedcbb1f2103abf2b72459ee70e6240a7b2ade1a6fa41c7f38cc1db5e63c6f92c01b859017ee2102e849a65234e77627daab722dd75aee7a8f35981ec1dbd5ec5ee7220075b2cd2d53aee80300000000000069512102ce014780415d0eafbd2fcbf00e308d420b59df89ebba83369fea96a9a06fcf562102373ec5e1389ccadf0a972ec451f8aea015104ded7a57b936d374d0ecfe8067412102e849a65234e77627daab722dd75aee7a8f35981ec1dbd5ec5ee7220075b2cd2d53aee80300000000000069512103d0014780415d0eafbd76dacca0b613dda4b8f37e3015031f11220ac5cf43ef4e21034051b78cdcbde85f0c120261e6ab383015104ded7a57b93cd374d900776d4e132102e849a65234e77627daab722dd75aee7a8f35981ec1dbd5ec5ee7220075b2cd2d53ae22fd0200000000001976a914a39dbfab6f1da182af53a4d14799ee545a6176be88ac00000000", + "params": { + "source": "1Fv87qmdtjQDP9d4p9E5ncBQvYB4a3Rhy6", + "asset_dest_quant_list": [ + [ + "BAABAABLKSHP", + "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + 1 + ], + [ + "BADHAIRDAY", + "1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD", + 2 + ], + [ + "BADWOJAK", + "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + 3 + ] + ], + "memo": "\"Hello, world!\"", + "memo_is_hex": false + }, + "name": "mpma" + } + } + ``` + +### Compose Order [GET /addresses/{address}/compose/order{?give_asset}{?give_quantity}{?get_asset}{?get_quantity}{?expiration}{?fee_required}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to place an order on the distributed exchange. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be issuing the order request (must have the necessary quantity of the specified asset to give) + + give_asset: `XCP` (str, required) - The asset that will be given in the trade + + give_quantity: `1000` (int, required) - The quantity of the asset that will be given + + get_asset: `PEPECASH` (str, required) - The asset that will be received in the trade + + get_quantity: `1000` (int, required) - The quantity of the asset that will be received + + expiration: `100` (int, required) - The number of blocks for which the order should be valid + + fee_required: `100` (int, required) - The miners’ fee required to be paid by orders for them to match this one; in BTC; required only if buying BTC (may be zero, though) + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000356a330d1e454cefefcbe16fffa672ce93608ec55d2594e5d1946a774ef2724a2a4f457bc28ec195ee18fbd616f461236d8be718616dac000406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "give_asset": "XCP", + "give_quantity": 1000, + "get_asset": "PEPECASH", + "get_quantity": 1000, + "expiration": 100, + "fee_required": 100 + }, + "name": "order" + } + } + ``` + +### Compose Send [GET /addresses/{address}/compose/send{?destination}{?asset}{?quantity}{?memo}{?memo_is_hex}{?use_enhanced_send}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to send a quantity of an asset to another address. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be sending (must have the necessary quantity of the specified asset) + + destination: `1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev` (str, required) - The address that will be receiving the asset + + asset: `XCP` (str, required) - The asset or subasset to send + + quantity: `1000` (int, required) - The quantity of the asset to send + + memo (str, optional) - The Memo associated with this transaction + + Default: `None` + + memo_is_hex (bool, optional) - Whether the memo field is a hexadecimal string + + Default: `False` + + use_enhanced_send (bool, optional) - If this is false, the construct a legacy transaction sending bitcoin dust + + Default: `True` + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000306a2e0d1e454cefefcbe167ffa672ce93608ec55d2594e5d1946a774e4e944f50dfb46943bffd3b68866791f7f496f8c270060406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "destination": "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + "asset": "XCP", + "quantity": 1000, + "memo": null, + "memo_is_hex": false, + "use_enhanced_send": true + }, + "name": "send" + } + } + ``` + +### Compose Sweep [GET /addresses/{address}/compose/sweep{?destination}{?flags}{?memo}{?encoding}{?fee_per_kb}{?regular_dust_size}{?multisig_dust_size}{?op_return_value}{?pubkey}{?allow_unconfirmed_inputs}{?fee}{?fee_provided}{?unspent_tx_hash}{?dust_return_pubkey}{?disable_utxo_locks}{?extended_tx_info}{?p2sh_pretx_txid}{?old_style_api}{?segwit}] + +Composes a transaction to Sends all assets and/or transfer ownerships to a destination address. + ++ Parameters + + address: `1CounterpartyXXXXXXXXXXXXXXXUWLpVr` (str, required) - The address that will be sending + + destination: `1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev` (str, required) - The address to receive the assets and/or ownerships + + flags: `7` (int, required) - An OR mask of flags indicating how the sweep should be processed. Possible flags are: - FLAG_BALANCES: (integer) 1, specifies that all balances should be transferred. - FLAG_OWNERSHIP: (integer) 2, specifies that all ownerships should be transferred. - FLAG_BINARY_MEMO: (integer) 4, specifies that the memo is in binary/hex form. + + memo: `FFFF` (str, required) - The Memo associated with this transaction in hex format + + encoding (str, optional) - The encoding method to use + + Default: `auto` + + fee_per_kb (int, optional) - The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi) + + Default: `None` + + regular_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output. + + Default: `546` + + multisig_dust_size (int, optional) - Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output + + Default: `1000` + + op_return_value (int, optional) - The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away + + Default: `0` + + pubkey (str, optional) - The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash. + + Default: `None` + + allow_unconfirmed_inputs (bool, optional) - Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs + + Default: `False` + + fee (int, optional) - If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose + + Default: `None` + + fee_provided (int, optional) - If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value + + Default: `0` + + unspent_tx_hash (str, optional) - When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs + + Default: `None` + + dust_return_pubkey (str, optional) - The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception + + Default: `None` + + disable_utxo_locks (bool, optional) - By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs + + Default: `False` + + extended_tx_info (bool, optional) - When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee + + Default: `False` + + p2sh_pretx_txid (str, optional) - The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction + + Default: `None` + + old_style_api (bool, optional) - Use the old style API + + Default: `True` + + segwit (bool, optional) - Use segwit + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000236a210d1e454cefefcbe161ff1a94d78892739ddc14a84b570af630af96858de42ab6cf6e150406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "destination": "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + "flags": 7, + "memo": "FFFF" + }, + "name": "sweep" + } + } + ``` + +## Group Assets + +### Get Valid Assets [GET /assets{?offset}{?limit}] + +Returns the valid assets + ++ Parameters + + offset: `0` (int, optional) - The offset of the assets to return + + Default: `0` + + limit: `5` (int, optional) - The limit of the assets to return + + Default: `100` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "asset": "A100000000000000000", + "asset_longname": null + }, + { + "asset": "A1000000000000000000", + "asset_longname": null + }, + { + "asset": "A10000000000000000000", + "asset_longname": null + }, + { + "asset": "A10000000000000000001", + "asset_longname": null + }, + { + "asset": "A10000000000000000002", + "asset_longname": null + } + ] + } + ``` + +### Get Asset Info [GET /assets/{asset}] + +Returns the asset information + ++ Parameters + + asset: `UNNEGOTIABLE` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": { + "asset": "UNNEGOTIABLE", + "asset_longname": null, + "owner": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "divisible": false, + "locked": false, + "supply": 1, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "holder_count": 1 + } + } + ``` + +### Get Asset Balances [GET /assets/{asset}/balances{?exclude_zero_balances}] + +Returns the asset balances + ++ Parameters + + asset: `UNNEGOTIABLE` (str, required) - The asset to return + + exclude_zero_balances: `True` (bool, optional) - Whether to exclude zero balances + + Default: `True` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "quantity": 1 + } + ] + } + ``` + +### Get Balance By Address And Asset [GET /assets/{asset}/balances/{address}] + +Returns the balance of an address and asset + ++ Parameters + + address: `1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs` (str, required) - The address to return + + asset: `XCP` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": { + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000 + } + } + ``` + +### Get Orders By Asset [GET /assets/{asset}/orders{?status}] + +Returns the orders of an asset + ++ Parameters + + asset: `NEEDPEPE` (str, required) - The asset to return + + status: `filled` (str, optional) - The status of the orders to return + + Default: `open` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 825373, + "tx_hash": "0129611a0aece52adddf6d929e75c703baa9cdcb7e4ce887aa859f9640aa9640", + "block_index": 455461, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 400000000000, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 46098, + "fee_provided_remaining": 46098, + "status": "filled" + }, + { + "tx_index": 2225134, + "tx_hash": "5b6e0c741d765ebd883dc16eecfb5c340c52865cabf297ca2c1432437c1348b7", + "block_index": 772817, + "source": "1FnM7akSCD8G3fRQHCUEXRCfL35gptsPZB", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "XCP", + "get_quantity": 80800000000, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 777817, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 5544, + "fee_provided_remaining": 5544, + "status": "filled" + }, + { + "tx_index": 1946026, + "tx_hash": "75dc6ee1f67317e674ef33b617d3a9839ee53bf4a2e8274c88d6202d4d89b59a", + "block_index": 727444, + "source": "1GotRejB6XsGgMsM79TvcypeanDJRJbMtg", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "XCP", + "get_quantity": 70000000000, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 732381, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 264, + "fee_provided_remaining": 264, + "status": "filled" + }, + { + "tx_index": 2202451, + "tx_hash": "77f568fc6604dbe209d2ea1b0158d7de20723c0178107eb570f4f2a719b0d7c7", + "block_index": 772817, + "source": "184gKLQTtQU29LXbxbYJkUV4if9SmW6v2d", + "give_asset": "XCP", + "give_quantity": 80800000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 773300, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 264, + "fee_provided_remaining": 264, + "status": "filled" + }, + { + "tx_index": 825411, + "tx_hash": "7b2369f40078f4d98a3d3a7733315a1c4efd7977c75f7066dd447d5c7eed7f20", + "block_index": 455461, + "source": "18cmgoX99Nrm411YKpmTQsp23qczWdxS6w", + "give_asset": "PEPECASH", + "give_quantity": 300000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 460461, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 40000, + "fee_provided_remaining": 40000, + "status": "filled" + }, + { + "tx_index": 825403, + "tx_hash": "7e1abf6ad57eb61227015fc7a333da034b4dd2f1c4e23cf106864b60a20feef7", + "block_index": 455460, + "source": "18cmgoX99Nrm411YKpmTQsp23qczWdxS6w", + "give_asset": "PEPECASH", + "give_quantity": 200000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456460, + "fee_required": 20000, + "fee_required_remaining": 20000, + "fee_provided": 50766, + "fee_provided_remaining": 50766, + "status": "filled" + }, + { + "tx_index": 825370, + "tx_hash": "8e4d324407b62de773af53f8f7a556882ac82a217c216491a28072f293918fe6", + "block_index": 455457, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 100000000000, + "get_remaining": -1100000000, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 75791, + "fee_provided_remaining": 75791, + "status": "filled" + }, + { + "tx_index": 825413, + "tx_hash": "927878fa98edb6d24310c45254c324f3d5a7f625e2a3a0e7fd1e749b49493750", + "block_index": 455461, + "source": "18cmgoX99Nrm411YKpmTQsp23qczWdxS6w", + "give_asset": "PEPECASH", + "give_quantity": 400000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 460461, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 40000, + "fee_provided_remaining": 40000, + "status": "filled" + }, + { + "tx_index": 1946587, + "tx_hash": "b747f290cbbad6faa1c1c05d5c6d001b5a3ef487027bb0d4eefcdc9f6e865c39", + "block_index": 727444, + "source": "1AtcSh7uxenQ6AR5xqr6agAegWRUF5N4uh", + "give_asset": "XCP", + "give_quantity": 70000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 732444, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 792, + "fee_provided_remaining": 792, + "status": "filled" + }, + { + "tx_index": 825371, + "tx_hash": "b83c96217214decb6316c3619bc88a3471d17e46eb3708406c8f878dedd61610", + "block_index": 455460, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 200000000000, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 46098, + "fee_provided_remaining": 46098, + "status": "filled" + }, + { + "tx_index": 825372, + "tx_hash": "e32154f8ade796df0b121604de140703d062d22d1e82e77e629e6096668c812f", + "block_index": 455461, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 300000000000, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 46098, + "fee_provided_remaining": 46098, + "status": "filled" + } + ] + } + ``` + +### Get Credits By Asset [GET /assets/{asset}/credits{?limit}{?offset}] + +Returns the credits of an asset + ++ Parameters + + asset: `UNNEGOTIABLE` (str, required) - The asset to return + + limit: `5` (int, optional) - The maximum number of credits to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the credits to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 840464, + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "quantity": 1, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + } + ] + } + ``` + +### Get Debits By Asset [GET /assets/{asset}/debits{?limit}{?offset}] + +Returns the debits of an asset + ++ Parameters + + asset: `XCP` (str, required) - The asset to return + + limit: `5` (int, optional) - The maximum number of debits to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the debits to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "block_index": 280091, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1000000000, + "action": "send", + "event": "1c20d6596f6be031c94def5ad93a52217d76371885adcc53c91c3b1eaf76ccce", + "tx_index": 729 + }, + { + "block_index": 280112, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "action": "send", + "event": "4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6", + "tx_index": 749 + }, + { + "block_index": 280112, + "address": "1PMacKVWDszkBRbb2iWWvX63BwhKUTsSBd", + "asset": "XCP", + "quantity": 100000000, + "action": "send", + "event": "057d10cc33455f4f7af44d2f030b3866e3a16416ecf984e304c76abe98393c1d", + "tx_index": 752 + }, + { + "block_index": 280114, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "action": "send", + "event": "3ac6ea5b329832e2dc31ead6c5277beccb7d95f0d9f20f256f97067223c81e00", + "tx_index": 755 + }, + { + "block_index": 280156, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "action": "send", + "event": "66fc1409ac6646bd8c267de89c57d2204e31bb6dfce9ee2a3ab18416fadf9e9c", + "tx_index": 766 + } + ] + } + ``` + +### Get Dividends [GET /assets/{asset}/dividends] + +Returns the dividends of an asset + ++ Parameters + + asset: `GMONEYPEPE` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 1914456, + "tx_hash": "30760e413947ebdc80ed7a5ada1bd4466800b87e9976bbe811ad4e2b46546359", + "block_index": 724381, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "ENDTHEFED", + "quantity_per_unit": 1, + "fee_paid": 2520000, + "status": "valid" + }, + { + "tx_index": 1915246, + "tx_hash": "827794cbab3299f80a5b8b8cb8ec29ec3aee1373f7da2c05a156bed902bf4684", + "block_index": 724479, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "TRUMPDANCING", + "quantity_per_unit": 100, + "fee_paid": 2520000, + "status": "valid" + }, + { + "tx_index": 1920208, + "tx_hash": "7014f1e259531ba9632ca5000c35df5bd47f237318e48955900453ce9c07e917", + "block_index": 724931, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "CTRWOJACK", + "quantity_per_unit": 1111, + "fee_paid": 2700000, + "status": "valid" + }, + { + "tx_index": 1927909, + "tx_hash": "5556fd2b0802cf3bc0abd5001ecbac3adbc5b7c5c46a145a78daeef358c308de", + "block_index": 725654, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "WHITERUSSIAN", + "quantity_per_unit": 1, + "fee_paid": 3220000, + "status": "valid" + }, + { + "tx_index": 1983693, + "tx_hash": "cda646285cc63f758d19b5403070f23e2a6e4b34eb3b86b63a0f56f971345657", + "block_index": 730568, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "A4520591452211866149", + "quantity_per_unit": 1, + "fee_paid": 4040000, + "status": "valid" + }, + { + "tx_index": 1983842, + "tx_hash": "e4b73dc974cc279b873b78e5dc4a347c08788b02143ae27aa0582f900289be10", + "block_index": 730588, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "NCSWIC", + "quantity_per_unit": 1, + "fee_paid": 4040000, + "status": "valid" + }, + { + "tx_index": 1996395, + "tx_hash": "b342feb1421df107010ad3c8ee2043ded802bdf6cd619862459da3d0f87d6a99", + "block_index": 731994, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "FUCKTHEFED", + "quantity_per_unit": 1, + "fee_paid": 4380000, + "status": "valid" + }, + { + "tx_index": 2035947, + "tx_hash": "02d715fd9e8b7bbc782b1b2d92a1b9ffae9326bfc88ba76c453c515ad7c8c2bc", + "block_index": 738763, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "HOLDTHELINE", + "quantity_per_unit": 1, + "fee_paid": 4940000, + "status": "valid" + }, + { + "tx_index": 2174481, + "tx_hash": "b935a06fc34d8fa4f0c526984085b1b12c78e899415e595b625f1bee84ce3709", + "block_index": 762733, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "EOXIXIZERO", + "quantity_per_unit": 1, + "fee_paid": 6500000, + "status": "valid" + }, + { + "tx_index": 2198534, + "tx_hash": "a063e9a745b9f6bc3201f72abff196de20ec106bcc71d820673d516ddbb3aa90", + "block_index": 767569, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "TRUMPCARDS", + "quantity_per_unit": 1, + "fee_paid": 6660000, + "status": "valid" + }, + { + "tx_index": 2704948, + "tx_hash": "437102ca4698f63a12e369f6168e3c7f5f8eef3e225395d515775673e33d39c1", + "block_index": 832745, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "FUCKYOUWAR", + "quantity_per_unit": 1, + "fee_paid": 6840000, + "status": "valid" + }, + { + "tx_index": 2704949, + "tx_hash": "7d3807cc58fa2d9751b2b0089bfa8fa86ef795821be6d8e9418ab3a819eba299", + "block_index": 832745, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "MEDICINEPEPE", + "quantity_per_unit": 1, + "fee_paid": 6840000, + "status": "valid" + } + ] + } + ``` + +### Get Issuances By Asset [GET /assets/{asset}/issuances] + +Returns the issuances of an asset + ++ Parameters + + asset: `UNNEGOTIABLE` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "msg_index": 0, + "block_index": 840464, + "asset": "UNNEGOTIABLE", + "quantity": 1, + "divisible": 0, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "transfer": 0, + "callable": 0, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "fee_paid": 50000000, + "locked": 0, + "status": "valid", + "asset_longname": null, + "reset": 0 + } + ] + } + ``` + +### Get Sends Or Receives By Asset [GET /assets/{asset}/sends{?limit}{?offset}] + +Returns the sends of an asset + ++ Parameters + + asset: `XCP` (str, required) - The asset to return + + limit: `5` (int, optional) - The maximum number of sends to return + + Default: `100` + + offset: `0` (int, optional) - The offset of the sends to return + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 729, + "tx_hash": "1c20d6596f6be031c94def5ad93a52217d76371885adcc53c91c3b1eaf76ccce", + "block_index": 280091, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1000000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 749, + "tx_hash": "4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6", + "block_index": 280112, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 752, + "tx_hash": "057d10cc33455f4f7af44d2f030b3866e3a16416ecf984e304c76abe98393c1d", + "block_index": 280112, + "source": "1PMacKVWDszkBRbb2iWWvX63BwhKUTsSBd", + "destination": "1PMacKVWDszkBRbb2iWWvX63BwhKUTsSBd", + "asset": "XCP", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 755, + "tx_hash": "3ac6ea5b329832e2dc31ead6c5277beccb7d95f0d9f20f256f97067223c81e00", + "block_index": 280114, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 766, + "tx_hash": "66fc1409ac6646bd8c267de89c57d2204e31bb6dfce9ee2a3ab18416fadf9e9c", + "block_index": 280156, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + } + ``` + +### Get Dispensers By Asset [GET /assets/{asset}/dispensers{?status}] + +Returns the dispensers of an asset + ++ Parameters + + asset: `ERYKAHPEPU` (str, required) - The asset to return + + status (int, optional) - + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + } + ``` + +### Get Dispensers By Address And Asset [GET /assets/{asset}/dispensers/{address}{?status}] + +Returns the dispensers of an address and an asset + ++ Parameters + + address: `bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz` (str, required) - The address to return + + asset: `ERYKAHPEPU` (str, required) - The asset to return + + status (int, optional) - + + Default: `0` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + } + ``` + +### Get Asset Holders [GET /assets/{asset}/holders] + +Returns the holders of an asset + ++ Parameters + + asset: `ERYKAHPEPU` (str, required) - The asset to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "address": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "address_quantity": 63, + "escrow": null + }, + { + "address": "16yRstRXStVJJ1TN2S4DCWifyrCsetpma7", + "address_quantity": 1, + "escrow": null + }, + { + "address": "bc1qsvqsa9arwz30g2z0w09twzn8gz3380h36yxacs", + "address_quantity": 2, + "escrow": null + }, + { + "address": "17PnWBjHkekZKQPVagmTR5HiD51pN8WHC8", + "address_quantity": 1, + "escrow": null + }, + { + "address": "1FRxFpP9XoRsvZFVqGtt4fjjgKe1h5tbAh", + "address_quantity": 1, + "escrow": null + }, + { + "address": "1AdHg2q3M2rMFRgZyZ7RQyNHdwjSib7wSZ", + "address_quantity": 2, + "escrow": null + }, + { + "address": "1CTnziWXidHzY3qT8gwLa1ZxZK37A7HreR", + "address_quantity": 1, + "escrow": null + }, + { + "address": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "address_quantity": 25, + "escrow": null + } + ] + } + ``` + +## Group Orders + +### Get Order [GET /orders/{order_hash}] + +Returns the information of an order + ++ Parameters + + order_hash: `23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776` (str, required) - The hash of the transaction that created the order + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2724132, + "tx_hash": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776", + "block_index": 840381, + "source": "15L7U55PAsHLEpQkZqz62e3eqWd9AHb2DH", + "give_asset": "PEPECASH", + "give_quantity": 6966600000000, + "give_remaining": 900000000000, + "get_asset": "XCP", + "get_quantity": 11076894000, + "get_remaining": 1431000000, + "expiration": 5000, + "expire_index": 843055, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 4488, + "fee_provided_remaining": 4488, + "status": "open" + } + ] + } + ``` + +### Get Order Matches By Order [GET /orders/{order_hash}/matches{?status}] + +Returns the order matches of an order + ++ Parameters + + order_hash: `5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947` (str, required) - The hash of the transaction that created the order + + status: `completed` (str, optional) - The status of the order matches to return + + Default: `pending` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "id": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776_5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947", + "tx0_index": 2724132, + "tx0_hash": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776", + "tx0_address": "15L7U55PAsHLEpQkZqz62e3eqWd9AHb2DH", + "tx1_index": 2726591, + "tx1_hash": "5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947", + "tx1_address": "15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA", + "forward_asset": "PEPECASH", + "forward_quantity": 6066600000000, + "backward_asset": "XCP", + "backward_quantity": 9645894000, + "tx0_block_index": 838055, + "tx1_block_index": 840381, + "block_index": 840381, + "tx0_expiration": 5000, + "tx1_expiration": 8064, + "match_expire_index": 840401, + "fee_paid": 0, + "status": "completed" + } + ] + } + ``` + +### Get Btcpays By Order [GET /orders/{order_hash}/btcpays] + +Returns the BTC pays of an order + ++ Parameters + + order_hash: `299b5b648f54eacb839f3487232d49aea373cdd681b706d4cc0b5e0b03688db4` (str, required) - The hash of the transaction that created the order + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2719343, + "tx_hash": "6cfa7f31b43a46e5ad74a9db810bd6cac56235a8ebc73ec63d01b38ea7ea2414", + "block_index": 836188, + "source": "1NfJnJdAdmm2rJCFW54NsAKqqTTMexCNJ3", + "destination": "1BepkwAhEmEuEGF349XjmEUrRvoy9a7Biv", + "btc_amount": 4500000, + "order_match_id": "0a1387df82a8a7e9cec01c52c8fee01f6995c4e39dc5804e1d2bf40d9368f5c5_299b5b648f54eacb839f3487232d49aea373cdd681b706d4cc0b5e0b03688db4", + "status": "valid" + } + ] + } + ``` + +## Group Bets + +### Get Bet [GET /bets/{bet_hash}] + +Returns the information of a bet + ++ Parameters + + bet_hash: `5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed` (str, required) - The hash of the transaction that created the bet + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 15106, + "tx_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "block_index": 304063, + "source": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 3, + "deadline": 1401828300, + "wager_quantity": 50000000, + "wager_remaining": 0, + "counterwager_quantity": 50000000, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 11, + "expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "filled" + } + ] + } + ``` + +### Get Bet Matches By Bet [GET /bets/{bet_hash}/matches{?status}] + +Returns the bet matches of a bet + ++ Parameters + + bet_hash: `5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed` (str, required) - The hash of the transaction that created the bet + + status: `expired` (str, optional) - The status of the bet matches + + Default: `pending` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "id": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed_cb5f888c299a50967d523513daed71636d927e6ef3dbda85feb11ff112ae4330", + "tx0_index": 15106, + "tx0_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "tx0_address": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "tx1_index": 15108, + "tx1_hash": "cb5f888c299a50967d523513daed71636d927e6ef3dbda85feb11ff112ae4330", + "tx1_address": "1PTqJmRCMGs4qBEh2APAFSrBv95Uf1hfiD", + "tx0_bet_type": 3, + "tx1_bet_type": 2, + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "initial_value": -1, + "deadline": 1401828300, + "target_value": 1.0, + "leverage": 5040, + "forward_quantity": 50000000, + "backward_quantity": 50000000, + "tx0_block_index": 304062, + "tx1_block_index": 304063, + "block_index": 306379, + "tx0_expiration": 11, + "tx1_expiration": 1459, + "match_expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "expired" + } + ] + } + ``` + +### Get Resolutions By Bet [GET /bets/{bet_hash}/resolutions] + +Returns the resolutions of a bet + ++ Parameters + + bet_hash: `36bbbb7dbd85054dac140a8ad8204eda2ee859545528bd2a9da69ad77c277ace` (str, required) - The hash of the transaction that created the bet + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "bet_match_id": "36bbbb7dbd85054dac140a8ad8204eda2ee859545528bd2a9da69ad77c277ace_d70ee4e44f02fe6258ee0c267f33f304a0fc61d4ce424852f58c28967dc1924f", + "bet_match_type_id": 5, + "block_index": 401128, + "winner": "Equal", + "settled": null, + "bull_credit": null, + "bear_credit": null, + "escrow_less_fee": 2000000, + "fee": 0 + } + ] + } + ``` + +## Group Burns + +### Get All Burns [GET /burns{?status}{?offset}{?limit}] + +Returns the burns + ++ Parameters + + status: `valid` (str, optional) - The status of the burns to return + + Default: `valid` + + offset: `10` (int, optional) - The offset of the burns to return + + Default: `0` + + limit: `5` (int, optional) - The limit of the burns to return + + Default: `100` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 10, + "tx_hash": "41bbe1ec81da008a0e92758efb6084af3a6b6acf483983456ec797ee59c0e0f1", + "block_index": 278511, + "source": "12crRpZpn93PKTQ4WYxHMw4xi6ckh1CFR3", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 11, + "tx_hash": "c403a92281b568c7d428d942354d026594dc54ae35c21f53ecf5c918208c45de", + "block_index": 278511, + "source": "13UXh9dBEhA48gJiegJNodqe91PK88f4pW", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 12, + "tx_hash": "749ba1c2bd314f7b98e9cfb44575495b4ad2cf624901c65488fbc4f57a3dc0ac", + "block_index": 278511, + "source": "19Ht3rkW7JB9VuC7rsZEGZju96ujzchaZZ", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 13, + "tx_hash": "da330160b71138f9bda5e126df0d5d6248c0879d88e16255c74135274d8ebd27", + "block_index": 278511, + "source": "16Fu8Edsvxqixg6VnaHKPWE2TEsqQMwXfV", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 14, + "tx_hash": "66994176733650e77ae0cf34349f63e6538649f40f86d2719013d915bbb7701e", + "block_index": 278517, + "source": "14FFaRsfzYQxhZQv1YsMn65MvMLfJShgM8", + "burned": 99900000, + "earned": 147970063636, + "status": "valid" + } + ] + } + ``` + +## Group Dispensers + +### Get Dispenser Info By Hash [GET /dispensers/{dispenser_hash}] + +Returns the dispenser information by tx_hash + ++ Parameters + + dispenser_hash: `753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a` (str, required) - The hash of the dispenser to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2536311, + "tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "asset": "FLOCK", + "give_quantity": 10000000000, + "escrow_quantity": 250000000000, + "satoshirate": 330000, + "status": 0, + "give_remaining": 140000000000, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "dispense_count": 2, + "asset_longname": null + } + ] + } + ``` + +### Get Dispenses By Dispenser [GET /dispensers/{dispenser_hash}/dispenses] + +Returns the dispenses of a dispenser + ++ Parameters + + dispenser_hash: `753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a` (str, required) - The hash of the dispenser to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_index": 2610745, + "dispense_index": 0, + "tx_hash": "8c95cc6afc8fd466c784fd1c02749c585988999bbc66251b944c443dc31af757", + "block_index": 821450, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "1FKYM1CP9RfttJhNG8HTNQdE2uV3YvwbRB", + "asset": "FLOCK", + "dispense_quantity": 20000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + }, + { + "tx_index": 2726580, + "dispense_index": 0, + "tx_hash": "e7f0f2c9bef7a492b714a5952ec61b283be344419c5bc33f405f9af41ebfa48b", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "bc1qzcdkhnexpjc8wvkyrpyrsn0f5xzcpu877mjmgj", + "asset": "FLOCK", + "dispense_quantity": 90000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + } + ] + } + ``` + +## Group Events + +### Get All Events [GET /events{?last}{?limit}] + +Returns all events + ++ Parameters + + last: `10665092` (int, optional) - The last event index to return + + Default: `None` + + limit: `5` (int, optional) - The maximum number of events to return + + Default: `100` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event_index": 10665092, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665091, + "event": "ENHANCED_SEND", + "bindings": { + "asset": "THOTHPEPE", + "block_index": 744232, + "destination": "13re7J5Y5a8nZZSp8o1a3sEUqGik4NMXhS", + "memo": null, + "quantity": 1, + "source": "173cE6ScUFCmBLCqZeG18ij6r9KHRPbAjC", + "status": "valid", + "tx_hash": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665090, + "event": "CREDIT", + "bindings": { + "address": "13re7J5Y5a8nZZSp8o1a3sEUqGik4NMXhS", + "asset": "THOTHPEPE", + "block_index": 744232, + "calling_function": "send", + "event": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "quantity": 1, + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665089, + "event": "DEBIT", + "bindings": { + "action": "send", + "address": "173cE6ScUFCmBLCqZeG18ij6r9KHRPbAjC", + "asset": "THOTHPEPE", + "block_index": 744232, + "event": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "quantity": 1, + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665088, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "bbb2dfa7e7a32288a702ef0091ece8b2a929f94fd967a18e6071cd9c2b085eaf", + "tx_index": 2056159 + }, + "block_index": 744232, + "timestamp": 1712256340 + } + ] + } + ``` + +### Get Event By Index [GET /events/{event_index}] + +Returns the event of an index + ++ Parameters + + event_index: `10665092` (int, required) - The index of the event to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event_index": 10665092, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + } + ] + } + ``` + +### Get All Events Counts [GET /events/counts] + +Returns the event counts of all blocks ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event": "ASSET_CREATION", + "event_count": 235860 + }, + { + "event": "ASSET_DESTRUCTION", + "event_count": 11141 + }, + { + "event": "ASSET_DIVIDEND", + "event_count": 4092 + }, + { + "event": "ASSET_ISSUANCE", + "event_count": 322678 + }, + { + "event": "ASSET_TRANSFER", + "event_count": 10639 + }, + { + "event": "BET_EXPIRATION", + "event_count": 588 + }, + { + "event": "BET_MATCH", + "event_count": 397 + }, + { + "event": "BET_MATCH_EXPIRATION", + "event_count": 9 + }, + { + "event": "BET_MATCH_RESOLUTON", + "event_count": 387 + }, + { + "event": "BET_MATCH_UPDATE", + "event_count": 397 + }, + { + "event": "BET_UPDATE", + "event_count": 1474 + }, + { + "event": "BLOCK_PARSED", + "event_count": 562364 + }, + { + "event": "BROADCAST", + "event_count": 106518 + }, + { + "event": "BTC_PAY", + "event_count": 2921 + }, + { + "event": "BURN", + "event_count": 2576 + }, + { + "event": "CANCEL_BET", + "event_count": 101 + }, + { + "event": "CANCEL_ORDER", + "event_count": 80168 + }, + { + "event": "CREDIT", + "event_count": 3659293 + }, + { + "event": "DEBIT", + "event_count": 2617404 + }, + { + "event": "DISPENSE", + "event_count": 190873 + }, + { + "event": "DISPENSER_UPDATE", + "event_count": 228954 + }, + { + "event": "ENHANCED_SEND", + "event_count": 538426 + }, + { + "event": "MPMA_SEND", + "event_count": 279142 + }, + { + "event": "NEW_BLOCK", + "event_count": 1992 + }, + { + "event": "NEW_TRANSACTION", + "event_count": 4498 + }, + { + "event": "NEW_TRANSACTION_OUTPUT", + "event_count": 596 + }, + { + "event": "OPEN_BET", + "event_count": 1149 + }, + { + "event": "OPEN_DISPENSER", + "event_count": 88229 + }, + { + "event": "OPEN_ORDER", + "event_count": 530117 + }, + { + "event": "OPEN_RPS", + "event_count": 266 + }, + { + "event": "ORDER_EXPIRATION", + "event_count": 195968 + }, + { + "event": "ORDER_FILLED", + "event_count": 805 + }, + { + "event": "ORDER_MATCH", + "event_count": 209415 + }, + { + "event": "ORDER_MATCH_EXPIRATION", + "event_count": 20860 + }, + { + "event": "ORDER_MATCH_UPDATE", + "event_count": 23689 + }, + { + "event": "ORDER_UPDATE", + "event_count": 732646 + }, + { + "event": "REFILL_DISPENSER", + "event_count": 187 + }, + { + "event": "RESET_ISSUANCE", + "event_count": 454 + }, + { + "event": "RPS_EXPIRATION", + "event_count": 59 + }, + { + "event": "RPS_MATCH", + "event_count": 171 + }, + { + "event": "RPS_MATCH_EXPIRATION", + "event_count": 145 + }, + { + "event": "RPS_MATCH_UPDATE", + "event_count": 271 + }, + { + "event": "RPS_RESOLVE", + "event_count": 129 + }, + { + "event": "RPS_UPDATE", + "event_count": 540 + }, + { + "event": "SEND", + "event_count": 805983 + }, + { + "event": "SWEEP", + "event_count": 1020 + }, + { + "event": "TRANSACTION_PARSED", + "event_count": 2723802 + } + ] + } + ``` + +### Get Events By Name [GET /events/{event}{?last}{?limit}] + +Returns the events filtered by event name + ++ Parameters + + event: `CREDIT` (str, required) - The event to return + + last: `10665092` (int, optional) - The last event index to return + + Default: `None` + + limit: `5` (int, optional) - The maximum number of events to return + + Default: `100` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "event_index": 10665090, + "event": "CREDIT", + "bindings": { + "address": "13re7J5Y5a8nZZSp8o1a3sEUqGik4NMXhS", + "asset": "THOTHPEPE", + "block_index": 744232, + "calling_function": "send", + "event": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "quantity": 1, + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665085, + "event": "CREDIT", + "bindings": { + "address": "1LfDk3Ex9KPYS6L1WGwNdt1TvEg6Le8uq", + "asset": "XCP", + "block_index": 744232, + "calling_function": "dispense", + "event": "bbb2dfa7e7a32288a702ef0091ece8b2a929f94fd967a18e6071cd9c2b085eaf", + "quantity": 10000000000, + "tx_index": 2056159 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665082, + "event": "CREDIT", + "bindings": { + "address": "173cE6ScUFCmBLCqZeG18ij6r9KHRPbAjC", + "asset": "FREEDOMKEK", + "block_index": 744232, + "calling_function": "send", + "event": "b419d19729c2be813405c548431f4840d5c909b875f94b7c56aeca134e328ef6", + "quantity": 1, + "tx_index": 2056158 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665078, + "event": "CREDIT", + "bindings": { + "address": "1P8nYZwLmecAkQUHsx2H9Nkxd51UJ2Asau", + "asset": "PEPEFRIDAY", + "block_index": 744232, + "calling_function": "send", + "event": "145ebf6c563c4e91a2bc488954ef701dad730fc065697979c80d6d85cbba63e1", + "quantity": 1, + "tx_index": 2056157 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665074, + "event": "CREDIT", + "bindings": { + "address": "1NzDQ7HLm6PqJ2Wy6jEKMT7Zw1UbtjUV5a", + "asset": "PEPEFRIDAY", + "block_index": 744232, + "calling_function": "send", + "event": "388c7208d52bf617c1a3eef238a668f694a4f72dc97b3be92562fe636ca646fa", + "quantity": 2, + "tx_index": 2056156 + }, + "block_index": 744232, + "timestamp": 1712256340 + } + ] + } + ``` + +## Group Healthz + +### Check Server Status [GET /healthz{?check_type}] + +Health check route. + ++ Parameters + + check_type: `light` (str, optional) - Type of health check to perform. Options are 'light' and 'heavy' + + Default: `heavy` + ++ Response 200 (application/json) + + ``` + { + "result": { + "status": "Healthy" + } + } + ``` + +## Group Backend + +### Search Raw Transactions [GET /backend/addresses/{address}/transactions{?unconfirmed}{?only_tx_hashes}] + +Returns all transactions involving a given address + ++ Parameters + + address: `14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS` (str, required) - The address to search for + + unconfirmed: `True` (bool, optional) - Include unconfirmed transactions + + Default: `True` + + only_tx_hashes: `True` (bool, optional) - Return only the tx hashes + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_hash": "eae4f1dba4d75bda9dd0de12f69a980be267bbc16b7a280a2a4b40c4b3bbb70a" + }, + { + "tx_hash": "7ec16c461e3ba2d3acae48fcc8f58c04fba9f307b00c391eab507337ddc0bf16" + }, + { + "tx_hash": "ad35f05767aadd39019122b4f4828ccb059b8121c07be6d36eb1e2ddbe9ac317" + }, + { + "tx_hash": "3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018" + }, + { + "tx_hash": "aba5810714aa6196fec5538a83bbc281077a84ef2cbce2045b4c9f3c4439f14f" + }, + { + "tx_hash": "23758832e0fc92a7ea303623b8f743219cb8e637e7e7ac9fb6f90641efac9379" + }, + { + "tx_hash": "98bef616ef265dd2f6004683e908d7df97e0c5f322cdf2fb2ebea9a9131cfa79" + }, + { + "tx_hash": "687b875d1dc472aa2fb994c5753c9b9b56e5c6fd1a6de18a92fcb3dc7ba8067e" + }, + { + "tx_hash": "ec97c11ff5cb318505ebe20d7aa3c033816824a79f9a49821ffb584ed7d6c78f" + }, + { + "tx_hash": "c732f0906eeada2113524c6652c17b2784780110bffd4333eb8f719ac0eff3be" + }, + { + "tx_hash": "2c8bc3eede9ec60d26c6fd7f44829adc64da593552044a28c673022220f560c3" + }, + { + "tx_hash": "a209e345549cffef6e2190b53ac0222afc965fd618843df5ccbd645a6a7999ee" + } + ] + } + ``` + +### Get Oldest Tx [GET /backend/addresses/{address}/transactions/oldest{?block_index}] + +Get the oldest transaction for an address. + ++ Parameters + + address: `14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS` (str, required) - The address to search for. + + block_index (int, optional) - The block index to search from. + + Default: `None` + ++ Response 200 (application/json) + + ``` + { + "result": { + "block_index": 833187, + "tx_hash": "2c8bc3eede9ec60d26c6fd7f44829adc64da593552044a28c673022220f560c3" + } + } + ``` + +### Get Unspent Txouts [GET /backend/addresses/{address}/utxos{?unconfirmed}{?unspent_tx_hash}] + +Returns a list of unspent outputs for a specific address + ++ Parameters + + address: `14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS` (str, required) - The address to search for + + unconfirmed (bool, optional) - Include unconfirmed transactions + + Default: `False` + + unspent_tx_hash (str, optional) - Filter by unspent_tx_hash + + Default: `None` + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "vout": 6, + "height": 833559, + "value": 34611, + "confirmations": 7083, + "amount": 0.00034611, + "txid": "98bef616ef265dd2f6004683e908d7df97e0c5f322cdf2fb2ebea9a9131cfa79" + }, + { + "vout": 0, + "height": 833187, + "value": 619481, + "confirmations": 7455, + "amount": 0.00619481, + "txid": "2c8bc3eede9ec60d26c6fd7f44829adc64da593552044a28c673022220f560c3" + }, + { + "vout": 0, + "height": 837379, + "value": 992721, + "confirmations": 3263, + "amount": 0.00992721, + "txid": "ad35f05767aadd39019122b4f4828ccb059b8121c07be6d36eb1e2ddbe9ac317" + }, + { + "vout": 0, + "height": 840640, + "value": 838185, + "confirmations": 2, + "amount": 0.00838185, + "txid": "3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018" + }, + { + "vout": 0, + "height": 839421, + "value": 336973, + "confirmations": 1221, + "amount": 0.00336973, + "txid": "c732f0906eeada2113524c6652c17b2784780110bffd4333eb8f719ac0eff3be" + }, + { + "vout": 0, + "height": 839462, + "value": 78615, + "confirmations": 1180, + "amount": 0.00078615, + "txid": "eae4f1dba4d75bda9dd0de12f69a980be267bbc16b7a280a2a4b40c4b3bbb70a" + }, + { + "vout": 0, + "height": 838442, + "value": 557283, + "confirmations": 2200, + "amount": 0.00557283, + "txid": "aba5810714aa6196fec5538a83bbc281077a84ef2cbce2045b4c9f3c4439f14f" + }, + { + "vout": 0, + "height": 838608, + "value": 77148, + "confirmations": 2034, + "amount": 0.00077148, + "txid": "ec97c11ff5cb318505ebe20d7aa3c033816824a79f9a49821ffb584ed7d6c78f" + }, + { + "vout": 0, + "height": 837402, + "value": 70501, + "confirmations": 3240, + "amount": 0.00070501, + "txid": "687b875d1dc472aa2fb994c5753c9b9b56e5c6fd1a6de18a92fcb3dc7ba8067e" + }, + { + "vout": 0, + "height": 839021, + "value": 12354, + "confirmations": 1621, + "amount": 0.00012354, + "txid": "23758832e0fc92a7ea303623b8f743219cb8e637e7e7ac9fb6f90641efac9379" + } + ] + } + ``` + +### Pubkeyhash To Pubkey [GET /backend/addresses/{address}/pubkey{?provided_pubkeys}] + +Get pubkey for an address. + ++ Parameters + + address: `14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS` (str, required) - Address to get pubkey for. + + provided_pubkeys (str, optional) - Comma separated list of provided pubkeys. + + Default: `None` + ++ Response 200 (application/json) + + ``` + { + "result": "0388ef0905568d425f1ffd4031d93dda4ef0e220c9b5fc4a6cbaf11544c4a5ca49" + } + ``` + +### Get Raw Transaction [GET /backend/transactions/{tx_hash}{?verbose}] + +Get a raw transaction from the blockchain + ++ Parameters + + tx_hash: `3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018` (str, required) - The transaction hash + + verbose: `True` (bool, optional) - Whether to return JSON output or raw hex + + Default: `False` + ++ Response 200 (application/json) + + ``` + { + "result": { + "txid": "3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018", + "hash": "417c24d7a5539bc5b8496e26528382ac297a85a1c6b891b220f72712405ec300", + "version": 2, + "size": 195, + "vsize": 113, + "weight": 450, + "locktime": 0, + "vin": [ + { + "txid": "fc940430637d22a3d276bde8f7eb489760265cab642d8392f6017d73df94cd7a", + "vout": 2, + "scriptSig": { + "asm": "", + "hex": "" + }, + "txinwitness": [ + "3045022100e4a30e5c0e0f7a28dfcec566cda00d0775a4207744ed6f223a4234cbed87a8ac02205b2403279ba7d8235ea1e8b6497465b97b46f3b3066a58c326822a9b1c25b4a501", + "020e66cffeb4657b40a89063340cf7066030af3c6ce55744ed3570a7aecaa6b0da" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00838185, + "n": 0, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 25f70b0f1512c1742d3301fe34370894c79127bb OP_EQUALVERIFY OP_CHECKSIG", + "desc": "addr(14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS)#68uhm9u9", + "hex": "76a91425f70b0f1512c1742d3301fe34370894c79127bb88ac", + "address": "14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS", + "type": "pubkeyhash" + } + } + ], + "hex": "020000000001017acd94df737d01f692832d64ab5c26609748ebf7e8bd76d2a3227d63300494fc0200000000ffffffff0129ca0c00000000001976a91425f70b0f1512c1742d3301fe34370894c79127bb88ac02483045022100e4a30e5c0e0f7a28dfcec566cda00d0775a4207744ed6f223a4234cbed87a8ac02205b2403279ba7d8235ea1e8b6497465b97b46f3b3066a58c326822a9b1c25b4a50121020e66cffeb4657b40a89063340cf7066030af3c6ce55744ed3570a7aecaa6b0da00000000", + "blockhash": "000000000000000000020f596ed481076b7754143284b47fc8d32642202e5f76", + "confirmations": 2, + "time": 1713951767, + "blocktime": 1713951767 + } + } + ``` + +### Fee Per Kb [GET /backend/estimatesmartfee{?conf_target}{?mode}] + +Get the fee per kilobyte for a transaction to be confirmed in `conf_target` blocks. + ++ Parameters + + conf_target: `2` (int, optional) - Confirmation target in blocks (1 - 1008) + + Default: `3` + + mode: `CONSERVATIVE` (str, optional) - The fee estimate mode. + + Default: `CONSERVATIVE` + ++ Response 200 (application/json) + + ``` + { + "result": 295443 + } + ``` + +## Group Mempool + +### Get All Mempool Events [GET /mempool/events] + +Returns all mempool events ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "event": "NEW_TRANSACTION", + "bindings": { + "block_hash": "mempool", + "block_index": 9999999, + "block_time": 1713952590, + "btc_amount": 0, + "data": "0200454ceacf416ccf0000000000000001005461639d06ebc42d541b54b1c5525543ae4d6db3", + "destination": "", + "fee": 9900, + "source": "14PxDTVUMCjLoAcGPZGQf6cEtn7yLzdHp1", + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "tx_index": 2726767 + }, + "timestamp": 1713952691 + }, + { + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "event": "ENHANCED_SEND", + "bindings": { + "asset": "FIERCERABBIT", + "destination": "18hARq2fFJxiypHSnZ8yLcbPNpUfaozD8U", + "memo": null, + "quantity": 1, + "source": "14PxDTVUMCjLoAcGPZGQf6cEtn7yLzdHp1", + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e" + }, + "timestamp": 1713952691 + }, + { + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "tx_index": 2726767 + }, + "timestamp": 1713952691 + } + ] + } + ``` + +### Get Mempool Events By Name [GET /mempool/events/{event}] + +Returns the mempool events filtered by event name + ++ Parameters + + event: `OPEN_ORDER` (str, required) - The event to return + ++ Response 200 (application/json) + + ``` + { + "result": [ + { + "tx_hash": "90ba95c4578b9ab7866515d66736c5b4132e88a0bd9b0fca7b2f1be830a1bb81", + "event": "OPEN_ORDER", + "bindings": { + "expiration": 5000, + "expire_index": 10004999, + "fee_provided": 5016, + "fee_provided_remaining": 5016, + "fee_required": 0, + "fee_required_remaining": 0, + "get_asset": "XCP", + "get_quantity": 3300000000, + "get_remaining": 3300000000, + "give_asset": "PEPEPASSPORT", + "give_quantity": 100000000, + "give_remaining": 100000000, + "source": "1A36UrLHxeg9ABoS4zPsRUegyCWTWER2kF", + "tx_hash": "90ba95c4578b9ab7866515d66736c5b4132e88a0bd9b0fca7b2f1be830a1bb81" + }, + "timestamp": 1713952690 + }, + { + "tx_hash": "bc553f3d4349a266b70e7ed98e2198a18d634a5b247997f59817f69e19de2ad6", + "event": "OPEN_ORDER", + "bindings": { + "expiration": 5000, + "expire_index": 10004999, + "fee_provided": 5016, + "fee_provided_remaining": 5016, + "fee_required": 0, + "fee_required_remaining": 0, + "get_asset": "XCP", + "get_quantity": 1185000000, + "get_remaining": 1185000000, + "give_asset": "FRATPEPE", + "give_quantity": 3, + "give_remaining": 3, + "source": "1A36UrLHxeg9ABoS4zPsRUegyCWTWER2kF", + "tx_hash": "bc553f3d4349a266b70e7ed98e2198a18d634a5b247997f59817f69e19de2ad6" + }, + "timestamp": 1713952690 + } + ] + } + ``` diff --git a/counterparty-core/counterpartycore/cli.py b/counterparty-core/counterpartycore/cli.py index 327c36d549..d7762e88e9 100755 --- a/counterparty-core/counterpartycore/cli.py +++ b/counterparty-core/counterpartycore/cli.py @@ -7,7 +7,7 @@ from termcolor import cprint from counterpartycore import server -from counterpartycore.lib import config, log, setup +from counterpartycore.lib import config, setup logger = logging.getLogger(config.LOGGER_NAME) @@ -170,6 +170,35 @@ "help": f"number of RPC queries by batch (default: {config.DEFAULT_RPC_BATCH_SIZE})", }, ], + [ + ("--api-host",), + { + "default": "localhost", + "help": "the IP of the interface to bind to for providing API access (0.0.0.0 for all interfaces)", + }, + ], + [ + ("--api-port",), + {"type": int, "help": f"port on which to provide the {config.APP_NAME} API"}, + ], + [ + ("--api-user",), + { + "default": "api", + "help": f"required username to use the {config.APP_NAME} API (via HTTP basic auth)", + }, + ], + [ + ("--api-password",), + { + "default": "api", + "help": f"required password (for rpc-user) to use the {config.APP_NAME} API (via HTTP basic auth)", + }, + ], + [ + ("--api-no-allow-cors",), + {"action": "store_true", "default": False, "help": "allow ajax cross domain request"}, + ], [ ("--requests-timeout",), { @@ -204,6 +233,10 @@ "help": "log API requests to the specified file", }, ], + [ + ("--enable-api-v1",), + {"action": "store_true", "default": False, "help": "Enable the API v1"}, + ], [ ("--no-log-files",), {"action": "store_true", "default": False, "help": "Don't write log files"}, @@ -364,59 +397,8 @@ def main(): parser.print_help() exit(0) - # Configuration - init_args = dict( - database_file=args.database_file, - testnet=args.testnet, - testcoin=args.testcoin, - regtest=args.regtest, - customnet=args.customnet, - api_limit_rows=args.api_limit_rows, - backend_connect=args.backend_connect, - backend_port=args.backend_port, - backend_user=args.backend_user, - backend_password=args.backend_password, - backend_ssl=args.backend_ssl, - backend_ssl_no_verify=args.backend_ssl_no_verify, - backend_poll_interval=args.backend_poll_interval, - indexd_connect=args.indexd_connect, - indexd_port=args.indexd_port, - rpc_host=args.rpc_host, - rpc_port=args.rpc_port, - rpc_user=args.rpc_user, - rpc_password=args.rpc_password, - rpc_no_allow_cors=args.rpc_no_allow_cors, - requests_timeout=args.requests_timeout, - rpc_batch_size=args.rpc_batch_size, - check_asset_conservation=args.check_asset_conservation, - force=args.force, - p2sh_dust_return_pubkey=args.p2sh_dust_return_pubkey, - utxo_locks_max_addresses=args.utxo_locks_max_addresses, - utxo_locks_max_age=args.utxo_locks_max_age, - no_mempool=args.no_mempool, - ) - - server.initialise_log_config( - verbose=args.verbose, - quiet=args.quiet, - log_file=args.log_file, - api_log_file=args.api_log_file, - no_log_files=args.no_log_files, - testnet=args.testnet, - testcoin=args.testcoin, - regtest=args.regtest, - json_log=args.json_log, - ) - - # set up logging - log.set_up( - verbose=config.VERBOSE, - quiet=config.QUIET, - log_file=config.LOG, - log_in_console=args.action == "start", - ) - - server.initialise_config(**init_args) + # Configuration and logging + server.initialise_log_and_config(args) logger.info(f"Running v{APP_VERSION} of {APP_NAME}.") @@ -442,7 +424,7 @@ def main(): ) elif args.action == "start": - server.start_all(catch_up=args.catch_up) + server.start_all(args) elif args.action == "show-params": server.show_params() diff --git a/counterparty-core/counterpartycore/lib/api/api_server.py b/counterparty-core/counterpartycore/lib/api/api_server.py new file mode 100644 index 0000000000..9919763e8f --- /dev/null +++ b/counterparty-core/counterpartycore/lib/api/api_server.py @@ -0,0 +1,237 @@ +import argparse +import logging +import multiprocessing +import traceback +from multiprocessing import Process +from threading import Timer + +import flask +from counterpartycore import server +from counterpartycore.lib import ( + blocks, + config, + database, + exceptions, + ledger, +) +from counterpartycore.lib.api.routes import ROUTES +from counterpartycore.lib.api.util import ( + function_needs_db, + get_backend_height, + init_api_access_log, + remove_rowids, + to_json, +) +from flask import Flask, request +from flask import g as flask_globals +from flask_cors import CORS +from flask_httpauth import HTTPBasicAuth + +multiprocessing.set_start_method("spawn", force=True) + +logger = logging.getLogger(config.LOGGER_NAME) +auth = HTTPBasicAuth() + +BACKEND_HEIGHT = 0 +REFRESH_BACKEND_HEIGHT_INTERVAL = 10 +BACKEND_HEIGHT_TIMER = None + + +def get_db(): + """Get the database connection.""" + if not hasattr(flask_globals, "db"): + flask_globals.db = database.get_connection(read_only=True) + return flask_globals.db + + +@auth.verify_password +def verify_password(username, password): + return username == config.API_USER and password == config.API_PASSWORD + + +def api_root(): + counterparty_height = blocks.last_db_index(get_db()) + routes = [] + for path, route in ROUTES.items(): + routes.append( + { + "path": path, + "args": route.get("args", []), + "description": route.get("description", ""), + } + ) + network = "mainnet" + if config.TESTNET: + network = "testnet" + elif config.REGTEST: + network = "regtest" + elif config.TESTCOIN: + network = "testcoin" + return { + "server_ready": counterparty_height >= BACKEND_HEIGHT, + "network": network, + "version": config.VERSION_STRING, + "backend_height": BACKEND_HEIGHT, + "counterparty_height": counterparty_height, + "routes": routes, + } + + +def is_server_ready(): + return ledger.CURRENT_BLOCK_INDEX >= BACKEND_HEIGHT - 1 + + +def is_cachable(rule): + if rule.startswith("/blocks"): + return True + if rule.startswith("/transactions"): + return True + if rule.startswith("/backend"): + return True + return False + + +def return_result_if_not_ready(rule): + return is_cachable(rule) or rule == "/" + + +def return_result(http_code, result=None, error=None): + assert result is None or error is None + api_result = {} + if result is not None: + api_result["result"] = result + if error is not None: + api_result["error"] = error + response = flask.make_response(to_json(api_result), http_code) + response.headers["X-COUNTERPARTY-HEIGHT"] = ledger.CURRENT_BLOCK_INDEX + response.headers["X-COUNTERPARTY-READY"] = is_server_ready() + response.headers["X-BACKEND-HEIGHT"] = BACKEND_HEIGHT + response.headers["Content-Type"] = "application/json" + return response + + +def prepare_args(route, **kwargs): + function_args = dict(kwargs) + # inject args from request.args + for arg in route["args"]: + arg_name = arg["name"] + if arg_name in function_args: + continue + str_arg = request.args.get(arg_name) + + if str_arg is None and arg["required"]: + raise ValueError(f"Missing required parameter: {arg_name}") + + if str_arg is None: + function_args[arg_name] = arg["default"] + elif arg["type"] == "bool": + function_args[arg_name] = str_arg.lower() in ["true", "1"] + elif arg["type"] == "int": + try: + function_args[arg_name] = int(str_arg) + except ValueError as e: + raise ValueError(f"Invalid integer: {arg_name}") from e + elif arg["type"] == "float": + try: + function_args[arg_name] = float(str_arg) + except ValueError as e: + raise ValueError(f"Invalid float: {arg_name}") from e + else: + function_args[arg_name] = str_arg + return function_args + + +@auth.login_required +def handle_route(**kwargs): + db = get_db() + # update the current block index + ledger.CURRENT_BLOCK_INDEX = blocks.last_db_index(db) + + rule = str(request.url_rule.rule) + + # check if server must be ready + if not is_server_ready() and not return_result_if_not_ready(rule): + return return_result(503, error="Counterparty not ready") + + if rule == "/": + return return_result(200, result=api_root()) + + route = ROUTES.get(rule) + + # parse args + try: + function_args = prepare_args(route, **kwargs) + except ValueError as e: + return return_result(400, error=str(e)) + + # call the function + try: + if function_needs_db(route["function"]): + result = route["function"](db, **function_args) + else: + result = route["function"](**function_args) + except (exceptions.ComposeError, exceptions.UnpackError) as e: + return return_result(503, error=str(e)) + except Exception as e: + logger.exception("Error in API: %s", e) + traceback.print_exc() + return return_result(503, error="Unknwon error") + + # clean up and return the result + result = remove_rowids(result) + return return_result(200, result=result) + + +def run_api_server(args): + app = Flask(config.APP_NAME) + # Initialise log and config + server.initialise_log_and_config(argparse.Namespace(**args)) + with app.app_context(): + if not config.API_NO_ALLOW_CORS: + CORS(app) + # Initialise the API access log + init_api_access_log(app) + # Get the last block index + ledger.CURRENT_BLOCK_INDEX = blocks.last_db_index(get_db()) + # Add routes + app.add_url_rule("/", view_func=handle_route) + for path in ROUTES: + app.add_url_rule(path, view_func=handle_route) + # run the scheduler to refresh the backend height + # `no_refresh_backend_height` used only for testing. TODO: find a way to mock it + if "no_refresh_backend_height" not in args or not args["no_refresh_backend_height"]: + refresh_backend_height() + try: + # Start the API server + app.run(host=config.API_HOST, port=config.API_PORT, debug=False) + finally: + # ensure timer is cancelled + if BACKEND_HEIGHT_TIMER: + BACKEND_HEIGHT_TIMER.cancel() + + +def refresh_backend_height(): + global BACKEND_HEIGHT, BACKEND_HEIGHT_TIMER # noqa F811 + BACKEND_HEIGHT = get_backend_height() + if BACKEND_HEIGHT_TIMER: + BACKEND_HEIGHT_TIMER.cancel() + BACKEND_HEIGHT_TIMER = Timer(REFRESH_BACKEND_HEIGHT_INTERVAL, refresh_backend_height) + BACKEND_HEIGHT_TIMER.start() + + +class APIServer(object): + def __init__(self): + self.process = None + + def start(self, args): + if self.process is not None: + raise Exception("API server is already running") + self.process = Process(target=run_api_server, args=(vars(args),)) + self.process.start() + return self.process + + def stop(self): + logger.info("Stopping API server v2...") + if self.process and self.process.is_alive(): + self.process.terminate() + self.process = None diff --git a/counterparty-core/counterpartycore/lib/api.py b/counterparty-core/counterpartycore/lib/api/api_v1.py similarity index 80% rename from counterparty-core/counterpartycore/lib/api.py rename to counterparty-core/counterpartycore/lib/api/api_v1.py index 3f9ac5d0f6..cfb03dcd0d 100644 --- a/counterparty-core/counterpartycore/lib/api.py +++ b/counterparty-core/counterpartycore/lib/api/api_v1.py @@ -5,41 +5,22 @@ problem. """ +import binascii import collections import decimal import json import logging -import os # noqa: F401 +import math import re -import sys import threading import time import traceback -from logging import handlers as logging_handlers - -import requests # noqa: F401 import counterpartycore.lib.sentry as sentry # noqa: F401 - -D = decimal.Decimal -import binascii # noqa: E402 -import inspect # noqa: E402 -import math # noqa: E402 -import struct # noqa: E402, F401 - -import apsw # noqa: E402, F401 -import flask # noqa: E402 -import jsonrpc # noqa: E402 -from flask import request # noqa: E402 -from flask_httpauth import HTTPBasicAuth # noqa: E402 -from jsonrpc import dispatcher # noqa: E402 -from jsonrpc.exceptions import JSONRPCDispatchException # noqa: E402 -from werkzeug.serving import make_server # noqa: E402 -from xmltodict import unparse as serialize_to_xml # noqa: E402 - -from counterpartycore.lib import ( # noqa: E402 +import flask +import jsonrpc +from counterpartycore.lib import ( backend, - blocks, # noqa: F401 config, database, exceptions, @@ -50,8 +31,9 @@ transaction, util, ) -from counterpartycore.lib.kickstart.blocks_parser import BlockchainParser # noqa: E402 -from counterpartycore.lib.messages import ( # noqa: E402 +from counterpartycore.lib.api import util as api_util +from counterpartycore.lib.kickstart.blocks_parser import BlockchainParser +from counterpartycore.lib.messages import ( bet, # noqa: F401 broadcast, # noqa: F401 btcpay, # noqa: F401 @@ -74,6 +56,14 @@ is_docker, is_force_enabled, ) +from flask import request +from flask_httpauth import HTTPBasicAuth +from jsonrpc import dispatcher +from jsonrpc.exceptions import JSONRPCDispatchException +from werkzeug.serving import make_server +from xmltodict import unparse as serialize_to_xml + +D = decimal.Decimal logger = logging.getLogger(config.LOGGER_NAME) @@ -155,49 +145,6 @@ """, } -API_TRANSACTIONS = [ - "bet", - "broadcast", - "btcpay", - "burn", - "cancel", - "destroy", - "dividend", - "issuance", - "order", - "send", - "rps", - "rpsresolve", - "sweep", - "dispenser", -] - -COMMONS_ARGS = [ - "encoding", - "fee_per_kb", - "regular_dust_size", - "multisig_dust_size", - "op_return_value", - "pubkey", - "allow_unconfirmed_inputs", - "fee", - "fee_provided", - "estimate_fee_per_kb", - "estimate_fee_per_kb_nblocks", - "unspent_tx_hash", - "custom_inputs", - "dust_return_pubkey", - "disable_utxo_locks", - "extended_tx_info", - "p2sh_source_multisig_pubkeys", - "p2sh_source_multisig_pubkeys_required", - "p2sh_pretx_txid", -] - -API_MAX_LOG_SIZE = ( - 10 * 1024 * 1024 -) # max log size of 20 MB before rotation (make configurable later) -API_MAX_LOG_COUNT = 10 JSON_RPC_ERROR_API_COMPOSE = -32001 # code to use for error composing transaction result CURRENT_API_STATUS_CODE = None # is updated by the APIStatusPoller @@ -233,16 +180,6 @@ class DatabaseError(Exception): pass -def check_database_state(db, blockcount): - f"""Checks {config.XCP_NAME} database to see if is caught up with backend.""" # noqa: B021 - if ledger.CURRENT_BLOCK_INDEX + 1 < blockcount: - raise DatabaseError( - f"{config.XCP_NAME} database is behind backend. [{ledger.CURRENT_BLOCK_INDEX }/{blockcount} blocks parsed]" - ) - # logger.debug("Database state check passed.") - return - - # TODO: ALL queries EVERYWHERE should be done with these methods def db_query(db, statement, bindings=(), callback=None, **callback_args): """Allow direct access to the database in a parametrized manner.""" @@ -535,124 +472,6 @@ def adjust_get_transactions_results(query_result): return filtered_results -def get_default_args(func): - signature = inspect.signature(func) - return { - k: v.default - for k, v in signature.parameters.items() - if v.default is not inspect.Parameter.empty - } - - -def compose_transaction( - db, - name, - params, - encoding="auto", - fee_per_kb=None, - estimate_fee_per_kb=None, - regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE, - multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE, - op_return_value=config.DEFAULT_OP_RETURN_VALUE, - pubkey=None, - allow_unconfirmed_inputs=False, - fee=None, - fee_provided=0, - unspent_tx_hash=None, - custom_inputs=None, - dust_return_pubkey=None, - disable_utxo_locks=False, - extended_tx_info=False, - p2sh_source_multisig_pubkeys=None, - p2sh_source_multisig_pubkeys_required=None, - p2sh_pretx_txid=None, - old_style_api=True, - segwit=False, -): - """Create and return a transaction.""" - - # Get provided pubkeys. - if type(pubkey) == str: # noqa: E721 - provided_pubkeys = [pubkey] - elif type(pubkey) == list: # noqa: E721 - provided_pubkeys = pubkey - elif pubkey == None: # noqa: E711 - provided_pubkeys = [] - else: - assert False # noqa: B011 - - # Get additional pubkeys from `source` and `destination` params. - # Convert `source` and `destination` to pubkeyhash form. - for address_name in ["source", "destination"]: - if address_name in params: - address = params[address_name] - if isinstance(address, list): - # pkhshs = [] - # for addr in address: - # provided_pubkeys += script.extract_pubkeys(addr) - # pkhshs.append(script.make_pubkeyhash(addr)) - # params[address_name] = pkhshs - pass - else: - provided_pubkeys += script.extract_pubkeys(address) - params[address_name] = script.make_pubkeyhash(address) - - # Check validity of collected pubkeys. - for pubkey in provided_pubkeys: - if not script.is_fully_valid(binascii.unhexlify(pubkey)): - raise script.AddressError(f"invalid public key: {pubkey}") - - compose_method = sys.modules[f"counterpartycore.lib.messages.{name}"].compose - compose_params = inspect.getfullargspec(compose_method)[0] - missing_params = [p for p in compose_params if p not in params and p != "db"] - for param in missing_params: - params[param] = None - - # dont override fee_per_kb if specified - if fee_per_kb is not None: - estimate_fee_per_kb = False - else: - fee_per_kb = config.DEFAULT_FEE_PER_KB - - if "extended_tx_info" in params: - extended_tx_info = params["extended_tx_info"] - del params["extended_tx_info"] - - if "old_style_api" in params: - old_style_api = params["old_style_api"] - del params["old_style_api"] - - if "segwit" in params: - segwit = params["segwit"] - del params["segwit"] - - tx_info = compose_method(db, **params) - return transaction.construct( - db, - tx_info, - encoding=encoding, - fee_per_kb=fee_per_kb, - estimate_fee_per_kb=estimate_fee_per_kb, - regular_dust_size=regular_dust_size, - multisig_dust_size=multisig_dust_size, - op_return_value=op_return_value, - provided_pubkeys=provided_pubkeys, - allow_unconfirmed_inputs=allow_unconfirmed_inputs, - exact_fee=fee, - fee_provided=fee_provided, - unspent_tx_hash=unspent_tx_hash, - custom_inputs=custom_inputs, - dust_return_pubkey=dust_return_pubkey, - disable_utxo_locks=disable_utxo_locks, - extended_tx_info=extended_tx_info, - p2sh_source_multisig_pubkeys=p2sh_source_multisig_pubkeys, - p2sh_source_multisig_pubkeys_required=p2sh_source_multisig_pubkeys_required, - p2sh_pretx_txid=p2sh_pretx_txid, - old_style_api=old_style_api, - segwit=segwit, - ) - - def conditional_decorator(decorator, condition): """Checks the condition and if True applies specified decorator.""" @@ -664,27 +483,6 @@ def gen_decorator(f): return gen_decorator -def init_api_access_log(app): - """Initialize API logger.""" - loggers = (logging.getLogger("werkzeug"), app.logger) - - # Disable console logging... - for l in loggers: # noqa: E741 - l.setLevel(logging.CRITICAL) - l.propagate = False - - # Log to file, if configured... - if config.API_LOG: - handler = logging_handlers.RotatingFileHandler( - config.API_LOG, "a", API_MAX_LOG_SIZE, API_MAX_LOG_COUNT - ) - for l in loggers: # noqa: E741 - handler.setLevel(logging.DEBUG) - l.addHandler(handler) - - flask.cli.show_server_banner = lambda *args: None - - class APIStatusPoller(threading.Thread): """Perform regular checks on the state of the backend and the database.""" @@ -721,7 +519,7 @@ def run(self): check_backend_state() code = 12 logger.debug("Checking database state.") - check_database_state(self.db, backend.getblockcount()) + api_util.check_last_parsed_block(backend.getblockcount()) self.last_database_check = time.time() except (BackendError, DatabaseError) as e: exception_name = e.__class__.__name__ @@ -752,12 +550,12 @@ def __init__(self, db=None): sentry.init() def stop(self): - logger.info("Stopping API Server...") - self.server.shutdown() self.db.close() + self.server.shutdown() + self.join() def run(self): - logger.info("Starting API Server...") + logger.info("Starting API Server v1.") self.db = self.db or database.get_connection(read_only=True) app = flask.Flask(__name__) auth = HTTPBasicAuth() @@ -797,24 +595,13 @@ def sql(query, bindings=None): # Generate dynamically create_{transaction} methods def generate_create_method(tx): - def split_params(**kwargs): - transaction_args = {} - common_args = {} - private_key_wif = None - for key in kwargs: - if key in COMMONS_ARGS: - common_args[key] = kwargs[key] - elif key == "privkey": - private_key_wif = kwargs[key] - else: - transaction_args[key] = kwargs[key] - return transaction_args, common_args, private_key_wif - def create_method(**kwargs): try: - transaction_args, common_args, private_key_wif = split_params(**kwargs) - return compose_transaction( - self.db, name=tx, params=transaction_args, **common_args + transaction_args, common_args, private_key_wif = ( + transaction.split_compose_params(**kwargs) + ) + return transaction.compose_transaction( + self.db, name=tx, params=transaction_args, api_v1=True, **common_args ) except ( TypeError, @@ -833,7 +620,7 @@ def create_method(**kwargs): return create_method - for tx in API_TRANSACTIONS: + for tx in transaction.COMPOSABLE_TRANSACTIONS: create_method = generate_create_method(tx) create_method.__name__ = f"create_{tx}" dispatcher.add_method(create_method) @@ -1011,7 +798,7 @@ def get_running_info(): latest_block_index = backend.getblockcount() try: - check_database_state(self.db, latest_block_index) + api_util.check_last_parsed_block(latest_block_index) except DatabaseError: caught_up = False else: @@ -1194,12 +981,11 @@ def unpack(data_hex): # TODO: Enabled only for `send`. if message_type_id == send.ID: - unpack_method = send.unpack + unpacked = send.unpack(self.db, message, ledger.CURRENT_BLOCK_INDEX) elif message_type_id == enhanced_send.ID: - unpack_method = enhanced_send.unpack + unpacked = enhanced_send.unpack(message, ledger.CURRENT_BLOCK_INDEX) else: raise APIError("unsupported message type") - unpacked = unpack_method(self.db, message, ledger.CURRENT_BLOCK_INDEX) return message_type_id, unpacked @dispatcher.add_method @@ -1274,74 +1060,10 @@ def _set_cors_headers(response): ##### REST ROUTES ##### - @app.route("/addresses/
/balances", methods=["GET"]) - def handle_address_balances(address): - return remove_rowids(ledger.get_address_balances(self.db, address)) - - @app.route("/assets//balances", methods=["GET"]) - def handle_asset_balances(asset): - return remove_rowids(ledger.get_asset_balances(self.db, asset)) - - @app.route("/assets//", methods=["GET"]) - def handle_asset_info(asset): - return remove_rowids(get_asset_info(asset=asset)) - - @app.route("/assets//orders", methods=["GET"]) - def handle_asset_orders(asset): - status = request.args.get("status", "open") - return remove_rowids(ledger.get_orders_by_asset(self.db, asset, status)) - - @app.route("/orders/", methods=["GET"]) - def handle_order_info(tx_hash): - return remove_rowids(ledger.get_order(self.db, tx_hash)) - - @app.route("/orders//matches", methods=["GET"]) - def handle_order_matches(tx_hash): - status = request.args.get("status", "pending") - return remove_rowids(ledger.get_order_matches_by_order(self.db, tx_hash, status)) - @app.route("/healthz", methods=["GET"]) def handle_healthz(): - msg, code = "Healthy", 200 - - type_ = request.args.get("type", "light") - - def light_check(): - latest_block_index = backend.getblockcount() - check_database_state(self.db, latest_block_index) - - def heavy_check(): - compose_transaction( - self.db, - name="send", - params={ - "source": config.UNSPENDABLE, - "destination": config.UNSPENDABLE, - "asset": config.XCP, - "quantity": 100000000, - }, - allow_unconfirmed_inputs=True, - fee=1000, - ) - - try: - if type_ == "heavy": - # Perform a heavy healthz check. - # Do everything in light but also compose a - # send tx - - logger.debug("Performing heavy healthz check.") - - light_check() - heavy_check() - else: - logger.debug("Performing light healthz check.") - light_check() - - except Exception: - msg, code = "Unhealthy", 503 - - return flask.Response(msg, code, mimetype="application/json") + check_type = request.args.get("type", "light") + return api_util.handle_healthz_route(self.db, check_type) @app.route("/", defaults={"args_path": ""}, methods=["GET", "POST", "OPTIONS"]) @app.route("/", methods=["GET", "POST", "OPTIONS"]) @@ -1349,12 +1071,11 @@ def heavy_check(): @conditional_decorator(auth.login_required, hasattr(config, "RPC_PASSWORD")) def handle_root(args_path): """Handle all paths, decide where to forward the query.""" + request_path = args_path.lower() if ( - args_path == "" - or args_path.startswith("api/") - or args_path.startswith("API/") - or args_path.startswith("rpc/") - or args_path.startswith("RPC/") + request_path == "old" + or request_path.startswith("v1/api/") + or request_path.startswith("v1/rpc/") ): if flask.request.method == "POST": # Need to get those here because it might not be available in this aux function. @@ -1367,7 +1088,7 @@ def handle_root(args_path): else: error = "Invalid method." return flask.Response(error, 405, mimetype="application/json") - elif args_path.startswith("rest/") or args_path.startswith("REST/"): + elif request_path.startswith("v1/rest/"): if flask.request.method == "GET" or flask.request.method == "POST": # Pass the URL path without /REST/ part and Flask request object. rest_path = args_path.split("/", 1)[1] @@ -1386,6 +1107,7 @@ def handle_root(args_path): def handle_rpc_options(): response = flask.Response("", 204) _set_cors_headers(response) + response.headers["X-API-WARN"] = "Deprecated API" return response def handle_rpc_post(request_json): @@ -1425,6 +1147,10 @@ def handle_rpc_post(request_json): jsonrpc_response.json.encode(), 200, mimetype="application/json" ) _set_cors_headers(response) + response.headers["X-API-WARN"] = "Deprecated API" + logger.warning( + "API v1 is deprecated and should be removed soon. Please migrate to REST API." + ) return response ###################### @@ -1449,7 +1175,7 @@ def handle_rest(path_args, flask_request): error = "No query_type provided." return flask.Response(error, 400, mimetype="application/json") # Check if message type or table name are valid. - if (compose and query_type not in API_TRANSACTIONS) or ( + if (compose and query_type not in transaction.COMPOSABLE_TRANSACTIONS) or ( not compose and query_type not in API_TABLES ): error = f'No such query type in supported queries: "{query_type}".' @@ -1460,24 +1186,9 @@ def handle_rest(path_args, flask_request): query_data = {} if compose: - common_args = {} - transaction_args = {} - for key, value in extra_args: - # Determine value type. - try: - value = int(value) # noqa: PLW2901 - except ValueError: - try: - value = float(value) # noqa: PLW2901 - except ValueError: - pass - # Split keys into common and transaction-specific arguments. Discard the privkey. - if key in COMMONS_ARGS: - common_args[key] = value - elif key == "privkey": - pass - else: - transaction_args[key] = value + transaction_args, common_args, private_key_wif = transaction.split_compose_params( + **extra_args + ) # Must have some additional transaction arguments. if not len(transaction_args): @@ -1486,7 +1197,7 @@ def handle_rest(path_args, flask_request): # Compose the transaction. try: - query_data = compose_transaction( + query_data = transaction.compose_transaction( self.db, name=query_type, params=transaction_args, **common_args ) except ( @@ -1539,7 +1250,7 @@ def handle_rest(path_args, flask_request): # Init the HTTP Server. self.is_ready = True self.server = make_server(config.RPC_HOST, config.RPC_PORT, app, threaded=True) - init_api_access_log(app) + api_util.init_api_access_log(app) self.ctx = app.app_context() self.ctx.push() # Run app server (blocking) diff --git a/counterparty-core/counterpartycore/lib/api/routes.py b/counterparty-core/counterpartycore/lib/api/routes.py new file mode 100644 index 0000000000..828716933e --- /dev/null +++ b/counterparty-core/counterpartycore/lib/api/routes.py @@ -0,0 +1,105 @@ +from counterpartycore.lib import ( + backend, + ledger, + transaction, +) +from counterpartycore.lib.api import util + +# Define the API routes except root (`/`) defined in `api_server.py` +ROUTES = util.prepare_routes( + { + ### /blocks ### + "/blocks": ledger.get_blocks, + "/blocks/": ledger.get_block, + "/blocks//transactions": ledger.get_transactions_by_block, + "/blocks//events": ledger.get_events_by_block, + "/blocks//events/counts": ledger.get_events_counts_by_block, + "/blocks//events/": ledger.get_events_by_block_and_event, + "/blocks//credits": ledger.get_credits_by_block, + "/blocks//debits": ledger.get_debits_by_block, + "/blocks//expirations": ledger.get_expirations, + "/blocks//cancels": ledger.get_cancels, + "/blocks//destructions": ledger.get_destructions, + "/blocks//issuances": ledger.get_issuances_by_block, + "/blocks//sends": ledger.get_sends_or_receives_by_block, + "/blocks//dispenses": ledger.get_dispenses_by_block, + "/blocks//sweeps": ledger.get_sweeps_by_block, + ### /transactions ### + "/transactions/info": transaction.info, + "/transactions/unpack": transaction.unpack, + "/transactions/": transaction.get_transaction_by_hash, + ### /addresses ### + "/addresses/
/balances": ledger.get_address_balances, + "/addresses/
/balances/": ledger.get_balance_by_address_and_asset, + "/addresses/
/credits": ledger.get_credits_by_address, + "/addresses/
/debits": ledger.get_debits_by_address, + "/addresses/
/bets": ledger.get_bet_by_feed, + "/addresses/
/broadcasts": ledger.get_broadcasts_by_source, + "/addresses/
/burns": ledger.get_burns_by_address, + "/addresses/
/sends": ledger.get_send_by_address, + "/addresses/
/receives": ledger.get_receive_by_address, + "/addresses/
/sends/": ledger.get_send_by_address_and_asset, + "/addresses/
/receives/": ledger.get_receive_by_address_and_asset, + "/addresses/
/dispensers": ledger.get_dispensers_by_address, + "/addresses/
/dispensers/": ledger.get_dispensers_by_address_and_asset, + "/addresses/
/sweeps": ledger.get_sweeps_by_address, + ### /addresses/
/compose/ ### + "/addresses/
/compose/bet": transaction.compose_bet, + "/addresses/
/compose/broadcast": transaction.compose_broadcast, + "/addresses/
/compose/btcpay": transaction.compose_btcpay, + "/addresses/
/compose/burn": transaction.compose_burn, + "/addresses/
/compose/cancel": transaction.compose_cancel, + "/addresses/
/compose/destroy": transaction.compose_destroy, + "/addresses/
/compose/dispenser": transaction.compose_dispenser, + "/addresses/
/compose/dividend": transaction.compose_dividend, + "/addresses/
/compose/issuance": transaction.compose_issuance, + "/addresses/
/compose/mpma": transaction.compose_mpma, + "/addresses/
/compose/order": transaction.compose_order, + "/addresses/
/compose/send": transaction.compose_send, + "/addresses/
/compose/sweep": transaction.compose_sweep, + ### /assets ### + "/assets": ledger.get_valid_assets, + "/assets/": ledger.get_asset_info, + "/assets//balances": ledger.get_asset_balances, + "/assets//balances/
": ledger.get_balance_by_address_and_asset, + "/assets//orders": ledger.get_orders_by_asset, + "/assets//credits": ledger.get_credits_by_asset, + "/assets//debits": ledger.get_debits_by_asset, + "/assets//dividends": ledger.get_dividends, + "/assets//issuances": ledger.get_issuances_by_asset, + "/assets//sends": ledger.get_sends_or_receives_by_asset, + "/assets//dispensers": ledger.get_dispensers_by_asset, + "/assets//dispensers/
": ledger.get_dispensers_by_address_and_asset, + "/assets//holders": ledger.get_asset_holders, + ### /orders ### + "/orders/": ledger.get_order, + "/orders//matches": ledger.get_order_matches_by_order, + "/orders//btcpays": ledger.get_btcpays_by_order, + ### /bets ### + "/bets/": ledger.get_bet, + "/bets//matches": ledger.get_bet_matches_by_bet, + "/bets//resolutions": ledger.get_resolutions_by_bet, + ### /burns ### + "/burns": ledger.get_all_burns, + ### /dispensers ### + "/dispensers/": ledger.get_dispenser_info_by_hash, + "/dispensers//dispenses": ledger.get_dispenses_by_dispenser, + ### /events ### + "/events": ledger.get_all_events, + "/events/": ledger.get_event_by_index, + "/events/counts": ledger.get_all_events_counts, + "/events/": ledger.get_events_by_name, + ### /healthz ### + "/healthz": util.check_server_status, + ### /backend ### + "/backend/addresses/
/transactions": backend.search_raw_transactions, + "/backend/addresses/
/transactions/oldest": backend.get_oldest_tx, + "/backend/addresses/
/utxos": backend.get_unspent_txouts, + "/backend/addresses/
/pubkey": util.pubkeyhash_to_pubkey, + "/backend/transactions/": util.get_raw_transaction, + "/backend/estimatesmartfee": backend.fee_per_kb, + ### /mempool ### + "/mempool/events": ledger.get_all_mempool_events, + "/mempool/events/": ledger.get_mempool_events_by_name, + } +) diff --git a/counterparty-core/counterpartycore/lib/api/util.py b/counterparty-core/counterpartycore/lib/api/util.py new file mode 100644 index 0000000000..e4ab9a1075 --- /dev/null +++ b/counterparty-core/counterpartycore/lib/api/util.py @@ -0,0 +1,227 @@ +import decimal +import inspect +import json +import logging +from logging import handlers as logging_handlers + +import flask +from counterpartycore.lib import backend, config, exceptions, ledger, transaction +from docstring_parser import parse as parse_docstring + +logger = logging.getLogger(config.LOGGER_NAME) + + +def check_last_parsed_block(blockcount): + """Checks database to see if is caught up with backend.""" + if ledger.CURRENT_BLOCK_INDEX + 1 < blockcount: + raise exceptions.DatabaseError(f"{config.XCP_NAME} database is behind backend.") + logger.debug("Database state check passed.") + + +def healthz_light(): + logger.debug("Performing light healthz check.") + latest_block_index = backend.getblockcount() + check_last_parsed_block(latest_block_index) + + +def healthz_heavy(db): + logger.debug("Performing heavy healthz check.") + transaction.compose_transaction( + db, + name="send", + params={ + "source": config.UNSPENDABLE, + "destination": config.UNSPENDABLE, + "asset": config.XCP, + "quantity": 100000000, + }, + allow_unconfirmed_inputs=True, + fee=1000, + ) + + +def healthz(db, check_type: str = "heavy"): + try: + if check_type == "light": + healthz_light() + else: + healthz_light() + healthz_heavy(db) + except Exception as e: + logger.error(f"Health check failed: {e}") + return False + return True + + +def handle_healthz_route(db, check_type: str = "heavy"): + """ + Health check route. + :param check_type: Type of health check to perform. Options are 'light' and 'heavy' (e.g. light) + """ + msg, code = "Healthy", 200 + if not healthz(db, check_type): + msg, code = "Unhealthy", 503 + result = {"result": msg, "success": code == 200} + if code != 200: + result["error"] = msg + return flask.Response(to_json(result), code, mimetype="application/json") + + +def check_server_status(db, check_type: str = "heavy"): + """ + Health check route. + :param check_type: Type of health check to perform. Options are 'light' and 'heavy' (e.g. light) + """ + if not healthz(db, check_type): + return {"status": "Unhealthy"} + return {"status": "Healthy"} + + +def remove_rowids(query_result): + """Remove the rowid field from the query result.""" + if isinstance(query_result, list): + filtered_results = [] + for row in list(query_result): + if "rowid" in row: + del row["rowid"] + if "MAX(rowid)" in row: + del row["MAX(rowid)"] + filtered_results.append(row) + return filtered_results + if isinstance(query_result, dict): + filtered_results = query_result + if "rowid" in filtered_results: + del filtered_results["rowid"] + if "MAX(rowid)" in filtered_results: + del filtered_results["MAX(rowid)"] + return filtered_results + return query_result + + +def getrawtransactions(tx_hashes, verbose=False, skip_missing=False, _retry=0): + txhash_list = tx_hashes.split(",") + return backend.getrawtransaction_batch(txhash_list, verbose, skip_missing, _retry) + + +def pubkeyhash_to_pubkey(address: str, provided_pubkeys: str = None): + """ + Get pubkey for an address. + :param address: Address to get pubkey for. (e.g. 14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS) + :param provided_pubkeys: Comma separated list of provided pubkeys. + """ + if provided_pubkeys: + provided_pubkeys_list = provided_pubkeys.split(",") + else: + provided_pubkeys_list = None + return backend.pubkeyhash_to_pubkey(address, provided_pubkeys=provided_pubkeys_list) + + +def get_raw_transaction(tx_hash: str, verbose: bool = False): + """ + Get a raw transaction from the blockchain + :param tx_hash: The transaction hash (e.g. 3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018) + :param verbose: Whether to return JSON output or raw hex (e.g. True) + """ + return backend.getrawtransaction(tx_hash, verbose=verbose) + + +def get_backend_height(): + block_count = backend.getblockcount() + blocks_behind = backend.getindexblocksbehind() + return block_count + blocks_behind + + +def init_api_access_log(flask_app): + """Initialize API logger.""" + flask_app.logger.removeHandler(flask.logging.default_handler) + flask_app.logger.setLevel(logging.DEBUG) + werkzeug_logger = logging.getLogger("werkzeug") + while len(werkzeug_logger.handlers) > 0: + werkzeug_logger.removeHandler(werkzeug_logger.handlers[0]) + + # Log to file, if configured... + if config.API_LOG: + handler = logging_handlers.RotatingFileHandler( + config.API_LOG, "a", config.API_MAX_LOG_SIZE, config.API_MAX_LOG_COUNT + ) + handler.propagate = False + handler.setLevel(logging.DEBUG) + flask_app.logger.addHandler(handler) + werkzeug_logger.addHandler(handler) + + flask.cli.show_server_banner = lambda *args: None + + +def get_args_description(function): + docstring = parse_docstring(function.__doc__) + args = {} + for param in docstring.params: + args[param.arg_name] = param.description + return args + + +def function_needs_db(function): + return "db" in inspect.signature(function).parameters + + +def prepare_route_args(function): + args = [] + function_args = inspect.signature(function).parameters + args_description = get_args_description(function) + for arg_name, arg in function_args.items(): + if arg_name == "construct_args": + for carg_name, carg_info in transaction.COMPOSE_COMMONS_ARGS.items(): + args.append( + { + "name": carg_name, + "type": carg_info[0].__name__, + "default": carg_info[1], + "description": carg_info[2], + "required": False, + } + ) + continue + annotation = arg.annotation + if annotation is inspect.Parameter.empty: + continue + route_arg = {"name": arg_name} + default = arg.default + if default is not inspect.Parameter.empty: + route_arg["default"] = default + route_arg["required"] = False + else: + route_arg["required"] = True + route_arg["type"] = arg.annotation.__name__ + if arg_name in args_description: + route_arg["description"] = args_description[arg_name] + args.append(route_arg) + return args + + +def get_function_description(function): + docstring = parse_docstring(function.__doc__) + return docstring.description + + +def prepare_routes(routes): + prepared_routes = {} + for route, function in routes.items(): + prepared_routes[route] = { + "function": function, + "description": get_function_description(function), + "args": prepare_route_args(function), + } + return prepared_routes + + +class ApiJsonEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, decimal.Decimal): + return str(o) + if isinstance(o, bytes): + return o.hex() + return super().default(o) + + +def to_json(obj, indent=None): + return json.dumps(obj, cls=ApiJsonEncoder, indent=indent) diff --git a/counterparty-core/counterpartycore/lib/backend/__init__.py b/counterparty-core/counterpartycore/lib/backend/__init__.py index bf476b658e..802a8eced2 100644 --- a/counterparty-core/counterpartycore/lib/backend/__init__.py +++ b/counterparty-core/counterpartycore/lib/backend/__init__.py @@ -72,7 +72,9 @@ def clear_pretx(txid): del PRETX_CACHE[binascii.hexlify(txid).decode("utf8")] -def getrawtransaction(tx_hash, verbose=False, skip_missing=False, block_index=None): +def getrawtransaction( + tx_hash: str, verbose: bool = False, skip_missing: bool = False, block_index: int = None +): if block_index and block_index in prefetcher.BLOCKCHAIN_CACHE: return prefetcher.BLOCKCHAIN_CACHE[block_index]["raw_transactions"][tx_hash] @@ -124,14 +126,15 @@ def ensure_script_pub_key_for_inputs(coins): return coins -def fee_per_kb(conf_target, mode, nblocks=None): +def fee_per_kb( + conf_target: int = config.ESTIMATE_FEE_CONF_TARGET, mode: str = config.ESTIMATE_FEE_MODE +): """ - :param conf_target: - :param mode: - :return: fee_per_kb in satoshis, or None when unable to determine + Get the fee per kilobyte for a transaction to be confirmed in `conf_target` blocks. + :param conf_target: Confirmation target in blocks (1 - 1008) (e.g. 2) + :param mode: The fee estimate mode. (e.g. CONSERVATIVE) """ - - return backend().fee_per_kb(conf_target, mode, nblocks=nblocks) + return backend().fee_per_kb(conf_target, mode, nblocks=None) def deserialize(tx_hex): @@ -207,12 +210,15 @@ class MempoolError(Exception): pass -def get_unspent_txouts(source, unconfirmed=False, unspent_tx_hash=None): - """returns a list of unspent outputs for a specific address - @return: A list of dicts, with each entry in the dict having the following keys: +def get_unspent_txouts(address: str, unconfirmed: bool = False, unspent_tx_hash: str = None): + """ + Returns a list of unspent outputs for a specific address + :param address: The address to search for (e.g. 14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS) + :param unconfirmed: Include unconfirmed transactions + :param unspent_tx_hash: Filter by unspent_tx_hash """ - unspent = backend().get_unspent_txouts(source) + unspent = backend().get_unspent_txouts(address) # filter by unspent_tx_hash if unspent_tx_hash is not None: @@ -232,11 +238,22 @@ def get_unspent_txouts(source, unconfirmed=False, unspent_tx_hash=None): return unspent -def search_raw_transactions(address, unconfirmed=True, only_tx_hashes=False): +def search_raw_transactions(address: str, unconfirmed: bool = True, only_tx_hashes: bool = False): + """ + Returns all transactions involving a given address + :param address: The address to search for (e.g. 14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS) + :param unconfirmed: Include unconfirmed transactions (e.g. True) + :param only_tx_hashes: Return only the tx hashes (e.g. True) + """ return backend().search_raw_transactions(address, unconfirmed, only_tx_hashes) -def get_oldest_tx(address, block_index=None): +def get_oldest_tx(address: str, block_index: int = None): + """ + Get the oldest transaction for an address. + :param address: The address to search for. (e.g. 14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS) + :param block_index: The block index to search from. + """ return backend().get_oldest_tx(address, block_index=block_index) diff --git a/counterparty-core/counterpartycore/lib/backend/addrindexrs.py b/counterparty-core/counterpartycore/lib/backend/addrindexrs.py index fed8580729..429131247e 100644 --- a/counterparty-core/counterpartycore/lib/backend/addrindexrs.py +++ b/counterparty-core/counterpartycore/lib/backend/addrindexrs.py @@ -663,7 +663,7 @@ def get_unspent_txouts(source): # ] # # } -def search_raw_transactions(address, unconfirmed=True, only_tx_hashes=False): +def search_raw_transactions(address, unconfirmed: bool = True, only_tx_hashes: bool = False): hsh = _address_to_hash(address) txs = INDEXER_THREAD.send({"method": "blockchain.scripthash.get_history", "params": [hsh]})[ "result" @@ -797,7 +797,7 @@ def get_oldest_tx(self, address, timeout=ADDRINDEXRS_CLIENT_TIMEOUT, block_index ADDRINDEXRS_CLIENT = None -def get_oldest_tx(address, block_index=None): +def get_oldest_tx(address: str, block_index: int = None): current_block_index = block_index or ledger.CURRENT_BLOCK_INDEX hardcoded_key = f"{current_block_index}-{address}" if hardcoded_key in GET_OLDEST_TX_HARDCODED: diff --git a/counterparty-core/counterpartycore/lib/blocks.py b/counterparty-core/counterpartycore/lib/blocks.py index 639fe9e3eb..c72025e95f 100644 --- a/counterparty-core/counterpartycore/lib/blocks.py +++ b/counterparty-core/counterpartycore/lib/blocks.py @@ -628,6 +628,9 @@ def initialise(db): bindings TEXT, timestamp INTEGER) """) + columns = [column["name"] for column in cursor.execute("""PRAGMA table_info(mempool)""")] + if "event" not in columns: + cursor.execute("""ALTER TABLE mempool ADD COLUMN event TEXT""") # Lock UPDATE on all tables for table in TABLES: @@ -1251,7 +1254,7 @@ def follow(db): tx_hash, new_message = message new_message["tx_hash"] = tx_hash cursor.execute( - """INSERT INTO mempool VALUES(:tx_hash, :command, :category, :bindings, :timestamp)""", + """INSERT INTO mempool VALUES(:tx_hash, :command, :category, :bindings, :timestamp, :event)""", new_message, ) diff --git a/counterparty-core/counterpartycore/lib/config.py b/counterparty-core/counterpartycore/lib/config.py index 5c2667f6c0..f9b608b707 100644 --- a/counterparty-core/counterpartycore/lib/config.py +++ b/counterparty-core/counterpartycore/lib/config.py @@ -53,9 +53,13 @@ FULL_APP_NAME = "Counterparty Core" LOGGER_NAME = APP_NAME -DEFAULT_RPC_PORT_REGTEST = 24000 -DEFAULT_RPC_PORT_TESTNET = 14000 -DEFAULT_RPC_PORT = 4000 +DEFAULT_API_PORT_REGTEST = 24000 +DEFAULT_API_PORT_TESTNET = 14000 +DEFAULT_API_PORT = 4000 + +DEFAULT_RPC_PORT_REGTEST = 24100 +DEFAULT_RPC_PORT_TESTNET = 14100 +DEFAULT_RPC_PORT = 4100 DEFAULT_BACKEND_PORT_REGTEST = 28332 DEFAULT_BACKEND_PORT_TESTNET = 18332 @@ -151,5 +155,7 @@ BOOTSTRAP_URL_MAINNET = "https://bootstrap.counterparty.io/counterparty.latest.tar.gz" BOOTSTRAP_URL_TESTNET = "https://bootstrap.counterparty.io/counterparty-testnet.latest.tar.gz" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 +API_MAX_LOG_SIZE = ( + 10 * 1024 * 1024 +) # max log size of 20 MB before rotation (make configurable later) +API_MAX_LOG_COUNT = 10 diff --git a/counterparty-core/counterpartycore/lib/exceptions.py b/counterparty-core/counterpartycore/lib/exceptions.py index 07f846c577..cbe5260481 100644 --- a/counterparty-core/counterpartycore/lib/exceptions.py +++ b/counterparty-core/counterpartycore/lib/exceptions.py @@ -83,4 +83,8 @@ class ComposeTransactionError(Exception): pass +class InvalidArgument(Exception): + pass + + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/counterparty-core/counterpartycore/lib/kickstart/utils.py b/counterparty-core/counterpartycore/lib/kickstart/utils.py index 71dc0da42a..b5f8d05224 100644 --- a/counterparty-core/counterpartycore/lib/kickstart/utils.py +++ b/counterparty-core/counterpartycore/lib/kickstart/utils.py @@ -1,7 +1,5 @@ import binascii -import decimal import hashlib -import json import math import os from multiprocessing import resource_tracker @@ -31,13 +29,6 @@ def ib2h(b): return inverse_hash(b2h(b)) -class JsonDecimalEncoder(json.JSONEncoder): - def default(self, o): - if isinstance(o, decimal.Decimal): - return str(o) - return super(DecimalEncoder, self).default(o) # noqa: F821 - - def decode_value(key, value): # Repeated key to make both same length adjusted_key = key * int(math.ceil(float(len(value)) / len(key))) diff --git a/counterparty-core/counterpartycore/lib/ledger.py b/counterparty-core/counterpartycore/lib/ledger.py index 9795cb90d5..3ebbaee5ce 100644 --- a/counterparty-core/counterpartycore/lib/ledger.py +++ b/counterparty-core/counterpartycore/lib/ledger.py @@ -6,7 +6,7 @@ import time from decimal import Decimal as D -from counterpartycore.lib import config, exceptions, log, util +from counterpartycore.lib import backend, config, exceptions, log, util logger = logging.getLogger(config.LOGGER_NAME) @@ -64,6 +64,160 @@ def get_messages(db, block_index=None, block_index_in=None, message_index_in=Non return cursor.fetchall() +def get_events(db, block_index=None, event=None, event_index=None, last=None, limit=None): + cursor = db.cursor() + where = [] + bindings = [] + if block_index is not None: + where.append("block_index = ?") + bindings.append(block_index) + if event is not None: + where.append("event = ?") + bindings.append(event) + if event_index is not None: + where.append("message_index = ?") + bindings.append(event_index) + if last is not None: + where.append("message_index <= ?") + bindings.append(last) + if block_index is None and limit is None: + limit = 100 + if limit is not None: + limit = f"LIMIT {int(limit)}" + else: + limit = "" + # no sql injection here + query = """ + SELECT message_index AS event_index, event, bindings, block_index, timestamp + FROM messages + """ + if len(where) > 0: + query += f""" + WHERE ({" AND ".join(where)}) + """ # nosec B608 # noqa: S608 + query += f""" + ORDER BY message_index DESC {limit} + """ + cursor.execute(query, tuple(bindings)) + events = cursor.fetchall() + for i, _ in enumerate(events): + events[i]["bindings"] = json.loads(events[i]["bindings"]) + return events + + +def get_all_events(db, last: int = None, limit: int = 100): + """ + Returns all events + :param int last: The last event index to return (e.g. 10665092) + :param int limit: The maximum number of events to return (e.g. 5) + """ + return get_events(db, last=last, limit=limit) + + +def get_events_by_block(db, block_index: int): + """ + Returns the events of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + return get_events(db, block_index=block_index) + + +def get_events_by_block_and_event(db, block_index: int, event: str): + """ + Returns the events of a block filtered by event + :param int block_index: The index of the block to return (e.g. 840464) + :param str event: The event to filter by (e.g. CREDIT) + """ + if event == "count": + return get_events_counts_by_block(db, block_index=block_index) + return get_events(db, block_index=block_index, event=event) + + +def get_event_by_index(db, event_index: int): + """ + Returns the event of an index + :param int event_index: The index of the event to return (e.g. 10665092) + """ + return get_events(db, event_index=event_index) + + +def get_events_by_name(db, event: str, last: int = None, limit: int = 100): + """ + Returns the events filtered by event name + :param str event: The event to return (e.g. CREDIT) + :param int last: The last event index to return (e.g. 10665092) + :param int limit: The maximum number of events to return (e.g. 5) + """ + return get_events(db, event=event, last=last, limit=limit) + + +def get_mempool_events(db, event_name=None): + cursor = db.cursor() + where = [] + bindings = [] + if event_name is not None: + where.append("event = ?") + bindings.append(event_name) + # no sql injection here + query = """ + SELECT tx_hash, event, bindings, timestamp + FROM mempool + """ + if event_name is not None: + query += f"""WHERE ({" AND ".join(where)})""" # nosec B608 # noqa: S608 + query += """ORDER BY timestamp DESC""" + cursor.execute(query, tuple(bindings)) + events = cursor.fetchall() + for i, _ in enumerate(events): + events[i]["bindings"] = json.loads(events[i]["bindings"]) + return events + + +def get_all_mempool_events(db): + """ + Returns all mempool events + """ + return get_mempool_events(db) + + +def get_mempool_events_by_name(db, event: str): + """ + Returns the mempool events filtered by event name + :param str event: The event to return (e.g. OPEN_ORDER) + """ + return get_mempool_events(db, event_name=event) + + +def get_events_counts(db, block_index=None): + cursor = db.cursor() + bindings = [] + query = """ + SELECT event, COUNT(*) AS event_count + FROM messages + """ + if block_index is not None: + query += "WHERE block_index = ?" + bindings.append(block_index) + query += " GROUP BY event" + cursor.execute(query, bindings) + return cursor.fetchall() + + +def get_events_counts_by_block(db, block_index: int): + """ + Returns the event counts of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + return get_events_counts(db, block_index=block_index) + + +def get_all_events_counts(db): + """ + Returns the event counts of all blocks + """ + return get_events_counts(db) + + # we are using a function here for testing purposes def curr_time(): return int(time.time()) @@ -285,7 +439,24 @@ def get_balance(db, address, asset, raise_error_if_no_balance=False, return_list return balances[0]["quantity"] -def get_address_balances(db, address): +def get_balance_by_address_and_asset(db, address: str, asset: str): + """ + Returns the balance of an address and asset + :param str address: The address to return (e.g. 1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs) + :param str asset: The asset to return (e.g. XCP) + """ + return { + "address": address, + "asset": asset, + "quantity": get_balance(db, address, asset), + } + + +def get_address_balances(db, address: str): + """ + Returns the balances of an address + :param str address: The address to return (e.g. 1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs) + """ cursor = db.cursor() query = """ SELECT address, asset, quantity, MAX(rowid) @@ -326,6 +497,283 @@ def get_balances_count(db, address): return cursor.fetchall() +def get_credits_or_debits( + db, table, address=None, asset=None, block_index=None, tx_index=None, offset=0, limit=None +): + cursor = db.cursor() + where = [] + bindings = [] + if address is not None: + where.append("address = ?") + bindings.append(address) + if asset is not None: + where.append("asset = ?") + bindings.append(asset) + if block_index is not None: + where.append("block_index = ?") + bindings.append(block_index) + if tx_index is not None: + where.append("tx_index = ?") + bindings.append(tx_index) + query_limit = "" + if limit is not None: + query_limit = "LIMIT ?" + bindings.append(limit) + query_offset = "" + if offset > 0: + query_offset = "OFFSET ?" + bindings.append(offset) + # no sql injection here + query = f"""SELECT * FROM {table} WHERE ({" AND ".join(where)}) {query_limit} {query_offset}""" # nosec B608 # noqa: S608 + cursor.execute(query, tuple(bindings)) + return cursor.fetchall() + + +def get_credits(db, address=None, asset=None, block_index=None, tx_index=None, limit=100, offset=0): + return get_credits_or_debits( + db, "credits", address, asset, block_index, tx_index, limit=limit, offset=offset + ) + + +def get_credits_by_block(db, block_index: int): + """ + Returns the credits of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + return get_credits(db, block_index=block_index) + + +def get_credits_by_address(db, address: str, limit: int = 100, offset: int = 0): + """ + Returns the credits of an address + :param str address: The address to return (e.g. 1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs) + :param int limit: The maximum number of credits to return (e.g. 5) + :param int offset: The offset of the credits to return (e.g. 0) + """ + return get_credits(db, address=address, limit=limit, offset=offset) + + +def get_credits_by_asset(db, asset: str, limit: int = 100, offset: int = 0): + """ + Returns the credits of an asset + :param str asset: The asset to return (e.g. UNNEGOTIABLE) + :param int limit: The maximum number of credits to return (e.g. 5) + :param int offset: The offset of the credits to return (e.g. 0) + """ + return get_credits(db, asset=asset, limit=limit, offset=offset) + + +def get_debits(db, address=None, asset=None, block_index=None, tx_index=None, limit=100, offset=0): + return get_credits_or_debits( + db, "debits", address, asset, block_index, tx_index, limit=limit, offset=offset + ) + + +def get_debits_by_block(db, block_index: int): + """ + Returns the debits of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + return get_debits(db, block_index=block_index) + + +def get_debits_by_address(db, address: str, limit: int = 100, offset: int = 0): + """ + Returns the debits of an address + :param str address: The address to return (e.g. bc1q7787j6msqczs58asdtetchl3zwe8ruj57p9r9y) + :param int limit: The maximum number of debits to return (e.g. 5) + :param int offset: The offset of the debits to return (e.g. 0) + """ + return get_debits(db, address=address, limit=limit, offset=offset) + + +def get_debits_by_asset(db, asset: str, limit: int = 100, offset: int = 0): + """ + Returns the debits of an asset + :param str asset: The asset to return (e.g. XCP) + :param int limit: The maximum number of debits to return (e.g. 5) + :param int offset: The offset of the debits to return (e.g. 0) + """ + return get_debits(db, asset=asset, limit=limit, offset=offset) + + +def get_sends_or_receives( + db, + source=None, + destination=None, + asset=None, + block_index=None, + status="valid", + limit=None, + offset=0, +): + cursor = db.cursor() + where = [] + bindings = [] + if source is not None: + where.append("source = ?") + bindings.append(source) + if destination is not None: + where.append("destination = ?") + bindings.append(destination) + if asset is not None: + where.append("asset = ?") + bindings.append(asset) + if block_index is not None: + where.append("block_index = ?") + bindings.append(block_index) + if status is not None: + where.append("status = ?") + bindings.append(status) + query_limit = "" + if limit is not None: + query_limit = "LIMIT ?" + bindings.append(limit) + query_offset = "" + if offset > 0: + query_offset = "OFFSET ?" + bindings.append(offset) + # no sql injection here + query = f"""SELECT * FROM sends WHERE ({" AND ".join(where)}) {query_limit} {query_offset}""" # nosec B608 # noqa: S608 + cursor.execute(query, tuple(bindings)) + return cursor.fetchall() + + +def get_sends_or_receives_by_block(db, block_index: int, limit: int = 100, offset: int = 0): + """ + Returns the sends of a block + :param int block_index: The index of the block to return (e.g. 840459) + """ + return get_sends_or_receives(db, block_index=block_index, limit=limit, offset=offset) + + +def get_sends_or_receives_by_asset(db, asset: str, limit: int = 100, offset: int = 0): + """ + Returns the sends of an asset + :param str asset: The asset to return (e.g. XCP) + :param int limit: The maximum number of sends to return (e.g. 5) + :param int offset: The offset of the sends to return (e.g. 0) + """ + return get_sends_or_receives(db, asset=asset, limit=limit, offset=offset) + + +def get_sends( + db, + address=None, + asset=None, + block_index=None, + status="valid", + limit: int = 100, + offset: int = 0, +): + return get_sends_or_receives( + db, + source=address, + asset=asset, + block_index=block_index, + status=status, + limit=limit, + offset=offset, + ) + + +def get_send_by_address(db, address: str, limit: int = 100, offset: int = 0): + """ + Returns the sends of an address + :param str address: The address to return (e.g. 1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W) + :param int limit: The maximum number of sends to return (e.g. 5) + :param int offset: The offset of the sends to return (e.g. 0) + """ + return get_sends(db, address=address, limit=limit, offset=offset) + + +def get_send_by_address_and_asset(db, address: str, asset: str): + """ + Returns the sends of an address and asset + :param str address: The address to return (e.g. 1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W) + :param str asset: The asset to return (e.g. XCP) + """ + return get_sends(db, address=address, asset=asset) + + +def get_receives( + db, + address=None, + asset=None, + block_index=None, + status="valid", + limit: int = 100, + offset: int = 0, +): + return get_sends_or_receives( + db, + destination=address, + asset=asset, + block_index=block_index, + status=status, + limit=limit, + offset=offset, + ) + + +def get_receive_by_address(db, address: str, limit: int = 100, offset: int = 0): + """ + Returns the receives of an address + :param str address: The address to return (e.g. 1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs) + :param int limit: The maximum number of receives to return (e.g. 5) + :param int offset: The offset of the receives to return (e.g. 0) + """ + return get_receives(db, address=address, limit=limit, offset=offset) + + +def get_receive_by_address_and_asset( + db, address: str, asset: str, limit: int = 100, offset: int = 0 +): + """ + Returns the receives of an address and asset + :param str address: The address to return (e.g. 1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs) + :param str asset: The asset to return (e.g. XCP) + :param int limit: The maximum number of receives to return (e.g. 5) + :param int offset: The offset of the receives to return (e.g. 0) + """ + return get_receives(db, address=address, asset=asset, limit=limit, offset=offset) + + +def get_sweeps(db, address=None, block_index=None, status="valid"): + cursor = db.cursor() + where = [] + bindings = [] + if address is not None: + where.append("source = ?") + bindings.append(address) + if block_index is not None: + where.append("block_index = ?") + bindings.append(block_index) + if status is not None: + where.append("status = ?") + bindings.append(status) + # no sql injection here + query = f"""SELECT * FROM sweeps WHERE ({" AND ".join(where)})""" # nosec B608 # noqa: S608 + cursor.execute(query, tuple(bindings)) + return cursor.fetchall() + + +def get_sweeps_by_block(db, block_index: int): + """ + Returns the sweeps of a block + :param int block_index: The index of the block to return (e.g. 836519) + """ + return get_sweeps(db, block_index=block_index) + + +def get_sweeps_by_address(db, address: str): + """ + Returns the sweeps of an address + :param str address: The address to return (e.g. 18szqTVJUWwYrtRHq98Wn4DhCGGiy3jZ87) + """ + return get_sweeps(db, address=address) + + ##################### # ISSUANCES # ##################### @@ -574,7 +1022,12 @@ def get_asset_issued(db, address): return cursor.fetchall() -def get_asset_balances(db, asset, exclude_zero_balances=True): +def get_asset_balances(db, asset: str, exclude_zero_balances: bool = True): + """ + Returns the asset balances + :param str asset: The asset to return (e.g. UNNEGOTIABLE) + :param bool exclude_zero_balances: Whether to exclude zero balances (e.g. True) + """ cursor = db.cursor() query = """ SELECT address, asset, quantity, MAX(rowid) @@ -608,22 +1061,63 @@ def get_asset_issuances_quantity(db, asset): return issuances[0]["issuances_count"] -def get_asset_info(db, asset): - if asset == config.BTC or asset == config.XCP: - return {"divisible": True} +def get_asset_info(db, asset: str): + """ + Returns the asset information + :param str asset: The asset to return (e.g. UNNEGOTIABLE) + """ + asset_name = resolve_subasset_longname(db, asset) + + # Defaults. + asset_info = { + "asset": asset_name, + "asset_longname": None, + "owner": None, + "divisible": True, + "locked": False, + "supply": 0, + "description": "", + "issuer": None, + } + + if asset_name == config.BTC: + asset_info["supply"] = backend.get_btc_supply(normalize=False) + return asset_info + + if asset_name == config.XCP: + asset_info["supply"] = xcp_supply(db) + asset_info["holder_count"] = get_asset_holder_count(db, asset) + return asset_info + + asset_info["supply"] = asset_supply(db, asset_name) + asset_info["holder_count"] = get_asset_holder_count(db, asset) + cursor = db.cursor() query = """ SELECT * FROM issuances WHERE (status = ? AND asset = ?) - ORDER BY tx_index DESC + ORDER BY rowid DESC + LIMIT 1 """ bindings = ("valid", asset) cursor.execute(query, bindings) - issuances = cursor.fetchall() - return issuances[0] + issuance = cursor.fetchone() + + asset_info = asset_info | { + "asset_longname": issuance["asset_longname"], + "owner": issuance["issuer"], + "divisible": bool(issuance["divisible"]), + "locked": bool(issuance["locked"]), + "description": issuance["description"], + "issuer": issuance["issuer"], + } + + return asset_info -def get_issuances(db, asset=None, status=None, locked=None, first=False, last=False): +def get_issuances( + db, asset=None, status=None, locked=None, block_index=None, first=False, last=False +): cursor = db.cursor() cursor = db.cursor() where = [] @@ -637,6 +1131,9 @@ def get_issuances(db, asset=None, status=None, locked=None, first=False, last=Fa if locked is not None: where.append("locked = ?") bindings.append(locked) + if block_index is not None: + where.append("block_index = ?") + bindings.append(block_index) # no sql injection here query = f"""SELECT * FROM issuances WHERE ({" AND ".join(where)})""" # nosec B608 # noqa: S608 if first: @@ -647,6 +1144,22 @@ def get_issuances(db, asset=None, status=None, locked=None, first=False, last=Fa return cursor.fetchall() +def get_issuances_by_block(db, block_index: int): + """ + Returns the issuances of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + return get_issuances(db, block_index=block_index) + + +def get_issuances_by_asset(db, asset: str): + """ + Returns the issuances of an asset + :param str asset: The asset to return (e.g. UNNEGOTIABLE) + """ + return get_issuances(db, asset=asset) + + def get_assets_by_longname(db, asset_longname): cursor = db.cursor() query = """ @@ -658,7 +1171,17 @@ def get_assets_by_longname(db, asset_longname): return cursor.fetchall() -def get_valid_assets(db): +def get_valid_assets(db, offset: int = 0, limit: int = 100): + """ + Returns the valid assets + :param int offset: The offset of the assets to return (e.g. 0) + :param int limit: The limit of the assets to return (e.g. 5) + """ + try: + int(offset) + int(limit) + except ValueError as e: + raise exceptions.InvalidArgument("Invalid offset or limit parameter") from e cursor = db.cursor() query = """ SELECT asset, asset_longname @@ -666,8 +1189,24 @@ def get_valid_assets(db): WHERE status = 'valid' GROUP BY asset ORDER BY asset ASC + LIMIT ? OFFSET ? + """ + cursor.execute(query, (limit, offset)) + return cursor.fetchall() + + +def get_dividends(db, asset: str): """ - cursor.execute(query) + Returns the dividends of an asset + :param str asset: The asset to return (e.g. GMONEYPEPE) + """ + cursor = db.cursor() + query = """ + SELECT * FROM dividends + WHERE asset = ? AND status = ? + """ + bindings = (asset, "valid") + cursor.execute(query, bindings) return cursor.fetchall() @@ -706,14 +1245,22 @@ def get_oracle_last_price(db, oracle_address, block_index): ) -def get_broadcasts_by_source(db, source, status): +def get_broadcasts_by_source(db, address: str, status: str = "valid", order_by: str = "DESC"): + """ + Returns the broadcasts of a source + :param str address: The address to return (e.g. 1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk) + :param str status: The status of the broadcasts to return (e.g. valid) + :param str order_by: The order of the broadcasts to return (e.g. ASC) + """ + if order_by not in ["ASC", "DESC"]: + raise exceptions.InvalidArgument("Invalid order_by parameter") cursor = db.cursor() - query = """ + query = f""" SELECT * FROM broadcasts WHERE (status = ? AND source = ?) - ORDER BY tx_index ASC - """ - bindings = (status, source) + ORDER BY tx_index {order_by} + """ # nosec B608 # noqa: S608 + bindings = (status, address) cursor.execute(query, bindings) return cursor.fetchall() @@ -723,25 +1270,109 @@ def get_broadcasts_by_source(db, source, status): ##################### -def get_burns(db, status=None, source=None): +def get_burns(db, address: str = None, status: str = "valid"): + """ + Returns the burns of an address + :param str address: The address to return + :param str status: The status of the burns to return + """ cursor = db.cursor() where = [] bindings = [] if status is not None: where.append("status = ?") bindings.append(status) - if source is not None: + if address is not None: where.append("source = ?") - bindings.append(source) + bindings.append(address) # no sql injection here query = f"""SELECT * FROM burns WHERE ({" AND ".join(where)})""" # nosec B608 # noqa: S608 cursor.execute(query, tuple(bindings)) return cursor.fetchall() -########################### -# TRANSACTIONS # -########################### +def get_burns_by_address(db, address: str): + """ + Returns the burns of an address + :param str address: The address to return (e.g. 1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W) + """ + return get_burns(db, address=address) + + +def get_all_burns(db, status: str = "valid", offset: int = 0, limit: int = 100): + """ + Returns the burns + :param str status: The status of the burns to return (e.g. valid) + :param int offset: The offset of the burns to return (e.g. 10) + :param int limit: The limit of the burns to return (e.g. 5) + """ + try: + int(offset) + int(limit) + except ValueError as e: + raise exceptions.InvalidArgument("Invalid offset or limit parameter") from e + cursor = db.cursor() + query = """ + SELECT * FROM burns + WHERE status = ? + ORDER BY tx_index ASC + LIMIT ? OFFSET ? + """ + bindings = (status, limit, offset) + cursor.execute(query, bindings) + return cursor.fetchall() + + +###################################### +# BLOCKS AND TRANSACTIONS # +###################################### + + +def get_blocks(db, last: int = None, limit: int = 10): + """ + Returns the list of the last ten blocks + :param int last: The index of the most recent block to return (e.g. 840000) + :param int limit: The number of blocks to return (e.g. 2) + """ + cursor = db.cursor() + bindings = [] + query = """ + SELECT * FROM blocks + """ + if last is not None: + query += "WHERE block_index <= ?" + bindings.append(last) + query += " ORDER BY block_index DESC LIMIT ?" + bindings.append(limit) + cursor.execute(query, tuple(bindings)) + return cursor.fetchall() + + +def get_block(db, block_index: int): + """ + Return the information of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + blocks = get_blocks(db, last=block_index, limit=1) + if blocks: + return blocks[0] + return None + + +def get_transactions_by_block(db, block_index: int): + """ + Returns the transactions of a block + :param int block_index: The index of the block to return (e.g. 840464) + """ + cursor = db.cursor() + query = """ + SELECT * FROM transactions + WHERE block_index = ? + ORDER BY tx_index ASC + """ + bindings = (block_index,) + cursor.execute(query, bindings) + return cursor.fetchall() def get_vouts(db, tx_hash): @@ -771,6 +1402,17 @@ def get_transactions(db, tx_hash=None): return cursor.fetchall() +def get_transaction(db, tx_hash: str): + """ + Returns the information of a transaction + :param str tx_hash: The hash of the transaction to return (e.g. 876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5) + """ + transactions = get_transactions(db, tx_hash) + if transactions: + return transactions[0] + return None + + def get_transaction_source(db, tx_hash): cursor = db.cursor() query = """SELECT source FROM transactions WHERE tx_hash = ?""" @@ -792,6 +1434,74 @@ def get_addresses(db, address=None): return cursor.fetchall() +def get_expirations(db, block_index: int): + """ + Returns the expirations of a block + :param int block_index: The index of the block to return (e.g. 840356) + """ + cursor = db.cursor() + queries = [ + """ + SELECT 'order' AS type, order_hash AS object_id FROM order_expirations + WHERE block_index = ? + """, + """ + SELECT 'order_match' AS type, order_match_id AS object_id FROM order_match_expirations + WHERE block_index = ? + """, + """ + SELECT 'bet' AS type, bet_hash AS object_id FROM bet_expirations + WHERE block_index = ? + """, + """ + SELECT 'bet_match' AS type, bet_match_id AS object_id FROM bet_match_expirations + WHERE block_index = ? + """, + """ + SELECT 'rps' AS type, rps_hash AS object_id FROM rps_expirations + WHERE block_index = ? + """, + """ + SELECT 'rps_match' AS type, rps_match_id AS object_id FROM rps_match_expirations + WHERE block_index = ? + """, + ] + query = " UNION ALL ".join(queries) + bindings = (block_index,) * 6 + cursor.execute(query, bindings) + return cursor.fetchall() + + +def get_cancels(db, block_index: int): + """ + Returns the cancels of a block + :param int block_index: The index of the block to return (e.g. 839746) + """ + cursor = db.cursor() + query = """ + SELECT * FROM cancels + WHERE block_index = ? + """ + bindings = (block_index,) + cursor.execute(query, bindings) + return cursor.fetchall() + + +def get_destructions(db, block_index: int): + """ + Returns the destructions of a block + :param int block_index: The index of the block to return (e.g. 839988) + """ + cursor = db.cursor() + query = """ + SELECT * FROM destructions + WHERE block_index = ? + """ + bindings = (block_index,) + cursor.execute(query, bindings) + return cursor.fetchall() + + ############################### # UTIL FUNCTIONS # ############################### @@ -963,6 +1673,14 @@ def get_dispenser_info(db, tx_hash=None, tx_index=None): return cursor.fetchall() +def get_dispenser_info_by_hash(db, dispenser_hash: str): + """ + Returns the dispenser information by tx_hash + :param str dispenser_hash: The hash of the dispenser to return (e.g. 753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a) + """ + return get_dispenser_info(db, tx_hash=dispenser_hash) + + def get_refilling_count(db, dispenser_tx_hash): cursor = db.cursor() query = """ @@ -1077,7 +1795,7 @@ def get_dispensers( db, status_in=None, source_in=None, - source=None, + address=None, asset=None, origin=None, status=None, @@ -1089,9 +1807,9 @@ def get_dispensers( bindings = [] # where for immutable fields first_where = [] - if source is not None: + if address is not None: first_where.append("source = ?") - bindings.append(source) + bindings.append(address) if source_in is not None: first_where.append(f"source IN ({','.join(['?' for e in range(0, len(source_in))])})") bindings += source_in @@ -1132,6 +1850,63 @@ def get_dispensers( return cursor.fetchall() +def get_dispensers_by_address(db, address: str, status: int = 0): + """ + Returns the dispensers of an address + :param str address: The address to return (e.g. bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz) + """ + return get_dispensers(db, address=address, status=status) + + +def get_dispensers_by_asset(db, asset: str, status: int = 0): + """ + Returns the dispensers of an asset + :param str asset: The asset to return (e.g. ERYKAHPEPU) + """ + return get_dispensers(db, asset=asset, status=status) + + +def get_dispensers_by_address_and_asset(db, address: str, asset: str, status: int = 0): + """ + Returns the dispensers of an address and an asset + :param str address: The address to return (e.g. bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz) + :param str asset: The asset to return (e.g. ERYKAHPEPU) + """ + return get_dispensers(db, address=address, asset=asset, status=status) + + +def get_dispenses(db, dispenser_tx_hash=None, block_index=None): + cursor = db.cursor() + where = [] + bindings = [] + if dispenser_tx_hash is not None: + where.append("dispenser_tx_hash = ?") + bindings.append(dispenser_tx_hash) + if block_index is not None: + where.append("block_index = ?") + bindings.append(block_index) + # no sql injection here + query = f"""SELECT * FROM dispenses WHERE ({" AND ".join(where)})""" # nosec B608 # noqa: S608 + cursor.execute(query, tuple(bindings)) + return cursor.fetchall() + + +def get_dispenses_by_block(db, block_index: int): + """ + Returns the dispenses of a block + :param int block_index: The index of the block to return (e.g. 840322) + """ + return get_dispenses(db, block_index=block_index) + + +def get_dispenses_by_dispenser(db, dispenser_hash: str): + """ + Returns the dispenses of a dispenser + :param str dispenser_hash: The hash of the dispenser to return (e.g. 753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a) + """ + return get_dispenses(db, dispenser_tx_hash=dispenser_hash) + + ### UPDATES ### @@ -1186,14 +1961,18 @@ def get_bet_matches_to_expire(db, block_time): return cursor.fetchall() -def get_bet(db, tx_hash): +def get_bet(db, bet_hash: str): + """ + Returns the information of a bet + :param str bet_hash: The hash of the transaction that created the bet (e.g. 5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed) + """ cursor = db.cursor() query = """ SELECT * FROM bets WHERE tx_hash = ? ORDER BY rowid DESC LIMIT 1 """ - bindings = (tx_hash,) + bindings = (bet_hash,) cursor.execute(query, bindings) return cursor.fetchall() @@ -1230,7 +2009,12 @@ def get_matching_bets(db, feed_address, bet_type): return cursor.fetchall() -def get_open_bet_by_feed(db, feed_address): +def get_bet_by_feed(db, address: str, status: str = "open"): + """ + Returns the bets of a feed + :param str address: The address of the feed (e.g. 1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk) + :param str status: The status of the bet (e.g. filled) + """ cursor = db.cursor() query = """ SELECT * FROM ( @@ -1241,7 +2025,43 @@ def get_open_bet_by_feed(db, feed_address): ) WHERE status = ? ORDER BY tx_index, tx_hash """ - bindings = (feed_address, "open") + bindings = (address, status) + cursor.execute(query, bindings) + return cursor.fetchall() + + +def get_bet_matches_by_bet(db, bet_hash: str, status: str = "pending"): + """ + Returns the bet matches of a bet + :param str bet_hash: The hash of the transaction that created the bet (e.g. 5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed) + :param str status: The status of the bet matches (e.g. expired) + """ + cursor = db.cursor() + query = """ + SELECT * FROM ( + SELECT *, MAX(rowid) + FROM bet_matches + WHERE (tx0_hash = ? OR tx1_hash = ?) + GROUP BY id + ) WHERE status = ? + """ + bindings = (bet_hash, bet_hash, status) + cursor.execute(query, bindings) + return cursor.fetchall() + + +def get_resolutions_by_bet(db, bet_hash: str): + """ + Returns the resolutions of a bet + :param str bet_hash: The hash of the transaction that created the bet (e.g. 36bbbb7dbd85054dac140a8ad8204eda2ee859545528bd2a9da69ad77c277ace) + """ + cursor = db.cursor() + query = """ + SELECT * + FROM bet_match_resolutions + WHERE bet_match_id LIKE ? + """ + bindings = (f"%{bet_hash}%",) cursor.execute(query, bindings) return cursor.fetchall() @@ -1325,14 +2145,18 @@ def get_order_matches_to_expire(db, block_index): return cursor.fetchall() -def get_order(db, tx_hash): +def get_order(db, order_hash: str): + """ + Returns the information of an order + :param str order_hash: The hash of the transaction that created the order (e.g. 23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776) + """ cursor = db.cursor() query = """ SELECT * FROM orders WHERE tx_hash = ? ORDER BY rowid DESC LIMIT 1 """ - bindings = (tx_hash,) + bindings = (order_hash,) cursor.execute(query, bindings) return cursor.fetchall() @@ -1396,7 +2220,12 @@ def get_matching_orders(db, tx_hash, give_asset, get_asset): return cursor.fetchall() -def get_orders_by_asset(db, asset, status="open"): +def get_orders_by_asset(db, asset: str, status: str = "open"): + """ + Returns the orders of an asset + :param str asset: The asset to return (e.g. NEEDPEPE) + :param str status: The status of the orders to return (e.g. filled) + """ cursor = db.cursor() query = """ SELECT * FROM ( @@ -1411,7 +2240,12 @@ def get_orders_by_asset(db, asset, status="open"): return cursor.fetchall() -def get_order_matches_by_order(db, tx_hash, status="pending"): +def get_order_matches_by_order(db, order_hash: str, status: str = "pending"): + """ + Returns the order matches of an order + :param str order_hash: The hash of the transaction that created the order (e.g. 5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947) + :param str status: The status of the order matches to return (e.g. completed) + """ cursor = db.cursor() query = """ SELECT * FROM ( @@ -1421,7 +2255,23 @@ def get_order_matches_by_order(db, tx_hash, status="pending"): GROUP BY id ) WHERE status = ? """ - bindings = (tx_hash, tx_hash, status) + bindings = (order_hash, order_hash, status) + cursor.execute(query, bindings) + return cursor.fetchall() + + +def get_btcpays_by_order(db, order_hash: str): + """ + Returns the BTC pays of an order + :param str order_hash: The hash of the transaction that created the order (e.g. 299b5b648f54eacb839f3487232d49aea373cdd681b706d4cc0b5e0b03688db4) + """ + cursor = db.cursor() + query = """ + SELECT * + FROM btcpays + WHERE order_match_id LIKE ? + """ + bindings = (f"%{order_hash}%",) cursor.execute(query, bindings) return cursor.fetchall() @@ -1854,6 +2704,21 @@ def holders(db, asset, exclude_empty_holders=False): return holders +def get_asset_holders(db, asset: str): + """ + Returns the holders of an asset + :param str asset: The asset to return (e.g. ERYKAHPEPU) + """ + asset_name = resolve_subasset_longname(db, asset) + return holders(db, asset_name, True) + + +def get_asset_holder_count(db, asset): + holders = get_asset_holders(db, asset) + addresses = [holder["address"] for holder in holders] + return len(set(addresses)) + + def xcp_created(db): """Return number of XCP created thus far.""" cursor = db.cursor() diff --git a/counterparty-core/counterpartycore/lib/log.py b/counterparty-core/counterpartycore/lib/log.py index 1df6153e37..4ff3cea510 100644 --- a/counterparty-core/counterpartycore/lib/log.py +++ b/counterparty-core/counterpartycore/lib/log.py @@ -3,6 +3,7 @@ import sys import traceback from datetime import datetime +from logging.handlers import RotatingFileHandler from colorlog import ColoredFormatter from dateutil.tz import tzlocal @@ -34,7 +35,7 @@ def set_up(verbose=False, quiet=True, log_file=None, log_in_console=False): # File Logging if log_file: max_log_size = 20 * 1024 * 1024 # 20 MB - fileh = logging.handlers.RotatingFileHandler(log_file, maxBytes=max_log_size, backupCount=5) + fileh = RotatingFileHandler(log_file, maxBytes=max_log_size, backupCount=5) fileh.setLevel(log_level) log_format = "%(asctime)s [%(levelname)s] %(message)s" formatter = logging.Formatter(log_format, "%Y-%m-%d-T%H:%M:%S%z") diff --git a/counterparty-core/counterpartycore/lib/messages/bet.py b/counterparty-core/counterpartycore/lib/messages/bet.py index 6a56955827..7a9c453e10 100644 --- a/counterparty-core/counterpartycore/lib/messages/bet.py +++ b/counterparty-core/counterpartycore/lib/messages/bet.py @@ -254,7 +254,7 @@ def cancel_bet_match(db, bet_match, status, block_index, tx_index): def get_fee_fraction(db, feed_address): """Get fee fraction from last broadcast from the feed_address address.""" - broadcasts = ledger.get_broadcasts_by_source(db, feed_address, "valid") + broadcasts = ledger.get_broadcasts_by_source(db, feed_address, "valid", order_by="ASC") if broadcasts: last_broadcast = broadcasts[-1] @@ -297,7 +297,7 @@ def validate( problems.append("integer overflow") # Look at feed to be bet on. - broadcasts = ledger.get_broadcasts_by_source(db, feed_address, "valid") + broadcasts = ledger.get_broadcasts_by_source(db, feed_address, "valid", order_by="ASC") if not broadcasts: problems.append("feed doesn’t exist") elif not broadcasts[-1]["text"]: @@ -358,15 +358,15 @@ def validate( def compose( db, - source, - feed_address, - bet_type, - deadline, - wager_quantity, - counterwager_quantity, - target_value, - leverage, - expiration, + source: str, + feed_address: str, + bet_type: int, + deadline: int, + wager_quantity: int, + counterwager_quantity: int, + target_value: int, + leverage: int, + expiration: int, ): if ledger.get_balance(db, source, config.XCP) < wager_quantity: raise exceptions.ComposeError("insufficient funds") @@ -403,9 +403,7 @@ def compose( return (source, [(feed_address, None)], data) -def parse(db, tx, message): - bet_parse_cursor = db.cursor() - +def unpack(message, return_dict=False): # Unpack message. try: if len(message) != LENGTH: @@ -429,9 +427,45 @@ def parse(db, tx, message): target_value, leverage, expiration, - fee_fraction_int, # noqa: F841 - ) = 0, 0, 0, 0, 0, 0, 0, 0 + ) = 0, 0, 0, 0, 0, 0, 0 status = "invalid: could not unpack" + if return_dict: + return { + "bet_type": bet_type, + "deadline": deadline, + "wager_quantity": wager_quantity, + "counterwager_quantity": counterwager_quantity, + "target_value": target_value, + "leverage": leverage, + "expiration": expiration, + "status": status, + } + return ( + bet_type, + deadline, + wager_quantity, + counterwager_quantity, + target_value, + leverage, + expiration, + status, + ) + + +def parse(db, tx, message): + bet_parse_cursor = db.cursor() + + # Unpack message. + ( + bet_type, + deadline, + wager_quantity, + counterwager_quantity, + target_value, + leverage, + expiration, + status, + ) = unpack(message) odds, fee_fraction = 0, 0 feed_address = tx["destination"] @@ -515,7 +549,7 @@ def parse(db, tx, message): def match(db, tx): # Get bet in question. - bets = ledger.get_bet(db, tx_hash=tx["tx_hash"]) + bets = ledger.get_bet(db, bet_hash=tx["tx_hash"]) if not bets: return else: @@ -661,7 +695,7 @@ def match(db, tx): ledger.update_bet(db, tx1["tx_hash"], set_data) # Get last value of feed. - broadcasts = ledger.get_broadcasts_by_source(db, feed_address, "valid") + broadcasts = ledger.get_broadcasts_by_source(db, feed_address, "valid", order_by="ASC") initial_value = broadcasts[-1]["value"] # Record bet fulfillment. diff --git a/counterparty-core/counterpartycore/lib/messages/broadcast.py b/counterparty-core/counterpartycore/lib/messages/broadcast.py index 6a7dff6049..0b55e0eee1 100644 --- a/counterparty-core/counterpartycore/lib/messages/broadcast.py +++ b/counterparty-core/counterpartycore/lib/messages/broadcast.py @@ -111,7 +111,7 @@ def validate(db, source, timestamp, value, fee_fraction_int, text, block_index): if not source: problems.append("null source address") # Check previous broadcast in this feed. - broadcasts = ledger.get_broadcasts_by_source(db, source, "valid") + broadcasts = ledger.get_broadcasts_by_source(db, source, "valid", order_by="ASC") if broadcasts: last_broadcast = broadcasts[-1] if last_broadcast["locked"]: @@ -135,7 +135,7 @@ def validate(db, source, timestamp, value, fee_fraction_int, text, block_index): return problems -def compose(db, source, timestamp, value, fee_fraction, text): +def compose(db, source: str, timestamp: int, value: float, fee_fraction: float, text: str): # Store the fee fraction as an integer. fee_fraction_int = int(fee_fraction * 1e8) @@ -162,12 +162,9 @@ def compose(db, source, timestamp, value, fee_fraction, text): return (source, [], data) -def parse(db, tx, message): - cursor = db.cursor() - - # Unpack message. +def unpack(message, block_index, return_dict=False): try: - if ledger.enabled("broadcast_pack_text", tx["block_index"]): + if ledger.enabled("broadcast_pack_text", block_index): timestamp, value, fee_fraction_int, rawtext = struct.unpack( FORMAT + f"{len(message) - LENGTH}s", message ) @@ -197,6 +194,24 @@ def parse(db, tx, message): except AssertionError: timestamp, value, fee_fraction_int, text = 0, None, 0, None status = "invalid: could not unpack text" + + if return_dict: + return { + "timestamp": timestamp, + "value": value, + "fee_fraction_int": fee_fraction_int, + "text": text, + "status": status, + } + return timestamp, value, fee_fraction_int, text, status + + +def parse(db, tx, message): + cursor = db.cursor() + + # Unpack message. + timestamp, value, fee_fraction_int, text, status = unpack(message, tx["block_index"]) + if status == "valid": # For SQLite3 timestamp = min(timestamp, config.MAX_INT) @@ -256,7 +271,7 @@ def parse(db, tx, message): if value is None or value < 0: # Cancel Open Bets? if value == -2: - for i in ledger.get_open_bet_by_feed(db, tx["source"]): + for i in ledger.get_bet_by_feed(db, tx["source"], status="open"): bet.cancel_bet(db, i, "dropped", tx["block_index"], tx["tx_index"]) # Cancel Pending Bet Matches? if value == -3: diff --git a/counterparty-core/counterpartycore/lib/messages/btcpay.py b/counterparty-core/counterpartycore/lib/messages/btcpay.py index f4dec2b7bb..49308b5e14 100644 --- a/counterparty-core/counterpartycore/lib/messages/btcpay.py +++ b/counterparty-core/counterpartycore/lib/messages/btcpay.py @@ -106,7 +106,7 @@ def validate(db, source, order_match_id, block_index): return destination, btc_quantity, escrowed_asset, escrowed_quantity, order_match, problems -def compose(db, source, order_match_id): +def compose(db, source: str, order_match_id: str): tx0_hash, tx1_hash = util.parse_id(order_match_id) destination, btc_quantity, escrowed_asset, escrowed_quantity, order_match, problems = validate( @@ -133,10 +133,7 @@ def compose(db, source, order_match_id): return (source, [(destination, btc_quantity)], data) -def parse(db, tx, message): - cursor = db.cursor() - - # Unpack message. +def unpack(message, return_dict=False): try: if len(message) != LENGTH: raise exceptions.UnpackError @@ -151,6 +148,22 @@ def parse(db, tx, message): tx0_hash, tx1_hash, order_match_id = None, None, None status = "invalid: could not unpack" + if return_dict: + return { + "tx0_hash": tx0_hash, + "tx1_hash": tx1_hash, + "order_match_id": order_match_id, + "status": status, + } + return tx0_hash, tx1_hash, order_match_id, status + + +def parse(db, tx, message): + cursor = db.cursor() + + # Unpack message. + tx0_hash, tx1_hash, order_match_id, status = unpack(message) + if status == "valid": destination, btc_quantity, escrowed_asset, escrowed_quantity, order_match, problems = ( validate(db, tx["source"], order_match_id, tx["block_index"]) diff --git a/counterparty-core/counterpartycore/lib/messages/burn.py b/counterparty-core/counterpartycore/lib/messages/burn.py index 568cb51d6e..f7cc8f2a8d 100644 --- a/counterparty-core/counterpartycore/lib/messages/burn.py +++ b/counterparty-core/counterpartycore/lib/messages/burn.py @@ -72,7 +72,7 @@ def validate(db, source, destination, quantity, block_index, overburn=False): return problems -def compose(db, source, quantity, overburn=False): +def compose(db, source: str, quantity: int, overburn: bool = False): cursor = db.cursor() destination = config.UNSPENDABLE problems = validate( @@ -82,7 +82,7 @@ def compose(db, source, quantity, overburn=False): raise exceptions.ComposeError(problems) # Check that a maximum of 1 BTC total is burned per address. - burns = ledger.get_burns(db, status="valid", source=source) + burns = ledger.get_burns(db, source) already_burned = sum([burn["burned"] for burn in burns]) if quantity > (1 * config.UNIT - already_burned) and not overburn: @@ -118,7 +118,7 @@ def parse(db, tx, mainnet_burns, message=None): if status == "valid": # Calculate quantity of XCP earned. (Maximum 1 BTC in total, ever.) - burns = ledger.get_burns(db, status="valid", source=tx["source"]) + burns = ledger.get_burns(db, tx["source"]) already_burned = sum([burn["burned"] for burn in burns]) one = 1 * config.UNIT max_burn = one - already_burned diff --git a/counterparty-core/counterpartycore/lib/messages/cancel.py b/counterparty-core/counterpartycore/lib/messages/cancel.py index 22c57e59bd..4ab5bc3ebb 100644 --- a/counterparty-core/counterpartycore/lib/messages/cancel.py +++ b/counterparty-core/counterpartycore/lib/messages/cancel.py @@ -56,8 +56,8 @@ def validate(db, source, offer_hash): problems = [] # TODO: make query only if necessary - orders = ledger.get_order(db, tx_hash=offer_hash) - bets = ledger.get_bet(db, tx_hash=offer_hash) + orders = ledger.get_order(db, order_hash=offer_hash) + bets = ledger.get_bet(db, bet_hash=offer_hash) rps = ledger.get_rps(db, tx_hash=offer_hash) offer_type = None @@ -82,7 +82,7 @@ def validate(db, source, offer_hash): return offer, offer_type, problems -def compose(db, source, offer_hash): +def compose(db, source: str, offer_hash: str): # Check that offer exists. offer, offer_type, problems = validate(db, source, offer_hash) if problems: @@ -94,10 +94,7 @@ def compose(db, source, offer_hash): return (source, [], data) -def parse(db, tx, message): - cursor = db.cursor() - - # Unpack message. +def unpack(message, return_dict=False): try: if len(message) != LENGTH: raise exceptions.UnpackError @@ -107,6 +104,19 @@ def parse(db, tx, message): except (exceptions.UnpackError, struct.error) as e: # noqa: F841 offer_hash = None status = "invalid: could not unpack" + if return_dict: + return { + "offer_hash": offer_hash, + "status": status, + } + return offer_hash, status + + +def parse(db, tx, message): + cursor = db.cursor() + + # Unpack message. + offer_hash, status = unpack(message) if status == "valid": offer, offer_type, problems = validate(db, tx["source"], offer_hash) diff --git a/counterparty-core/counterpartycore/lib/messages/destroy.py b/counterparty-core/counterpartycore/lib/messages/destroy.py index 5793a0c15b..1e556b58d6 100644 --- a/counterparty-core/counterpartycore/lib/messages/destroy.py +++ b/counterparty-core/counterpartycore/lib/messages/destroy.py @@ -68,7 +68,7 @@ def pack(db, asset, quantity, tag): return data -def unpack(db, message): +def unpack(db, message, return_dict=False): try: asset_id, quantity = struct.unpack(FORMAT, message[0:16]) tag = message[16:] @@ -80,6 +80,8 @@ def unpack(db, message): except AssetIDError: # noqa: F405 raise UnpackError("asset id invalid") # noqa: B904, F405 + if return_dict: + return {"asset": asset, "quantity": quantity, "tag": tag} return asset, quantity, tag @@ -113,7 +115,7 @@ def validate(db, source, destination, asset, quantity): raise BalanceError("balance insufficient") # noqa: F405 -def compose(db, source, asset, quantity, tag): +def compose(db, source: str, asset: str, quantity: int, tag: str): # resolve subassets asset = ledger.resolve_subasset_longname(db, asset) diff --git a/counterparty-core/counterpartycore/lib/messages/dispenser.py b/counterparty-core/counterpartycore/lib/messages/dispenser.py index 2206bab853..9dbf07af54 100644 --- a/counterparty-core/counterpartycore/lib/messages/dispenser.py +++ b/counterparty-core/counterpartycore/lib/messages/dispenser.py @@ -228,12 +228,12 @@ def validate( and open_address != source ): open_dispensers = ledger.get_dispensers( - db, status_in=[0, 11], source=open_address, asset=asset, origin=source + db, status_in=[0, 11], address=open_address, asset=asset, origin=source ) else: query_address = open_address if status == STATUS_OPEN_EMPTY_ADDRESS else source open_dispensers = ledger.get_dispensers( - db, status_in=[0, 11], source=query_address, asset=asset + db, status_in=[0, 11], address=query_address, asset=asset ) if len(open_dispensers) == 0 or open_dispensers[0]["status"] != STATUS_CLOSING: @@ -338,14 +338,14 @@ def validate( def compose( db, - source, - asset, - give_quantity, - escrow_quantity, - mainchainrate, - status, - open_address=None, - oracle_address=None, + source: str, + asset: str, + give_quantity: int, + escrow_quantity: int, + mainchainrate: int, + status: int, + open_address: str = None, + oracle_address: str = None, ): assetid, problems = validate( db, @@ -409,12 +409,9 @@ def calculate_oracle_fee( return oracle_fee_btc -def parse(db, tx, message): - cursor = db.cursor() - - # Unpack message. +def unpack(message, return_dict=False): try: - action_address = tx["source"] + action_address = None oracle_address = None assetid, give_quantity, escrow_quantity, mainchainrate, dispenser_status = struct.unpack( FORMAT, message[0:LENGTH] @@ -432,9 +429,57 @@ def parse(db, tx, message): asset = ledger.generate_asset_name(assetid, ledger.CURRENT_BLOCK_INDEX) status = "valid" except (exceptions.UnpackError, struct.error) as e: # noqa: F841 - assetid, give_quantity, mainchainrate, asset = None, None, None, None + ( + give_quantity, + escrow_quantity, + mainchainrate, + dispenser_status, + action_address, + oracle_address, + asset, + ) = None, None, None, None, None, None, None status = "invalid: could not unpack" + if return_dict: + return { + "asset": asset, + "give_quantity": give_quantity, + "escrow_quantity": escrow_quantity, + "mainchainrate": mainchainrate, + "dispenser_status": dispenser_status, + "action_address": action_address, + "oracle_address": oracle_address, + "status": status, + } + return ( + asset, + give_quantity, + escrow_quantity, + mainchainrate, + dispenser_status, + action_address, + oracle_address, + status, + ) + + +def parse(db, tx, message): + cursor = db.cursor() + + # Unpack message. + ( + asset, + give_quantity, + escrow_quantity, + mainchainrate, + dispenser_status, + action_address, + oracle_address, + status, + ) = unpack(message) + if action_address is None: + action_address = tx["source"] + if status == "valid": if ledger.enabled("dispenser_parsing_validation", ledger.CURRENT_BLOCK_INDEX): asset_id, problems = validate( @@ -459,7 +504,7 @@ def parse(db, tx, message): else: if dispenser_status == STATUS_OPEN or dispenser_status == STATUS_OPEN_EMPTY_ADDRESS: existing = ledger.get_dispensers( - db, source=action_address, asset=asset, status=STATUS_OPEN + db, address=action_address, asset=asset, status=STATUS_OPEN ) if len(existing) == 0: @@ -612,7 +657,7 @@ def parse(db, tx, message): ) dispenser_tx_hash = ledger.get_dispensers( - db, source=action_address, asset=asset, status=STATUS_OPEN + db, address=action_address, asset=asset, status=STATUS_OPEN )[0]["tx_hash"] bindings_refill = { "tx_index": tx["tx_index"], @@ -647,14 +692,14 @@ def parse(db, tx, message): if close_from_another_address: existing = ledger.get_dispensers( db, - source=action_address, + address=action_address, asset=asset, status=STATUS_OPEN, origin=tx["source"], ) else: existing = ledger.get_dispensers( - db, source=tx["source"], asset=asset, status=STATUS_OPEN + db, address=tx["source"], asset=asset, status=STATUS_OPEN ) if len(existing) == 1: if close_delay == 0: @@ -692,7 +737,7 @@ def is_dispensable(db, address, amount): if address is None: return False - dispensers = ledger.get_dispensers(db, source=address, status_in=[0, 11]) + dispensers = ledger.get_dispensers(db, address=address, status_in=[0, 11]) for next_dispenser in dispensers: if next_dispenser["oracle_address"] != None: # noqa: E711 @@ -731,7 +776,7 @@ def dispense(db, tx): dispensers = [] if next_out["destination"] is not None: dispensers = ledger.get_dispensers( - db, source=next_out["destination"], status_in=[0, 11], order_by="asset" + db, address=next_out["destination"], status_in=[0, 11], order_by="asset" ) for dispenser in dispensers: diff --git a/counterparty-core/counterpartycore/lib/messages/dividend.py b/counterparty-core/counterpartycore/lib/messages/dividend.py index eec5dd54c8..d7f5afe433 100644 --- a/counterparty-core/counterpartycore/lib/messages/dividend.py +++ b/counterparty-core/counterpartycore/lib/messages/dividend.py @@ -179,7 +179,7 @@ def validate(db, source, quantity_per_unit, asset, dividend_asset, block_index): return dividend_total, outputs, problems, fee -def compose(db, source, quantity_per_unit, asset, dividend_asset): +def compose(db, source: str, quantity_per_unit: int, asset: str, dividend_asset: str): # resolve subassets asset = ledger.resolve_subasset_longname(db, asset) dividend_asset = ledger.resolve_subasset_longname(db, dividend_asset) @@ -207,23 +207,16 @@ def compose(db, source, quantity_per_unit, asset, dividend_asset): return (source, [], data) -def parse(db, tx, message): - dividend_parse_cursor = db.cursor() - - fee = 0 - - # Unpack message. +def unpack(db, message, block_index, return_dict=False): try: - if (tx["block_index"] > 288150 or config.TESTNET or config.REGTEST) and len( - message - ) == LENGTH_2: + if (block_index > 288150 or config.TESTNET or config.REGTEST) and len(message) == LENGTH_2: quantity_per_unit, asset_id, dividend_asset_id = struct.unpack(FORMAT_2, message) - asset = ledger.get_asset_name(db, asset_id, tx["block_index"]) - dividend_asset = ledger.get_asset_name(db, dividend_asset_id, tx["block_index"]) + asset = ledger.get_asset_name(db, asset_id, block_index) + dividend_asset = ledger.get_asset_name(db, dividend_asset_id, block_index) status = "valid" elif len(message) == LENGTH_1: quantity_per_unit, asset_id = struct.unpack(FORMAT_1, message) - asset = ledger.get_asset_name(db, asset_id, tx["block_index"]) + asset = ledger.get_asset_name(db, asset_id, block_index) dividend_asset = config.XCP status = "valid" else: @@ -232,6 +225,24 @@ def parse(db, tx, message): dividend_asset, quantity_per_unit, asset = None, None, None status = "invalid: could not unpack" + if return_dict: + return { + "asset": asset, + "quantity_per_unit": quantity_per_unit, + "dividend_asset": dividend_asset, + "status": status, + } + return asset, quantity_per_unit, dividend_asset, status + + +def parse(db, tx, message): + dividend_parse_cursor = db.cursor() + + fee = 0 + + # Unpack message. + asset, quantity_per_unit, dividend_asset, status = unpack(db, message, tx["block_index"]) + if dividend_asset == config.BTC: status = f"invalid: cannot pay {config.BTC} dividends within protocol" diff --git a/counterparty-core/counterpartycore/lib/messages/issuance.py b/counterparty-core/counterpartycore/lib/messages/issuance.py index 91db18cc2f..81d1a6ff81 100644 --- a/counterparty-core/counterpartycore/lib/messages/issuance.py +++ b/counterparty-core/counterpartycore/lib/messages/issuance.py @@ -347,14 +347,14 @@ def validate( def compose( db, - source, - asset, - quantity, - transfer_destination=None, - divisible=None, - lock=None, - reset=None, - description=None, + source: str, + asset: str, + quantity: int, + transfer_destination: str = None, + divisible: bool = None, + lock: bool = None, + reset: bool = None, + description: str = None, ): # Callability is deprecated, so for re‐issuances set relevant parameters # to old values; for first issuances, make uncallable. @@ -565,27 +565,26 @@ def compose( return (source, destination_outputs, data) -def parse(db, tx, message, message_type_id): - issuance_parse_cursor = db.cursor() +def unpack(db, message, message_type_id, block_index, return_dict=False): asset_format = ledger.get_value_by_block_index( - "issuance_asset_serialization_format", tx["block_index"] + "issuance_asset_serialization_format", block_index ) asset_format_length = ledger.get_value_by_block_index( - "issuance_asset_serialization_length", tx["block_index"] + "issuance_asset_serialization_length", block_index ) subasset_format = ledger.get_value_by_block_index( - "issuance_subasset_serialization_format", tx["block_index"] + "issuance_subasset_serialization_format", block_index ) subasset_format_length = ledger.get_value_by_block_index( - "issuance_subasset_serialization_length", tx["block_index"] + "issuance_subasset_serialization_length", block_index ) # Unpack message. try: subasset_longname = None if message_type_id == LR_SUBASSET_ID or message_type_id == SUBASSET_ID: - if not ledger.enabled("subassets", block_index=tx["block_index"]): - logger.warning(f"subassets are not enabled at block {tx['block_index']}") + if not ledger.enabled("subassets", block_index=block_index): + logger.warning(f"subassets are not enabled at block {block_index}") raise exceptions.UnpackError # parse a subasset original issuance message @@ -607,9 +606,7 @@ def parse(db, tx, message, message_type_id): description_length = len(message) - subasset_format_length - compacted_subasset_length if description_length < 0: - logger.warning( - f"invalid subasset length: [issuance] tx [{tx['tx_hash']}]: {compacted_subasset_length}" - ) + logger.warning(f"invalid subasset length: {compacted_subasset_length}") raise exceptions.UnpackError messages_format = f">{compacted_subasset_length}s{description_length}s" compacted_subasset_longname, description = struct.unpack( @@ -628,7 +625,7 @@ def parse(db, tx, message, message_type_id): description = None except UnicodeDecodeError: description = "" - elif (tx["block_index"] > 283271 or config.TESTNET or config.REGTEST) and len( + elif (block_index > 283271 or config.TESTNET or config.REGTEST) and len( message ) >= asset_format_length: # Protocol change. if (len(message) - asset_format_length <= 42) and not ledger.enabled( @@ -698,11 +695,11 @@ def parse(db, tx, message, message_type_id): "", ) try: - asset = ledger.generate_asset_name(asset_id, tx["block_index"]) + asset = ledger.generate_asset_name(asset_id, block_index) ##This is for backwards compatibility with assets names longer than 12 characters if asset.startswith("A"): - named_asset = ledger.get_asset_name(db, asset_id, tx["block_index"]) + named_asset = ledger.get_asset_name(db, asset_id, block_index) if named_asset != 0: asset = named_asset @@ -718,7 +715,21 @@ def parse(db, tx, message, message_type_id): asset = None status = "invalid: bad asset name" except exceptions.UnpackError as e: # noqa: F841 - asset, quantity, divisible, lock, reset, callable_, call_date, call_price, description = ( + ( + asset_id, + asset, + subasset_longname, + quantity, + divisible, + lock, + reset, + callable_, + call_date, + call_price, + description, + ) = ( + None, + None, None, None, None, @@ -731,6 +742,52 @@ def parse(db, tx, message, message_type_id): ) status = "invalid: could not unpack" + if return_dict: + return { + "asset_id": asset_id, + "asset": asset, + "subasset_longname": subasset_longname, + "quantity": quantity, + "divisible": divisible, + "lock": lock, + "reset": reset, + "callable": callable_, + "call_date": call_date, + "call_price": call_price, + "description": description, + "status": status, + } + return ( + asset_id, + asset, + subasset_longname, + quantity, + divisible, + lock, + reset, + callable_, + call_date, + call_price, + description, + status, + ) + + +def parse(db, tx, message, message_type_id): + ( + asset_id, + asset, + subasset_longname, + quantity, + divisible, + lock, + reset, + callable_, + call_date, + call_price, + description, + status, + ) = unpack(db, message, message_type_id, tx["block_index"]) # parse and validate the subasset from the message subasset_parent = None if status == "valid" and subasset_longname is not None: # Protocol change. @@ -942,5 +999,3 @@ def parse(db, tx, message, message_type_id): action="issuance", event=tx["tx_hash"], ) - - issuance_parse_cursor.close() diff --git a/counterparty-core/counterpartycore/lib/messages/order.py b/counterparty-core/counterpartycore/lib/messages/order.py index 6339b44438..29d87356eb 100644 --- a/counterparty-core/counterpartycore/lib/messages/order.py +++ b/counterparty-core/counterpartycore/lib/messages/order.py @@ -247,7 +247,7 @@ def cancel_order_match(db, order_match, status, block_index, tx_index): ledger.update_order_match_status(db, order_match["id"], status) # If tx0 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. - orders = ledger.get_order(db, tx_hash=order_match["tx0_hash"]) + orders = ledger.get_order(db, order_hash=order_match["tx0_hash"]) assert len(orders) == 1 tx0_order = orders[0] if tx0_order["status"] in ("expired", "cancelled"): @@ -290,7 +290,7 @@ def cancel_order_match(db, order_match, status, block_index, tx_index): ledger.update_order(db, order_match["tx0_hash"], set_data) # If tx1 is dead, credit address directly; if not, replenish give remaining, get remaining, and fee required remaining. - orders = ledger.get_order(db, tx_hash=order_match["tx1_hash"]) + orders = ledger.get_order(db, order_hash=order_match["tx1_hash"]) assert len(orders) == 1 tx1_order = orders[0] if tx1_order["status"] in ("expired", "cancelled"): @@ -432,7 +432,14 @@ def validate( def compose( - db, source, give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required + db, + source: str, + give_asset: str, + give_quantity: int, + get_asset: str, + get_quantity: int, + expiration: int, + fee_required: int, ): cursor = db.cursor() @@ -470,18 +477,15 @@ def compose( return (source, [], data) -def parse(db, tx, message): - order_parse_cursor = db.cursor() - - # Unpack message. +def unpack(db, message, block_index, return_dict=False): try: if len(message) != LENGTH: raise exceptions.UnpackError give_id, give_quantity, get_id, get_quantity, expiration, fee_required = struct.unpack( FORMAT, message ) - give_asset = ledger.get_asset_name(db, give_id, tx["block_index"]) - get_asset = ledger.get_asset_name(db, get_id, tx["block_index"]) + give_asset = ledger.get_asset_name(db, give_id, block_index) + get_asset = ledger.get_asset_name(db, get_id, block_index) status = "open" except (exceptions.UnpackError, exceptions.AssetNameError, struct.error) as e: # noqa: F841 give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required = ( @@ -494,6 +498,27 @@ def parse(db, tx, message): ) status = "invalid: could not unpack" + if return_dict: + return { + "give_asset": give_asset, + "give_quantity": give_quantity, + "get_asset": get_asset, + "get_quantity": get_quantity, + "expiration": expiration, + "fee_required": fee_required, + "status": status, + } + return give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, status + + +def parse(db, tx, message): + order_parse_cursor = db.cursor() + + # Unpack message. + (give_asset, give_quantity, get_asset, get_quantity, expiration, fee_required, status) = unpack( + db, message, tx["block_index"] + ) + price = 0 if status == "open": try: @@ -588,7 +613,7 @@ def match(db, tx, block_index=None): cursor = db.cursor() # Get order in question. - orders = ledger.get_order(db, tx_hash=tx["tx_hash"]) + orders = ledger.get_order(db, order_hash=tx["tx_hash"]) if not orders: cursor.close() return @@ -941,14 +966,14 @@ def expire(db, block_index): if order_match["backward_asset"] == "BTC" and order_match["status"] == "expired": cancel_order( db, - ledger.get_order(db, tx_hash=order_match["tx1_hash"])[0], + ledger.get_order(db, order_hash=order_match["tx1_hash"])[0], "expired", block_index, ) if order_match["forward_asset"] == "BTC" and order_match["status"] == "expired": cancel_order( db, - ledger.get_order(db, tx_hash=order_match["tx0_hash"])[0], + ledger.get_order(db, order_hash=order_match["tx0_hash"])[0], "expired", block_index, ) @@ -956,7 +981,7 @@ def expire(db, block_index): if block_index >= 315000 or config.TESTNET or config.REGTEST: # Protocol change. # Re‐match. for order_match in order_matches: - match(db, ledger.get_order(db, tx_hash=order_match["tx0_hash"])[0], block_index) - match(db, ledger.get_order(db, tx_hash=order_match["tx1_hash"])[0], block_index) + match(db, ledger.get_order(db, order_hash=order_match["tx0_hash"])[0], block_index) + match(db, ledger.get_order(db, order_hash=order_match["tx1_hash"])[0], block_index) cursor.close() diff --git a/counterparty-core/counterpartycore/lib/messages/rps.py b/counterparty-core/counterpartycore/lib/messages/rps.py index daf1f10b64..134dd89f38 100644 --- a/counterparty-core/counterpartycore/lib/messages/rps.py +++ b/counterparty-core/counterpartycore/lib/messages/rps.py @@ -282,7 +282,9 @@ def validate(db, source, possible_moves, wager, move_random_hash, expiration, bl return problems -def compose(db, source, possible_moves, wager, move_random_hash, expiration): +def compose( + db, source: str, possible_moves: int, wager: int, move_random_hash: str, expiration: int +): problems = validate( db, source, possible_moves, wager, move_random_hash, expiration, ledger.CURRENT_BLOCK_INDEX ) @@ -298,9 +300,7 @@ def compose(db, source, possible_moves, wager, move_random_hash, expiration): return (source, [], data) -def parse(db, tx, message): - rps_parse_cursor = db.cursor() - # Unpack message. +def unpack(message, return_dict=False): try: if len(message) != LENGTH: raise exceptions.UnpackError @@ -310,6 +310,22 @@ def parse(db, tx, message): (possible_moves, wager, move_random_hash, expiration) = 0, 0, "", 0 status = "invalid: could not unpack" + if return_dict: + return { + "possible_moves": possible_moves, + "wager": wager, + "move_random_hash": binascii.hexlify(move_random_hash).decode("utf8"), + "expiration": expiration, + "status": status, + } + return possible_moves, wager, move_random_hash, expiration, status + + +def parse(db, tx, message): + rps_parse_cursor = db.cursor() + # Unpack message. + possible_moves, wager, move_random_hash, expiration, status = unpack(message) + if status == "open": move_random_hash = binascii.hexlify(move_random_hash).decode("utf8") # Overbet diff --git a/counterparty-core/counterpartycore/lib/messages/rpsresolve.py b/counterparty-core/counterpartycore/lib/messages/rpsresolve.py index 9e4634ae2f..77c8bf4ca0 100644 --- a/counterparty-core/counterpartycore/lib/messages/rpsresolve.py +++ b/counterparty-core/counterpartycore/lib/messages/rpsresolve.py @@ -106,7 +106,7 @@ def validate(db, source, move, random, rps_match_id): return txn, rps_match, problems -def compose(db, source, move, random, rps_match_id): +def compose(db, source: str, move: int, random: str, rps_match_id: str): tx0_hash, tx1_hash = util.parse_id(rps_match_id) txn, rps_match, problems = validate(db, source, move, random, rps_match_id) @@ -128,10 +128,7 @@ def compose(db, source, move, random, rps_match_id): return (source, [], data) -def parse(db, tx, message): - cursor = db.cursor() - - # Unpack message. +def unpack(message, return_dict=False): try: if len(message) != LENGTH: raise exceptions.UnpackError @@ -147,6 +144,22 @@ def parse(db, tx, message): move, random, tx0_hash, tx1_hash, rps_match_id = None, None, None, None, None status = "invalid: could not unpack" + if return_dict: + return { + "move": move, + "random": random, + "rps_match_id": rps_match_id, + "status": status, + } + return move, random, rps_match_id, status + + +def parse(db, tx, message): + cursor = db.cursor() + + # Unpack message. + move, random, rps_match_id, status = unpack(message) + if status == "valid": txn, rps_match, problems = validate(db, tx["source"], move, random, rps_match_id) if problems: diff --git a/counterparty-core/counterpartycore/lib/messages/send.py b/counterparty-core/counterpartycore/lib/messages/send.py index fbe0a3a96e..decce16caa 100644 --- a/counterparty-core/counterpartycore/lib/messages/send.py +++ b/counterparty-core/counterpartycore/lib/messages/send.py @@ -112,7 +112,14 @@ def validate(db, source, destination, asset, quantity, block_index): def compose( - db, source, destination, asset, quantity, memo=None, memo_is_hex=False, use_enhanced_send=None + db, + source: str, + destination: str, + asset: str, + quantity: int, + memo: str = None, + memo_is_hex: bool = False, + use_enhanced_send: bool = None, ): # special case - enhanced_send replaces send by default when it is enabled # but it can be explicitly disabled with an API parameter diff --git a/counterparty-core/counterpartycore/lib/messages/sweep.py b/counterparty-core/counterpartycore/lib/messages/sweep.py index 9ed81c7ae2..89d7427fd6 100644 --- a/counterparty-core/counterpartycore/lib/messages/sweep.py +++ b/counterparty-core/counterpartycore/lib/messages/sweep.py @@ -103,10 +103,11 @@ def validate(db, source, destination, flags, memo, block_index): return problems, total_fee -def compose(db, source, destination, flags, memo): +def compose(db, source: str, destination: str, flags: int, memo: str): if memo is None: memo = b"" elif flags & FLAG_BINARY_MEMO: + print("MEMEOOOO", memo) memo = bytes.fromhex(memo) else: memo = memo.encode("utf-8") @@ -126,7 +127,7 @@ def compose(db, source, destination, flags, memo): return (source, [], data) -def unpack(db, message, block_index): +def unpack(message): try: memo_bytes_length = len(message) - LENGTH if memo_bytes_length < 0: @@ -162,7 +163,7 @@ def parse(db, tx, message): # Unpack message. try: - unpacked = unpack(db, message, tx["block_index"]) + unpacked = unpack(message) destination, flags, memo_bytes = ( unpacked["destination"], unpacked["flags"], diff --git a/counterparty-core/counterpartycore/lib/messages/versions/enhanced_send.py b/counterparty-core/counterpartycore/lib/messages/versions/enhanced_send.py index a7bbca7945..b423af2f9f 100644 --- a/counterparty-core/counterpartycore/lib/messages/versions/enhanced_send.py +++ b/counterparty-core/counterpartycore/lib/messages/versions/enhanced_send.py @@ -14,7 +14,7 @@ ID = 2 # 0x02 -def unpack(db, message, block_index): +def unpack(message, block_index): try: # account for memo bytes memo_bytes_length = len(message) - LENGTH @@ -98,7 +98,9 @@ def validate(db, source, destination, asset, quantity, memo_bytes, block_index): return problems -def compose(db, source, destination, asset, quantity, memo, memo_is_hex): +def compose( + db, source: str, destination: str, asset: str, quantity: int, memo: str, memo_is_hex: bool +): cursor = db.cursor() # Just send BTC? @@ -150,7 +152,7 @@ def parse(db, tx, message): # Unpack message. try: - unpacked = unpack(db, message, tx["block_index"]) + unpacked = unpack(message, tx["block_index"]) asset, quantity, destination, memo_bytes = ( unpacked["asset"], unpacked["quantity"], diff --git a/counterparty-core/counterpartycore/lib/messages/versions/mpma.py b/counterparty-core/counterpartycore/lib/messages/versions/mpma.py index 95010bafd2..2d6a2c5c48 100644 --- a/counterparty-core/counterpartycore/lib/messages/versions/mpma.py +++ b/counterparty-core/counterpartycore/lib/messages/versions/mpma.py @@ -21,7 +21,7 @@ ## expected functions for message version -def unpack(db, message, block_index): +def unpack(message, block_index): try: unpacked = _decode_mpma_send_decode(message, block_index) except struct.error as e: # noqa: F841 @@ -98,7 +98,7 @@ def validate(db, source, asset_dest_quant_list, block_index): return problems -def compose(db, source, asset_dest_quant_list, memo, memo_is_hex): +def compose(db, source: str, asset_dest_quant_list: list, memo: str, memo_is_hex: bool): cursor = db.cursor() out_balances = util.accumulate([(t[0], t[2]) for t in asset_dest_quant_list]) @@ -132,7 +132,7 @@ def compose(db, source, asset_dest_quant_list, memo, memo_is_hex): def parse(db, tx, message): try: - unpacked = unpack(db, message, tx["block_index"]) + unpacked = unpack(message, tx["block_index"]) status = "valid" except struct.error as e: # noqa: F841 status = "invalid: truncated message" diff --git a/counterparty-core/counterpartycore/lib/messages/versions/send1.py b/counterparty-core/counterpartycore/lib/messages/versions/send1.py index 95e6642376..459c166d48 100644 --- a/counterparty-core/counterpartycore/lib/messages/versions/send1.py +++ b/counterparty-core/counterpartycore/lib/messages/versions/send1.py @@ -68,7 +68,7 @@ def validate(db, source, destination, asset, quantity, block_index): return problems -def compose(db, source, destination, asset, quantity): +def compose(db, source: str, destination: str, asset: str, quantity: int): cursor = db.cursor() # Just send BTC? diff --git a/counterparty-core/counterpartycore/lib/script.py b/counterparty-core/counterpartycore/lib/script.py index f8ae1364fe..df8424571d 100644 --- a/counterparty-core/counterpartycore/lib/script.py +++ b/counterparty-core/counterpartycore/lib/script.py @@ -12,8 +12,12 @@ from bitcoin.core.key import CPubKey from counterparty_rs import b58, utils -# We are using PyCryptodome not PyCrypto -# from Crypto.Hash import RIPEMD160 +# TODO: Use `python-bitcointools` instead. (Get rid of `pycoin` dependency.) +from pycoin.ecdsa.secp256k1 import secp256k1_generator as generator_secp256k1 +from pycoin.encoding.b58 import a2b_hashed_base58 +from pycoin.encoding.bytes32 import from_bytes_32 +from pycoin.encoding.exceptions import EncodingError +from pycoin.encoding.sec import public_pair_to_sec from ripemd import ripemd160 as RIPEMD160 # nosec B413 from counterpartycore.lib import config, exceptions, ledger, opcodes, util @@ -404,14 +408,6 @@ def scriptpubkey_to_address(scriptpubkey): return None -# TODO: Use `python-bitcointools` instead. (Get rid of `pycoin` dependency.) -from pycoin.ecdsa.secp256k1 import secp256k1_generator as generator_secp256k1 # noqa: E402 -from pycoin.encoding.b58 import a2b_hashed_base58 # noqa: E402 -from pycoin.encoding.bytes32 import from_bytes_32 # noqa: E402 -from pycoin.encoding.exceptions import EncodingError # noqa: E402 -from pycoin.encoding.sec import public_pair_to_sec # noqa: E402 - - def wif_to_tuple_of_prefix_secret_exponent_compressed(wif): """ Return a tuple of (prefix, secret_exponent, is_compressed). diff --git a/counterparty-core/counterpartycore/lib/transaction.py b/counterparty-core/counterpartycore/lib/transaction.py index 13819c7106..5cc4923c8c 100644 --- a/counterparty-core/counterpartycore/lib/transaction.py +++ b/counterparty-core/counterpartycore/lib/transaction.py @@ -7,30 +7,25 @@ import binascii import decimal import hashlib +import inspect import io -import json # noqa: F401 import logging -import math # noqa: F401 -import os # noqa: F401 -import re # noqa: F401 -import sys # noqa: F401 +import sys import threading -import time # noqa: F401 import bitcoin as bitcoinlib import cachetools -import requests # noqa: F401 -from bitcoin.core import CTransaction, b2lx, x # noqa: F401 -from bitcoin.core.script import CScript # noqa: F401 +from bitcoin.core import CTransaction from counterpartycore.lib import ( arc4, # noqa: F401 backend, - blocks, # noqa: F401 config, exceptions, gettxinfo, ledger, + message_type, + messages, script, util, ) @@ -959,3 +954,826 @@ def get_dust_return_pubkey(source, provided_pubkeys, encoding): raise script.InputError("Invalid private key.") # noqa: B904 return dust_return_pubkey + + +COMPOSE_COMMONS_ARGS = { + "encoding": (str, "auto", "The encoding method to use"), + "fee_per_kb": ( + int, + None, + "The fee per kilobyte of transaction data constant that the server uses when deciding on the dynamic fee to use (in satoshi)", + ), + "regular_dust_size": ( + int, + config.DEFAULT_REGULAR_DUST_SIZE, + "Specify (in satoshi) to override the (dust) amount of BTC used for each non-(bare) multisig output.", + ), + "multisig_dust_size": ( + int, + config.DEFAULT_MULTISIG_DUST_SIZE, + "Specify (in satoshi) to override the (dust) amount of BTC used for each (bare) multisig output", + ), + "op_return_value": ( + int, + config.DEFAULT_OP_RETURN_VALUE, + "The value (in satoshis) to use with any OP_RETURN outputs in the generated transaction. Defaults to 0. Don't use this, unless you like throwing your money away", + ), + "pubkey": ( + str, + None, + "The hexadecimal public key of the source address (or a list of the keys, if multi-sig). Required when using encoding parameter values of multisig or pubkeyhash.", + ), + "allow_unconfirmed_inputs": ( + bool, + False, + "Set to true to allow this transaction to utilize unconfirmed UTXOs as inputs", + ), + "fee": ( + int, + None, + "If you'd like to specify a custom miners' fee, specify it here (in satoshi). Leave as default for the server to automatically choose", + ), + "fee_provided": ( + int, + 0, + "If you would like to specify a maximum fee (up to and including which may be used as the transaction fee), specify it here (in satoshi). This differs from fee in that this is an upper bound value, which fee is an exact value", + ), + "unspent_tx_hash": ( + str, + None, + "When compiling the UTXOs to use as inputs for the transaction being created, only consider unspent outputs from this specific transaction hash. Defaults to null to consider all UTXOs for the address. Do not use this parameter if you are specifying custom_inputs", + ), + "dust_return_pubkey": ( + str, + None, + "The dust return pubkey is used in multi-sig data outputs (as the only real pubkey) to make those the outputs spendable. By default, this pubkey is taken from the pubkey used in the first transaction input. However, it can be overridden here (and is required to be specified if a P2SH input is used and multisig is used as the data output encoding.) If specified, specify the public key (in hex format) where dust will be returned to so that it can be reclaimed. Only valid/useful when used with transactions that utilize multisig data encoding. Note that if this value is set to false, this instructs counterparty-server to use the default dust return pubkey configured at the node level. If this default is not set at the node level, the call will generate an exception", + ), + "disable_utxo_locks": ( + bool, + False, + "By default, UTXO's utilized when creating a transaction are 'locked' for a few seconds, to prevent a case where rapidly generating create_ calls reuse UTXOs due to their spent status not being updated in bitcoind yet. Specify true for this parameter to disable this behavior, and not temporarily lock UTXOs", + ), + "extended_tx_info": ( + bool, + False, + "When this is not specified or false, the create_ calls return only a hex-encoded string. If this is true, the create_ calls return a data object with the following keys: tx_hex, btc_in, btc_out, btc_change, and btc_fee", + ), + "p2sh_pretx_txid": ( + str, + None, + "The previous transaction txid for a two part P2SH message. This txid must be taken from the signed transaction", + ), + "old_style_api": (bool, True, "Use the old style API"), + "segwit": (bool, False, "Use segwit"), +} + + +def split_compose_params(**kwargs): + transaction_args = {} + common_args = {} + private_key_wif = None + for key, value in kwargs.items(): + if key in COMPOSE_COMMONS_ARGS: + common_args[key] = value + elif key == "privkey": + private_key_wif = value + else: + transaction_args[key] = value + return transaction_args, common_args, private_key_wif + + +def get_default_args(func): + signature = inspect.signature(func) + return { + k: v.default + for k, v in signature.parameters.items() + if v.default is not inspect.Parameter.empty + } + + +def compose_transaction( + db, + name, + params, + encoding="auto", + fee_per_kb=None, + estimate_fee_per_kb=None, + regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE, + multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE, + op_return_value=config.DEFAULT_OP_RETURN_VALUE, + pubkey=None, + allow_unconfirmed_inputs=False, + fee=None, + fee_provided=0, + unspent_tx_hash=None, + custom_inputs=None, + dust_return_pubkey=None, + disable_utxo_locks=False, + extended_tx_info=False, + p2sh_source_multisig_pubkeys=None, + p2sh_source_multisig_pubkeys_required=None, + p2sh_pretx_txid=None, + old_style_api=True, + segwit=False, + api_v1=False, +): + """Create and return a transaction.""" + + # Get provided pubkeys. + if isinstance(pubkey, str): + provided_pubkeys = [pubkey] + elif isinstance(pubkey, list): + provided_pubkeys = pubkey + elif pubkey is None: + provided_pubkeys = [] + else: + raise exceptions.TransactionError("Invalid pubkey.") + + # Get additional pubkeys from `source` and `destination` params. + # Convert `source` and `destination` to pubkeyhash form. + for address_name in ["source", "destination"]: + if address_name in params: + address = params[address_name] + if isinstance(address, list): + # pkhshs = [] + # for addr in address: + # provided_pubkeys += script.extract_pubkeys(addr) + # pkhshs.append(script.make_pubkeyhash(addr)) + # params[address_name] = pkhshs + pass + else: + provided_pubkeys += script.extract_pubkeys(address) + params[address_name] = script.make_pubkeyhash(address) + + # Check validity of collected pubkeys. + for pubkey in provided_pubkeys: + if not script.is_fully_valid(binascii.unhexlify(pubkey)): + raise script.AddressError(f"invalid public key: {pubkey}") + + compose_method = sys.modules[f"counterpartycore.lib.messages.{name}"].compose + compose_params = inspect.getfullargspec(compose_method)[0] + missing_params = [p for p in compose_params if p not in params and p != "db"] + if api_v1: + for param in missing_params: + params[param] = None + else: + if len(missing_params) > 0: + default_values = get_default_args(compose_method) + for param in missing_params: + if param in default_values: + params[param] = default_values[param] + else: + raise exceptions.ComposeError( + f"missing parameters: {', '.join(missing_params)}" + ) + + # dont override fee_per_kb if specified + if fee_per_kb is not None: + estimate_fee_per_kb = False + else: + fee_per_kb = config.DEFAULT_FEE_PER_KB + + if "extended_tx_info" in params: + extended_tx_info = params["extended_tx_info"] + del params["extended_tx_info"] + + if "old_style_api" in params: + old_style_api = params["old_style_api"] + del params["old_style_api"] + + if "segwit" in params: + segwit = params["segwit"] + del params["segwit"] + + tx_info = compose_method(db, **params) + + return construct( + db, + tx_info, + encoding=encoding, + fee_per_kb=fee_per_kb, + estimate_fee_per_kb=estimate_fee_per_kb, + regular_dust_size=regular_dust_size, + multisig_dust_size=multisig_dust_size, + op_return_value=op_return_value, + provided_pubkeys=provided_pubkeys, + allow_unconfirmed_inputs=allow_unconfirmed_inputs, + exact_fee=fee, + fee_provided=fee_provided, + unspent_tx_hash=unspent_tx_hash, + custom_inputs=custom_inputs, + dust_return_pubkey=dust_return_pubkey, + disable_utxo_locks=disable_utxo_locks, + extended_tx_info=extended_tx_info, + p2sh_source_multisig_pubkeys=p2sh_source_multisig_pubkeys, + p2sh_source_multisig_pubkeys_required=p2sh_source_multisig_pubkeys_required, + p2sh_pretx_txid=p2sh_pretx_txid, + old_style_api=old_style_api, + segwit=segwit, + ) + + +COMPOSABLE_TRANSACTIONS = [ + "bet", + "broadcast", + "btcpay", + "burn", + "cancel", + "destroy", + "dispenser", + "dividend", + "issuance", + "mpma", + "order", + "send", + "sweep", +] + + +def compose(db, source, transaction_name, api_v1=False, **kwargs): + if transaction_name not in COMPOSABLE_TRANSACTIONS: + raise exceptions.TransactionError("Transaction type not composable.") + transaction_args, common_args, _ = split_compose_params(**kwargs) + transaction_args["source"] = source + return compose_transaction( + db, name=transaction_name, params=transaction_args, api_v1=api_v1, **common_args + ) + + +def compose_bet( + db, + address: str, + feed_address: str, + bet_type: int, + deadline: int, + wager_quantity: int, + counterwager_quantity: int, + expiration: int, + leverage: int = 5040, + target_value: int = None, + **construct_args, +): + """ + Composes a transaction to issue a bet against a feed. + :param address: The address that will make the bet (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param feed_address: The address that hosts the feed to be bet on (e.g. 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev) + :param bet_type: Bet 0 for Bullish CFD (deprecated), 1 for Bearish CFD (deprecated), 2 for Equal, 3 for NotEqual (e.g. 2) + :param deadline: The time at which the bet should be decided/settled, in Unix time (seconds since epoch) (e.g. 3000000000) + :param wager_quantity: The quantities of XCP to wager (in satoshis, hence integer) (e.g. 1000) + :param counterwager_quantity: The minimum quantities of XCP to be wagered against, for the bets to match (e.g. 1000) + :param expiration: The number of blocks after which the bet expires if it remains unmatched (e.g. 100) + :param leverage: Leverage, as a fraction of 5040 + :param target_value: Target value for Equal/NotEqual bet (e.g. 1000) + """ + params = { + "source": address, + "feed_address": feed_address, + "bet_type": bet_type, + "deadline": deadline, + "wager_quantity": wager_quantity, + "counterwager_quantity": counterwager_quantity, + "target_value": target_value, + "leverage": leverage, + "expiration": expiration, + } + rawtransaction = compose_transaction( + db, + name="bet", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "bet", + } + + +def compose_broadcast( + db, address: str, timestamp: int, value: float, fee_fraction: float, text: str, **construct_args +): + """ + Composes a transaction to broadcast textual and numerical information to the network. + :param address: The address that will be sending (must have the necessary quantity of the specified asset) (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param timestamp: The timestamp of the broadcast, in Unix time (e.g. 4003903983) + :param value: Numerical value of the broadcast (e.g. 100) + :param fee_fraction: How much of every bet on this feed should go to its operator; a fraction of 1, (i.e. 0.05 is five percent) (e.g. 0.05) + :param text: The textual part of the broadcast (e.g. "Hello, world!") + """ + params = { + "source": address, + "timestamp": timestamp, + "value": value, + "fee_fraction": fee_fraction, + "text": text, + } + rawtransaction = compose_transaction( + db, + name="broadcast", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "broadcast", + } + + +def compose_btcpay(db, address: str, order_match_id: str, **construct_args): + """ + Composes a transaction to pay for a BTC order match. + :param address: The address that will be sending the payment (e.g. bc1qsteve3tfxfg9pcmvzw645sr9zy7es5rx645p6l) + :param order_match_id: The ID of the order match to pay for (e.g. e470416a9500fb046835192da013f48e6468a07dba1bede4a0b68e666ed23c8d_4953bde3d9417b103615c2d3d4b284d4fcf7cbd820e5dd19ac0084e9ebd090b2) + """ + params = {"source": address, "order_match_id": order_match_id} + rawtransaction = compose_transaction( + db, + name="btcpay", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "btcpay", + } + + +def compose_burn(db, address: str, quantity: int, overburn: bool = False, **construct_args): + """ + Composes a transaction to burn a given quantity of BTC for XCP (on mainnet, possible between blocks 278310 and 283810; on testnet it is still available). + :param address: The address with the BTC to burn (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param quantity: The quantities of BTC to burn (1 BTC maximum burn per address) (e.g. 1000) + :param overburn: Whether to allow the burn to exceed 1 BTC for the address + """ + params = {"source": address, "quantity": quantity, "overburn": overburn} + rawtransaction = compose_transaction( + db, + name="burn", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "burn", + } + + +def compose_cancel(db, address: str, offer_hash: str, **construct_args): + """ + Composes a transaction to cancel an open order or bet. + :param address: The address that placed the order/bet to be cancelled (e.g. 15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA) + :param offer_hash: The hash of the order/bet to be cancelled (e.g. 8ce3335391bf71f8f12c0573b4f85b9adc4882a9955d9f8e5ababfdd0060279a) + """ + params = {"source": address, "offer_hash": offer_hash} + rawtransaction = compose_transaction( + db, + name="cancel", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "cancel", + } + + +def compose_destroy(db, address: str, asset: str, quantity: int, tag: str, **construct_args): + """ + Composes a transaction to destroy a quantity of an asset. + :param address: The address that will be sending the asset to be destroyed (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param asset: The asset to be destroyed (e.g. XCP) + :param quantity: The quantity of the asset to be destroyed (e.g. 1000) + :param tag: A tag for the destruction (e.g. "bugs!") + """ + params = {"source": address, "asset": asset, "quantity": quantity, "tag": tag} + rawtransaction = compose_transaction( + db, + name="destroy", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "destroy", + } + + +def compose_dispenser( + db, + address: str, + asset: str, + give_quantity: int, + escrow_quantity: int, + mainchainrate: int, + status: int, + open_address: str = None, + oracle_address: str = None, + **construct_args, +): + """ + Opens or closes a dispenser for a given asset at a given rate of main chain asset (BTC). Escrowed quantity on open must be equal or greater than give_quantity. It is suggested that you escrow multiples of give_quantity to ease dispenser operation. + :param address: The address that will be dispensing (must have the necessary escrow_quantity of the specified asset) (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param asset: The asset or subasset to dispense (e.g. XCP) + :param give_quantity: The quantity of the asset to dispense (e.g. 1000) + :param escrow_quantity: The quantity of the asset to reserve for this dispenser (e.g. 1000) + :param mainchainrate: The quantity of the main chain asset (BTC) per dispensed portion (e.g. 100) + :param status: The state of the dispenser. 0 for open, 1 for open using open_address, 10 for closed (e.g. 0) + :param open_address: The address that you would like to open the dispenser on + :param oracle_address: The address that you would like to use as a price oracle for this dispenser + """ + params = { + "source": address, + "asset": asset, + "give_quantity": give_quantity, + "escrow_quantity": escrow_quantity, + "mainchainrate": mainchainrate, + "status": status, + "open_address": open_address, + "oracle_address": oracle_address, + } + rawtransaction = compose_transaction( + db, + name="dispenser", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "dispenser", + } + + +def compose_dividend( + db, address: str, quantity_per_unit: int, asset: str, dividend_asset: str, **construct_args +): + """ + Composes a transaction to issue a dividend to holders of a given asset. + :param address: The address that will be issuing the dividend (must have the ownership of the asset which the dividend is being issued on) (e.g. 1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD) + :param quantity_per_unit: The amount of dividend_asset rewarded (e.g. 1) + :param asset: The asset or subasset that the dividends are being rewarded on (e.g. PEPECASH) + :param dividend_asset: The asset or subasset that the dividends are paid in (e.g. XCP) + """ + params = { + "source": address, + "quantity_per_unit": quantity_per_unit, + "asset": asset, + "dividend_asset": dividend_asset, + } + rawtransaction = compose_transaction( + db, + name="dividend", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "dividend", + } + + +def compose_issuance( + db, + address: str, + asset: str, + quantity: int, + transfer_destination: str = None, + divisible: bool = True, + lock: bool = False, + reset: bool = False, + description: str = None, + **construct_args, +): + """ + Composes a transaction to Issue a new asset, issue more of an existing asset, lock an asset, reset existing supply, or transfer the ownership of an asset. + :param address: The address that will be issuing or transfering the asset (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param asset: The assets to issue or transfer. This can also be a subasset longname for new subasset issuances (e.g. XCPTEST) + :param quantity: The quantity of the asset to issue (set to 0 if transferring an asset) (e.g. 1000) + :param transfer_destination: The address to receive the asset (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param divisible: Whether this asset is divisible or not (if a transfer, this value must match the value specified when the asset was originally issued) + :param lock: Whether this issuance should lock supply of this asset forever + :param reset: Wether this issuance should reset any existing supply + :param description: A textual description for the asset + """ + params = { + "source": address, + "asset": asset, + "quantity": quantity, + "transfer_destination": transfer_destination, + "divisible": divisible, + "lock": lock, + "reset": reset, + "description": description, + } + rawtransaction = compose_transaction( + db, + name="issuance", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "issuance", + } + + +def compose_mpma( + db, + address: str, + assets: str, + destinations: str, + quantities: str, + memo: str, + memo_is_hex: bool, + **construct_args, +): + """ + Composes a transaction to send multiple payments to multiple addresses. + :param address: The address that will be sending (must have the necessary quantity of the specified asset) (e.g. 1Fv87qmdtjQDP9d4p9E5ncBQvYB4a3Rhy6) + :param assets: comma-separated list of assets to send (e.g. BAABAABLKSHP,BADHAIRDAY,BADWOJAK) + :param destinations: comma-separated list of addresses to send to (e.g. 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev,1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD,1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs) + :param quantities: comma-separated list of quantities to send (e.g. 1,2,3) + :param memo: The Memo associated with this transaction (e.g. "Hello, world!") + :param memo_is_hex: Whether the memo field is a hexadecimal string (e.g. False) + """ + asset_list = assets.split(",") + destination_list = destinations.split(",") + quantity_list = quantities.split(",") + if len(asset_list) != len(destination_list) or len(asset_list) != len(quantity_list): + raise exceptions.ComposeError( + "The number of assets, destinations, and quantities must be equal" + ) + for quantity in quantity_list: + if not quantity.isdigit(): + raise exceptions.ComposeError("Quantity must be an integer") + quantity_list = [int(quantity) for quantity in quantity_list] + asset_dest_quant_list = list(zip(asset_list, destination_list, quantity_list)) + + params = { + "source": address, + "asset_dest_quant_list": asset_dest_quant_list, + "memo": memo, + "memo_is_hex": memo_is_hex, + } + + rawtransaction = compose_transaction( + db, + name="versions.mpma", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "mpma", + } + + +def compose_order( + db, + address: str, + give_asset: str, + give_quantity: int, + get_asset: str, + get_quantity: int, + expiration: int, + fee_required: int, + **construct_args, +): + """ + Composes a transaction to place an order on the distributed exchange. + :param address: The address that will be issuing the order request (must have the necessary quantity of the specified asset to give) (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param give_asset: The asset that will be given in the trade (e.g. XCP) + :param give_quantity: The quantity of the asset that will be given (e.g. 1000) + :param get_asset: The asset that will be received in the trade (e.g. PEPECASH) + :param get_quantity: The quantity of the asset that will be received (e.g. 1000) + :param expiration: The number of blocks for which the order should be valid (e.g. 100) + :param fee_required: The miners’ fee required to be paid by orders for them to match this one; in BTC; required only if buying BTC (may be zero, though) (e.g. 100) + """ + params = { + "source": address, + "give_asset": give_asset, + "give_quantity": give_quantity, + "get_asset": get_asset, + "get_quantity": get_quantity, + "expiration": expiration, + "fee_required": fee_required, + } + rawtransaction = compose_transaction( + db, + name="order", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "order", + } + + +def compose_send( + db, + address: str, + destination: str, + asset: str, + quantity: int, + memo: str = None, + memo_is_hex: bool = False, + use_enhanced_send: bool = True, + **construct_args, +): + """ + Composes a transaction to send a quantity of an asset to another address. + :param address: The address that will be sending (must have the necessary quantity of the specified asset) (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param destination: The address that will be receiving the asset (e.g. 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev) + :param asset: The asset or subasset to send (e.g. XCP) + :param quantity: The quantity of the asset to send (e.g. 1000) + :param memo: The Memo associated with this transaction + :param memo_is_hex: Whether the memo field is a hexadecimal string + :param use_enhanced_send: If this is false, the construct a legacy transaction sending bitcoin dust + """ + params = { + "source": address, + "destination": destination, + "asset": asset, + "quantity": quantity, + "memo": memo, + "memo_is_hex": memo_is_hex, + "use_enhanced_send": use_enhanced_send, + } + rawtransaction = compose_transaction( + db, + name="send", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "send", + } + + +def compose_sweep(db, address: str, destination: str, flags: int, memo: str, **construct_args): + """ + Composes a transaction to Sends all assets and/or transfer ownerships to a destination address. + :param address: The address that will be sending (e.g. 1CounterpartyXXXXXXXXXXXXXXXUWLpVr) + :param destination: The address to receive the assets and/or ownerships (e.g. 1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev) + :param flags: An OR mask of flags indicating how the sweep should be processed. Possible flags are: + - FLAG_BALANCES: (integer) 1, specifies that all balances should be transferred. + - FLAG_OWNERSHIP: (integer) 2, specifies that all ownerships should be transferred. + - FLAG_BINARY_MEMO: (integer) 4, specifies that the memo is in binary/hex form. + (e.g. 7) + :param memo: The Memo associated with this transaction in hex format (e.g. FFFF) + """ + params = { + "source": address, + "destination": destination, + "flags": flags, + "memo": memo, + } + rawtransaction = compose_transaction( + db, + name="sweep", + params=params, + **construct_args, + ) + return { + "rawtransaction": rawtransaction, + "params": params, + "name": "sweep", + } + + +def get_transaction_by_hash(db, tx_hash: str): + """ + Returns a transaction by its hash. + :param tx_hash: The hash of the transaction (e.g. 876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5) + """ + tx = ledger.get_transaction(db, tx_hash) + if tx and tx["data"]: + tx["unpacked_data"] = unpack(db, binascii.hexlify(tx["data"]), tx["block_index"]) + return tx + + +def info(db, rawtransaction: str, block_index: int = None): + """ + Returns Counterparty information from a raw transaction in hex format. + :param rawtransaction: Raw transaction in hex format (e.g. 01000000017828697743c03aef6a3a8ba54b22bf579ffcab8161faf20e7b20c4ecd75cc986010000006b483045022100d1bd0531bb1ed2dd2cbf77d6933273e792a3dbfa84327d419169850ddd5976f502205d1ab0f7bcbf1a0cc183f0520c9aa8f711d41cb790c0c4ac39da6da4a093d798012103d3b1f711e907acb556e239f6cafb6a4f7fe40d8dd809b0e06e739c2afd73f202ffffffff0200000000000000004d6a4bf29880b93b0711524c7ef9c76835752088db8bd4113a3daf41fc45ffdc8867ebdbf26817fae377696f36790e52f51005806e9399a427172fedf348cf798ed86e548002ee96909eef0775ec3c2b0100000000001976a91443434cf159cc585fbd74daa9c4b833235b19761b88ac00000000) + :param block_index: Block index mandatory for transactions before block 335000 + """ + source, destination, btc_amount, fee, data, extra = gettxinfo.get_tx_info( + db, BlockchainParser().deserialize_tx(rawtransaction), block_index=block_index + ) + result = { + "source": source, + "destination": destination, + "btc_amount": btc_amount, + "fee": fee, + "data": util.hexlify(data) if data else "", + } + if data: + result["data"] = util.hexlify(data) + result["unpacked_data"] = unpack(db, result["data"], block_index) + return result + + +def unpack(db, datahex: str, block_index: int = None): + """ + Unpacks Counterparty data in hex format and returns the message type and data. + :param datahex: Data in hex format (e.g. 16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245) + :param block_index: Block index of the transaction containing this data + """ + data = binascii.unhexlify(datahex) + message_type_id, message = message_type.unpack(data) + block_index = block_index or ledger.CURRENT_BLOCK_INDEX + + issuance_ids = [ + messages.issuance.ID, + messages.issuance.LR_ISSUANCE_ID, + messages.issuance.SUBASSET_ID, + messages.issuance.LR_SUBASSET_ID, + ] + + # Unknown message type + message_data = {"error": "Unknown message type"} + message_type_name = "unknown" + try: + # Bet + if message_type_id == messages.bet.ID: + message_type_name = "bet" + message_data = messages.bet.unpack(message, return_dict=True) + # Broadcast + elif message_type_id == messages.broadcast.ID: + message_type_name = "broadcast" + message_data = messages.broadcast.unpack(message, block_index, return_dict=True) + # BTCPay + elif message_type_id == messages.btcpay.ID: + message_type_name = "btcpay" + message_data = messages.btcpay.unpack(message, return_dict=True) + # Cancel + elif message_type_id == messages.cancel.ID: + message_type_name = "cancel" + message_data = messages.cancel.unpack(message, return_dict=True) + # Destroy + elif message_type_id == messages.destroy.ID: + message_type_name = "destroy" + message_data = messages.destroy.unpack(db, message, return_dict=True) + # Dispenser + elif message_type_id == messages.dispenser.ID: + message_type_name = "dispenser" + message_data = messages.dispenser.unpack(message, return_dict=True) + # Dividend + elif message_type_id == messages.dividend.ID: + message_type_name = "dividend" + message_data = messages.dividend.unpack(db, message, block_index, return_dict=True) + # Issuance + elif message_type_id in issuance_ids: + message_type_name = "issuance" + message_data = messages.issuance.unpack( + db, message, message_type_id, block_index, return_dict=True + ) + # Order + elif message_type_id == messages.order.ID: + message_type_name = "order" + message_data = messages.order.unpack(db, message, block_index, return_dict=True) + # Send + elif message_type_id == messages.send.ID: + message_type_name = "send" + message_data = messages.send.unpack(db, message, block_index) + # Enhanced send + elif message_type_id == messages.versions.enhanced_send.ID: + message_type_name = "enhanced_send" + message_data = messages.versions.enhanced_send.unpack(message, block_index) + # MPMA send + elif message_type_id == messages.versions.mpma.ID: + message_type_name = "mpma_send" + message_data = messages.versions.mpma.unpack(message, block_index) + # RPS + elif message_type_id == messages.rps.ID: + message_type_name = "rps" + message_data = messages.rps.unpack(message, return_dict=True) + # RPS Resolve + elif message_type_id == messages.rpsresolve.ID: + message_type_name = "rpsresolve" + message_data = messages.rpsresolve.unpack(message, return_dict=True) + # Sweep + elif message_type_id == messages.sweep.ID: + message_type_name = "sweep" + message_data = messages.sweep.unpack(message) + except exceptions.UnpackError as e: + message_data = {"error": str(e)} + + return { + "message_type": message_type_name, + "message_type_id": message_type_id, + "message_data": message_data, + } diff --git a/counterparty-core/counterpartycore/lib/util.py b/counterparty-core/counterpartycore/lib/util.py index 30c65e9681..61c39f417f 100644 --- a/counterparty-core/counterpartycore/lib/util.py +++ b/counterparty-core/counterpartycore/lib/util.py @@ -1,12 +1,10 @@ import binascii import collections import decimal -import fractions # noqa: F401 import hashlib import itertools import json import logging -import os # noqa: F401 import random import re import sys diff --git a/counterparty-core/counterpartycore/server.py b/counterparty-core/counterpartycore/server.py index ae5e7e7c3c..89270296f0 100755 --- a/counterparty-core/counterpartycore/server.py +++ b/counterparty-core/counterpartycore/server.py @@ -19,17 +19,19 @@ from termcolor import colored, cprint from counterpartycore.lib import ( - api, backend, blocks, check, config, database, ledger, + log, transaction, util, ) from counterpartycore.lib import kickstart as kickstarter +from counterpartycore.lib.api import api_server as api_v2 +from counterpartycore.lib.api import api_v1, routes # noqa: F401 from counterpartycore.lib.telemetry.client import TelemetryClientLocal from counterpartycore.lib.telemetry.collector import TelemetryCollectorLive from counterpartycore.lib.telemetry.daemon import TelemetryDaemon @@ -155,6 +157,11 @@ def initialise_config( rpc_user=None, rpc_password=None, rpc_no_allow_cors=False, + api_host=None, + api_port=None, + api_user=None, + api_password=None, + api_no_allow_cors=False, force=False, requests_timeout=config.DEFAULT_REQUESTS_TIMEOUT, rpc_batch_size=config.DEFAULT_RPC_BATCH_SIZE, @@ -366,7 +373,7 @@ def initialise_config( config.RPC_HOST = "localhost" # The web root directory for API calls, eg. localhost:14000/rpc/ - config.RPC_WEBROOT = "/rpc/" + config.RPC_WEBROOT = "/v1/rpc/" # Server API RPC port if rpc_port: @@ -419,6 +426,56 @@ def initialise_config( config.RPC_BATCH_SIZE = rpc_batch_size + # Server API RPC host + if api_host: + config.API_HOST = api_host + else: + config.API_HOST = "localhost" + + # Server API port + if api_port: + config.API_PORT = api_port + else: + if config.TESTNET: + if config.TESTCOIN: + config.API_PORT = config.DEFAULT_API_PORT_TESTNET + 1 + else: + config.API_PORT = config.DEFAULT_API_PORT_TESTNET + elif config.REGTEST: + if config.TESTCOIN: + config.API_PORT = config.DEFAULT_API_PORT_REGTEST + 1 + else: + config.API_PORT = config.DEFAULT_API_PORT_REGTEST + else: + if config.TESTCOIN: + config.API_PORT = config.DEFAULT_API_PORT + 1 + else: + config.API_PORT = config.DEFAULT_API_PORT + try: + config.API_PORT = int(config.API_PORT) + if not (int(config.API_PORT) > 1 and int(config.API_PORT) < 65535): + raise ConfigurationError("invalid server API port number") + except: # noqa: E722 + raise ConfigurationError( # noqa: B904 + "Please specific a valid port number rpc-port configuration parameter" + ) + + # Server API user + if api_user: + config.API_USER = api_user + else: + config.API_USER = "api" + + if api_password: + config.API_PASSWORD = api_password + else: + config.API_PASSWORD = "api" # noqa: S105 + + if api_no_allow_cors: + config.API_NO_ALLOW_CORS = api_no_allow_cors + else: + config.API_NO_ALLOW_CORS = False + ############## # OTHER SETTINGS @@ -518,6 +575,66 @@ def initialise_config( logger.info(f"Running v{config.VERSION_STRING} of counterparty-core.") +def initialise_log_and_config(args): + # Configuration + init_args = { + "database_file": args.database_file, + "testnet": args.testnet, + "testcoin": args.testcoin, + "regtest": args.regtest, + "customnet": args.customnet, + "api_limit_rows": args.api_limit_rows, + "backend_connect": args.backend_connect, + "backend_port": args.backend_port, + "backend_user": args.backend_user, + "backend_password": args.backend_password, + "backend_ssl": args.backend_ssl, + "backend_ssl_no_verify": args.backend_ssl_no_verify, + "backend_poll_interval": args.backend_poll_interval, + "indexd_connect": args.indexd_connect, + "indexd_port": args.indexd_port, + "rpc_host": args.rpc_host, + "rpc_port": args.rpc_port, + "rpc_user": args.rpc_user, + "rpc_password": args.rpc_password, + "rpc_no_allow_cors": args.rpc_no_allow_cors, + "api_host": args.api_host, + "api_port": args.api_port, + "api_user": args.api_user, + "api_password": args.api_password, + "api_no_allow_cors": args.api_no_allow_cors, + "requests_timeout": args.requests_timeout, + "rpc_batch_size": args.rpc_batch_size, + "check_asset_conservation": args.check_asset_conservation, + "force": args.force, + "p2sh_dust_return_pubkey": args.p2sh_dust_return_pubkey, + "utxo_locks_max_addresses": args.utxo_locks_max_addresses, + "utxo_locks_max_age": args.utxo_locks_max_age, + "no_mempool": args.no_mempool, + } + + initialise_log_config( + verbose=args.verbose, + quiet=args.quiet, + log_file=args.log_file, + api_log_file=args.api_log_file, + no_log_files=args.no_log_files, + testnet=args.testnet, + testcoin=args.testcoin, + regtest=args.regtest, + json_log=args.json_log, + ) + + # set up logging + log.set_up( + verbose=config.VERBOSE, + quiet=config.QUIET, + log_file=config.LOG, + log_in_console=args.action == "start", + ) + initialise_config(**init_args) + + def initialise_db(): if config.FORCE: cprint("THE OPTION `--force` IS NOT FOR USE ON PRODUCTION SYSTEMS.", "yellow") @@ -559,14 +676,16 @@ def connect_to_addrindexrs(): print(f"{OK_GREEN} {step}") -def start_all(catch_up="normal"): - api_status_poller, api_server, db = None, None, None +def start_all(args): + api_status_poller = None + api_server_v1 = None + api_server_v2 = None - try: - # Backend. - connect_to_backend() + # Backend. + connect_to_backend() - if not os.path.exists(config.DATABASE) and catch_up == "bootstrap": + try: + if not os.path.exists(config.DATABASE) and args.catch_up == "bootstrap": bootstrap(no_confirm=True) db = initialise_db() @@ -584,15 +703,20 @@ def start_all(catch_up="normal"): # initilise_config transaction.initialise() - # API Status Poller. - api_status_poller = api.APIStatusPoller() - api_status_poller.daemon = True - api_status_poller.start() + if args.enable_api_v1: + # API Status Poller. + api_status_poller = api_v1.APIStatusPoller() + api_status_poller.daemon = True + api_status_poller.start() + + # API Server v1. + api_server_v1 = api_v1.APIServer() + api_server_v1.daemon = True + api_server_v1.start() - # API Server. - api_server = api.APIServer() - api_server.daemon = True - api_server.start() + # API Server v2. + api_server_v2 = api_v2.APIServer() + api_server_v2.start(args) # Server blocks.follow(db) @@ -600,10 +724,13 @@ def start_all(catch_up="normal"): pass finally: telemetry_daemon.stop() - if api_status_poller: - api_status_poller.stop() - if api_server: - api_server.stop() + if args.enable_api_v1: + if api_status_poller: + api_status_poller.stop() + if api_server_v1: + api_server_v1.stop() + if api_server_v2: + api_server_v2.stop() backend.stop() if db: database.optimize(db) diff --git a/counterparty-core/counterpartycore/test/api_v2_test.py b/counterparty-core/counterpartycore/test/api_v2_test.py new file mode 100644 index 0000000000..e4fd79a843 --- /dev/null +++ b/counterparty-core/counterpartycore/test/api_v2_test.py @@ -0,0 +1,309 @@ +import json +import tempfile + +import pytest +import requests + +from counterpartycore.lib import util +from counterpartycore.lib.api import routes + +# this is require near the top to do setup of the test suite +from counterpartycore.test import ( + conftest, # noqa: F401 +) +from counterpartycore.test.fixtures.params import ADDR +from counterpartycore.test.util_test import CURR_DIR + +FIXTURE_SQL_FILE = CURR_DIR + "/fixtures/scenarios/unittest_fixture.sql" +FIXTURE_DB = tempfile.gettempdir() + "/fixtures.unittest_fixture.db" +API_V2_FIXTURES = CURR_DIR + "/fixtures/api_v2_fixtures.json" +API_ROOT = "http://api:api@localhost:10009" + + +@pytest.mark.usefixtures("api_server_v2") +def test_api_v2(request): + block_index = 310491 + address = ADDR[0] + asset = "NODIVISIBLE" + tx_hash = "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498" + order_hash = "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498" + bet_hash = "e566ab052d414d2c9b9d6ffc643bc5d2b31d80976dffe7acceaf2576246f9e42" + dispenser_hash = "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498" + event = "CREDIT" + event_index = 10 + exclude_routes = ["compose", "unpack", "info", "mempool", "healthz", "backend"] + results = {} + fixtures = {} + with open(API_V2_FIXTURES, "r") as f: + fixtures = json.load(f) + + for route in routes.ROUTES: + if any([exclude in route for exclude in exclude_routes]): + continue + + url = f"{API_ROOT}{route}" + url = url.replace("", str(block_index)) + url = url.replace("
", address) + url = url.replace("", asset) + url = url.replace("", event) + url = url.replace("", str(event_index)) + url = url.replace("", order_hash) + url = url.replace("", bet_hash) + url = url.replace("", dispenser_hash) + url = url.replace("", tx_hash) + if route.startswith("/events"): + url += "?limit=5" + print(url) + result = requests.get(url) # noqa: S113 + results[url] = result.json() + assert result.status_code == 200 + if not request.config.getoption("saveapifixtures"): + assert results[url] == fixtures[url] + + if request.config.getoption("saveapifixtures"): + with open(API_V2_FIXTURES, "w") as f: + f.write(json.dumps(results, indent=4)) + + +@pytest.mark.usefixtures("api_server_v2") +def test_api_v2_unpack(request, server_db): + with open(CURR_DIR + "/fixtures/api_v2_unpack_fixtures.json", "r") as f: + datas = json.load(f) + url = f"{API_ROOT}/transactions/unpack" + + for data in datas: + result = requests.get(url, params={"datahex": data["datahex"]}) # noqa: S113 + assert result.status_code == 200 + assert result.json()["result"] == data["result"] + + +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_balances_by_address(): + alice = ADDR[0] + url = f"{API_ROOT}/addresses/{alice}/balances" + result = requests.get(url) # noqa: S113 + assert result.json()["result"] == [ + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "A95428956661682277", + "quantity": 100000000, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "CALLABLE", + "quantity": 1000, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "DIVISIBLE", + "quantity": 98800000000, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "LOCKED", + "quantity": 1000, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "MAXI", + "quantity": 9223372036854775807, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 985, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "PARENT", + "quantity": 100000000, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 91875000000, + }, + ] + + +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_balances_by_asset(): + asset = "XCP" + url = f"{API_ROOT}/assets/{asset}/balances" + result = requests.get(url) # noqa: S113 + assert result.json()["result"] == [ + { + "address": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "XCP", + "quantity": 300000000, + }, + { + "address": "2MyJHMUenMWonC35Yi6PHC7i2tkS7PuomCy", + "asset": "XCP", + "quantity": 46449548498, + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 91875000000, + }, + { + "address": "mqPCfvqTfYctXMUfmniXeG2nyaN8w6tPmj", + "asset": "XCP", + "quantity": 92945878046, + }, + { + "address": "mrPk7hTeZWjjSCrMTC2ET4SAUThQt7C4uK", + "asset": "XCP", + "quantity": 14999857, + }, + { + "address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "XCP", + "quantity": 99999990, + }, + { + "address": "munimLLHjPhGeSU5rYB2HN79LJa8bRZr5b", + "asset": "XCP", + "quantity": 92999130360, + }, + { + "address": "mwtPsLQxW9xpm7gdLmwWvJK5ABdPUVJm42", + "asset": "XCP", + "quantity": 92949122099, + }, + { + "address": "myAtcJEHAsDLbTkai6ipWDZeeL7VkxXsiM", + "asset": "XCP", + "quantity": 92999138812, + }, + { + "address": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", + "asset": "XCP", + "quantity": 92999030129, + }, + ] + + +@pytest.mark.usefixtures("api_server") +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_balances_vs_old(): + asset = "XCP" + url = f"{API_ROOT}/assets/{asset}/balances" + new_balances = requests.get(url).json()["result"] # noqa: S113 + old_balance = util.api( + "get_balances", + { + "filters": [ + {"field": "asset", "op": "==", "value": asset}, + {"field": "quantity", "op": "!=", "value": 0}, + ], + }, + ) + new_balances = sorted(new_balances, key=lambda x: (x["address"], x["asset"], x["quantity"])) + old_balance = sorted(old_balance, key=lambda x: (x["address"], x["asset"], x["quantity"])) + assert len(new_balances) == len(old_balance) + for new_balance, old_balance in zip(new_balances, old_balance): # noqa: B020 + assert new_balance["address"] == old_balance["address"] + assert new_balance["asset"] == old_balance["asset"] + assert new_balance["quantity"] == old_balance["quantity"] + + +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_asset_info(): + asset = "NODIVISIBLE" + url = f"{API_ROOT}/assets/{asset}" + result = requests.get(url) # noqa: S113 + + assert result.json()["result"] == { + "asset": "NODIVISIBLE", + "asset_longname": None, + "description": "No divisible asset", + "divisible": False, + "issuer": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "locked": False, + "owner": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "supply": 1000, + "holder_count": 3, + } + + +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_asset_orders(): + asset = "XCP" + url = f"{API_ROOT}/assets/{asset}/orders" + result = requests.get(url).json()["result"] # noqa: S113 + assert len(result) == 6 + assert result[0] == { + "tx_index": 11, + "tx_hash": "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a", + "block_index": 310010, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "give_asset": "XCP", + "give_quantity": 100000000, + "give_remaining": 100000000, + "get_asset": "BTC", + "get_quantity": 1000000, + "get_remaining": 1000000, + "expiration": 2000, + "expire_index": 312010, + "fee_required": 900000, + "fee_required_remaining": 900000, + "fee_provided": 6800, + "fee_provided_remaining": 6800, + "status": "open", + } + + +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_order_info(): + tx_hash = "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a" + url = f"{API_ROOT}/orders/{tx_hash}" + result = requests.get(url).json()["result"] # noqa: S113 + assert result[0] == { + "tx_index": 11, + "tx_hash": "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a", + "block_index": 310010, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "give_asset": "XCP", + "give_quantity": 100000000, + "give_remaining": 100000000, + "get_asset": "BTC", + "get_quantity": 1000000, + "get_remaining": 1000000, + "expiration": 2000, + "expire_index": 312010, + "fee_required": 900000, + "fee_required_remaining": 900000, + "fee_provided": 6800, + "fee_provided_remaining": 6800, + "status": "open", + } + + +@pytest.mark.usefixtures("api_server_v2") +def test_new_get_order_matches(): + tx_hash = "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498" + url = f"{API_ROOT}/orders/{tx_hash}/matches" + result = requests.get(url).json()["result"] # noqa: S113 + assert result[0] == { + "id": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498_1b294dd8592e76899b1c106782e4c96e63114abd8e3fa09ab6d2d52496b5bf81", + "tx0_index": 492, + "tx0_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx0_address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "tx1_index": 493, + "tx1_hash": "1b294dd8592e76899b1c106782e4c96e63114abd8e3fa09ab6d2d52496b5bf81", + "tx1_address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "forward_asset": "XCP", + "forward_quantity": 100000000, + "backward_asset": "BTC", + "backward_quantity": 800000, + "tx0_block_index": 310491, + "tx1_block_index": 310492, + "block_index": 310492, + "tx0_expiration": 2000, + "tx1_expiration": 2000, + "match_expire_index": 310512, + "fee_paid": 7200, + "status": "pending", + } diff --git a/counterparty-core/counterpartycore/test/bytespersigop_test.py b/counterparty-core/counterpartycore/test/bytespersigop_test.py index 25e336538c..ee246d9471 100644 --- a/counterparty-core/counterpartycore/test/bytespersigop_test.py +++ b/counterparty-core/counterpartycore/test/bytespersigop_test.py @@ -1,9 +1,7 @@ import binascii -import pprint # noqa: F401 import tempfile import bitcoin as bitcoinlib -import pytest # noqa: F401 from counterpartycore.lib import api, blocks, exceptions, ledger, transaction, util # noqa: F401 from counterpartycore.test import ( @@ -26,7 +24,7 @@ def test_bytespersigop(server_db): transaction.initialise() # ADDR[0], bytespersigop=False, desc 41 bytes, opreturn - txhex = api.compose_transaction( + txhex = transaction.compose_transaction( server_db, "issuance", { @@ -46,7 +44,7 @@ def test_bytespersigop(server_db): assert "OP_RETURN" in repr(tx.vout[0].scriptPubKey) # ADDR[0], bytespersigop=False, desc 42 bytes, multisig - txhex = api.compose_transaction( + txhex = transaction.compose_transaction( server_db, "issuance", { @@ -71,7 +69,7 @@ def test_bytespersigop(server_db): assert ledger.enabled("bytespersigop") == True # noqa: E712 # ADDR[0], bytespersigop=True, desc 41 bytes, opreturn - txhex = api.compose_transaction( + txhex = transaction.compose_transaction( server_db, "issuance", { @@ -91,7 +89,7 @@ def test_bytespersigop(server_db): assert "OP_RETURN" in repr(tx.vout[0].scriptPubKey) # ADDR[1], bytespersigop=True, desc 41 bytes, opreturn encoding - txhex = api.compose_transaction( + txhex = transaction.compose_transaction( server_db, "issuance", { @@ -112,7 +110,7 @@ def test_bytespersigop(server_db): # ADDR[1], bytespersigop=True, desc 20 bytes, FORCED encoding=multisig # will use 2 UTXOs to make the bytes:sigop ratio in our favor - txhex = api.compose_transaction( + txhex = transaction.compose_transaction( server_db, "issuance", { diff --git a/counterparty-core/counterpartycore/test/complex_unit_test.py b/counterparty-core/counterpartycore/test/complex_unit_test.py index 62087416a3..7373d7ce3f 100644 --- a/counterparty-core/counterpartycore/test/complex_unit_test.py +++ b/counterparty-core/counterpartycore/test/complex_unit_test.py @@ -1,12 +1,11 @@ import json -import pprint # noqa: F401 import tempfile import pytest -import requests from apsw import ConstraintError -from counterpartycore.lib import api, blocks, config, ledger, util +from counterpartycore.lib import blocks, ledger, util +from counterpartycore.lib.api import api_v1 # this is require near the top to do setup of the test suite from counterpartycore.test import ( @@ -218,7 +217,7 @@ def test_update_lock(server_db): @pytest.mark.usefixtures("api_server") def test_updated_tables_endpoints(): - for table in api.API_TABLES: + for table in api_v1.API_TABLES: if table in ["mempool"]: continue result = util.api("get_" + table, {}) @@ -331,237 +330,6 @@ def test_updated_tables_endpoints(): } -@pytest.mark.usefixtures("api_server") -def test_new_get_balances_by_address(): - alice = ADDR[0] - url = f"{config.API_ROOT}/addresses/{alice}/balances" - result = requests.get(url) # noqa: S113 - assert result.json() == [ - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "A95428956661682277", - "quantity": 100000000, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "CALLABLE", - "quantity": 1000, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "DIVISIBLE", - "quantity": 98800000000, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "LOCKED", - "quantity": 1000, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "MAXI", - "quantity": 9223372036854775807, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "NODIVISIBLE", - "quantity": 985, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "PARENT", - "quantity": 100000000, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "XCP", - "quantity": 91875000000, - }, - ] - - -@pytest.mark.usefixtures("api_server") -def test_new_get_balances_by_asset(): - asset = "XCP" - url = f"{config.API_ROOT}/assets/{asset}/balances" - result = requests.get(url) # noqa: S113 - assert result.json() == [ - { - "address": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", - "asset": "XCP", - "quantity": 300000000, - }, - { - "address": "2MyJHMUenMWonC35Yi6PHC7i2tkS7PuomCy", - "asset": "XCP", - "quantity": 46449548498, - }, - { - "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "asset": "XCP", - "quantity": 91875000000, - }, - { - "address": "mqPCfvqTfYctXMUfmniXeG2nyaN8w6tPmj", - "asset": "XCP", - "quantity": 92945878046, - }, - { - "address": "mrPk7hTeZWjjSCrMTC2ET4SAUThQt7C4uK", - "asset": "XCP", - "quantity": 14999857, - }, - { - "address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", - "asset": "XCP", - "quantity": 99999990, - }, - { - "address": "munimLLHjPhGeSU5rYB2HN79LJa8bRZr5b", - "asset": "XCP", - "quantity": 92999130360, - }, - { - "address": "mwtPsLQxW9xpm7gdLmwWvJK5ABdPUVJm42", - "asset": "XCP", - "quantity": 92949122099, - }, - { - "address": "myAtcJEHAsDLbTkai6ipWDZeeL7VkxXsiM", - "asset": "XCP", - "quantity": 92999138812, - }, - { - "address": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", - "asset": "XCP", - "quantity": 92999030129, - }, - ] - - -@pytest.mark.usefixtures("api_server") -def test_new_get_balances_vs_old(): - asset = "XCP" - url = f"{config.API_ROOT}/assets/{asset}/balances" - new_balances = requests.get(url).json() # noqa: S113 - old_balance = util.api( - "get_balances", - { - "filters": [ - {"field": "asset", "op": "==", "value": asset}, - {"field": "quantity", "op": "!=", "value": 0}, - ], - }, - ) - new_balances = sorted(new_balances, key=lambda x: (x["address"], x["asset"], x["quantity"])) - old_balance = sorted(old_balance, key=lambda x: (x["address"], x["asset"], x["quantity"])) - assert len(new_balances) == len(old_balance) - for new_balance, old_balance in zip(new_balances, old_balance): # noqa: B020 - assert new_balance["address"] == old_balance["address"] - assert new_balance["asset"] == old_balance["asset"] - assert new_balance["quantity"] == old_balance["quantity"] - - -@pytest.mark.usefixtures("api_server") -def test_new_get_asset_info(): - asset = "NODIVISIBLE" - url = f"{config.API_ROOT}/assets/{asset}" - result = requests.get(url) # noqa: S113 - assert result.json() == [ - { - "asset": "NODIVISIBLE", - "asset_longname": None, - "description": "No divisible asset", - "divisible": False, - "issuer": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "locked": False, - "owner": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "supply": 1000, - } - ] - - -@pytest.mark.usefixtures("api_server") -def test_new_get_asset_orders(): - asset = "XCP" - url = f"{config.API_ROOT}/assets/{asset}/orders" - result = requests.get(url).json() # noqa: S113 - assert len(result) == 6 - assert result[0] == { - "tx_index": 11, - "tx_hash": "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a", - "block_index": 310010, - "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "give_asset": "XCP", - "give_quantity": 100000000, - "give_remaining": 100000000, - "get_asset": "BTC", - "get_quantity": 1000000, - "get_remaining": 1000000, - "expiration": 2000, - "expire_index": 312010, - "fee_required": 900000, - "fee_required_remaining": 900000, - "fee_provided": 6800, - "fee_provided_remaining": 6800, - "status": "open", - } - - -@pytest.mark.usefixtures("api_server") -def test_new_get_order_info(): - tx_hash = "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a" - url = f"{config.API_ROOT}/orders/{tx_hash}" - result = requests.get(url).json() # noqa: S113 - assert result[0] == { - "tx_index": 11, - "tx_hash": "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a", - "block_index": 310010, - "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "give_asset": "XCP", - "give_quantity": 100000000, - "give_remaining": 100000000, - "get_asset": "BTC", - "get_quantity": 1000000, - "get_remaining": 1000000, - "expiration": 2000, - "expire_index": 312010, - "fee_required": 900000, - "fee_required_remaining": 900000, - "fee_provided": 6800, - "fee_provided_remaining": 6800, - "status": "open", - } - - -@pytest.mark.usefixtures("api_server") -def test_new_get_order_matches(): - tx_hash = "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498" - url = f"{config.API_ROOT}/orders/{tx_hash}/matches" - result = requests.get(url).json() # noqa: S113 - assert result[0] == { - "id": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498_1b294dd8592e76899b1c106782e4c96e63114abd8e3fa09ab6d2d52496b5bf81", - "tx0_index": 492, - "tx0_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", - "tx0_address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", - "tx1_index": 493, - "tx1_hash": "1b294dd8592e76899b1c106782e4c96e63114abd8e3fa09ab6d2d52496b5bf81", - "tx1_address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", - "forward_asset": "XCP", - "forward_quantity": 100000000, - "backward_asset": "BTC", - "backward_quantity": 800000, - "tx0_block_index": 310491, - "tx1_block_index": 310492, - "block_index": 310492, - "tx0_expiration": 2000, - "tx1_expiration": 2000, - "match_expire_index": 310512, - "fee_paid": 7200, - "status": "pending", - } - - @pytest.mark.usefixtures("server_db") def test_messages_table(server_db): cursor = server_db.cursor() diff --git a/counterparty-core/counterpartycore/test/conftest.py b/counterparty-core/counterpartycore/test/conftest.py index 1073c4e90c..990a028180 100644 --- a/counterparty-core/counterpartycore/test/conftest.py +++ b/counterparty-core/counterpartycore/test/conftest.py @@ -2,11 +2,10 @@ Test suite configuration """ +import argparse import binascii import json import logging -import os # noqa: F401 -import pprint # noqa: F401 import time from datetime import datetime @@ -18,7 +17,9 @@ from pycoin.coins.bitcoin import Tx # noqa: F401 from counterpartycore import server -from counterpartycore.lib import api, arc4, config, database, ledger, log, script, util +from counterpartycore.lib import arc4, config, database, ledger, log, script, util +from counterpartycore.lib.api import api_server as api_v2 +from counterpartycore.lib.api import api_v1 as api from counterpartycore.test import util_test from counterpartycore.test.fixtures.params import DEFAULT_PARAMS from counterpartycore.test.fixtures.scenarios import INTEGRATION_SCENARIOS @@ -178,6 +179,12 @@ def pytest_addoption(parser): parser.addoption( "--testbook", action="store_true", default=False, help="Include testnet test book" ) + parser.addoption( + "--saveapifixtures", + action="store_true", + default=False, + help="Generate api v2 fixtures for tests", + ) @pytest.fixture(scope="function") @@ -234,12 +241,78 @@ def api_server(request, cp_server): return api_server +@pytest.fixture(scope="module") +def api_server_v2(request, cp_server): + default_config = { + "testnet": False, + "testcoin": False, + "regtest": False, + "api_limit_rows": 1000, + "backend_connect": None, + "backend_port": None, + "backend_user": None, + "backend_password": None, + "indexd_connect": None, + "indexd_port": None, + "backend_ssl": False, + "backend_ssl_no_verify": False, + "backend_poll_interval": None, + "rpc_host": None, + "rpc_user": None, + "rpc_password": None, + "rpc_no_allow_cors": False, + "api_host": "localhost", + "api_user": "api", + "api_password": "api", + "api_no_allow_cors": False, + "force": False, + "requests_timeout": config.DEFAULT_REQUESTS_TIMEOUT, + "rpc_batch_size": config.DEFAULT_RPC_BATCH_SIZE, + "check_asset_conservation": False, + "backend_ssl_verify": None, + "rpc_allow_cors": None, + "p2sh_dust_return_pubkey": None, + "utxo_locks_max_addresses": config.DEFAULT_UTXO_LOCKS_MAX_ADDRESSES, + "utxo_locks_max_age": config.DEFAULT_UTXO_LOCKS_MAX_AGE, + "estimate_fee_per_kb": None, + "customnet": None, + "verbose": False, + "quiet": False, + "log_file": None, + "api_log_file": None, + "no_log_files": False, + "json_log": False, + "no_check_asset_conservation": True, + "action": "", + "no_refresh_backend_height": True, + "no_mempool": False, + "skip_db_check": False, + } + server_config = ( + default_config + | util_test.COUNTERPARTYD_OPTIONS + | { + "database_file": request.module.FIXTURE_DB, + "api_port": TEST_RPC_PORT + 10, + } + ) + args = argparse.Namespace(**server_config) + api_server = api_v2.APIServer() + api_server.start(args) + time.sleep(1) + + request.addfinalizer(lambda: api_server.stop()) + + return api_server + + @pytest.fixture(scope="module") def cp_server(request): dbfile = request.module.FIXTURE_DB sqlfile = request.module.FIXTURE_SQL_FILE options = getattr(request.module, "FIXTURE_OPTIONS", {}) + print(f"cp_server: {dbfile} {sqlfile} {options}") db = util_test.init_database(sqlfile, dbfile, options) # noqa: F841 # monkeypatch this here because init_mock_functions can run before cp_server @@ -454,7 +527,7 @@ def init_arc4(seed): monkeypatch.setattr("counterpartycore.lib.log.isodt", isodt) monkeypatch.setattr("counterpartycore.lib.ledger.curr_time", curr_time) monkeypatch.setattr("counterpartycore.lib.util.date_passed", date_passed) - monkeypatch.setattr("counterpartycore.lib.api.init_api_access_log", init_api_access_log) + monkeypatch.setattr("counterpartycore.lib.api.util.init_api_access_log", init_api_access_log) if hasattr(config, "PREFIX"): monkeypatch.setattr("counterpartycore.lib.config.PREFIX", b"TESTXXXX") monkeypatch.setattr("counterpartycore.lib.backend.getrawtransaction", mocked_getrawtransaction) diff --git a/counterparty-core/counterpartycore/test/estimate_fee_per_kb_test.py b/counterparty-core/counterpartycore/test/estimate_fee_per_kb_test.py index 5a05f50b7d..89e8582c4d 100644 --- a/counterparty-core/counterpartycore/test/estimate_fee_per_kb_test.py +++ b/counterparty-core/counterpartycore/test/estimate_fee_per_kb_test.py @@ -4,7 +4,7 @@ import bitcoin as bitcoinlib -from counterpartycore.lib import api, backend, transaction +from counterpartycore.lib import backend, transaction from counterpartycore.test import ( util_test, ) @@ -41,7 +41,7 @@ def _fee_per_kb(conf_target, mode): with util_test.ConfigContext(ESTIMATE_FEE_PER_KB=True): transaction.initialise() - txhex = api.compose_transaction( + txhex = transaction.compose_transaction( server_db, "send", {"source": ADDR[0], "destination": ADDR[1], "asset": "XCP", "quantity": 100}, diff --git a/counterparty-core/counterpartycore/test/fixtures/api_v2_fixtures.json b/counterparty-core/counterpartycore/test/fixtures/api_v2_fixtures.json new file mode 100644 index 0000000000..ebbd0f3106 --- /dev/null +++ b/counterparty-core/counterpartycore/test/fixtures/api_v2_fixtures.json @@ -0,0 +1,1523 @@ +{ + "http://api:api@localhost:10009/blocks": { + "result": [ + { + "block_index": 310500, + "block_hash": "54aeaf47d5387964e2d51617bf3af50520a0449410e0d096cf8c2aa9dad5550b", + "block_time": 310500000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "5ffefc7a2724be6bd697796bb82638ec913c5cbb73627153d1a13b48c7a6c02d", + "txlist_hash": "35f4a33840d002ab4e0e44f11c1749ae95b41376927fb346140508b32518edd1", + "messages_hash": "45f296a535c13129cb1aaeb4e28a03e04ad902917891c39ae59ea2894e9f868f" + }, + { + "block_index": 310499, + "block_hash": "1950e1a4d7fc820ed9603f6df6819c3c953c277c726340dec2a4253e261a1764", + "block_time": 310499000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "b9fcbdafddd46fdda061f6e9f8744b426b6ca37e32b315df1098cbc7899ae9b9", + "txlist_hash": "032166892f568bb97f4f69ef5bdf49cc1b15cc9f8c7f6c1f3e1f9d54816ad7e5", + "messages_hash": "d6aedacd4f81520d86ae47c9c776d17a213c706a5cf7c91203a4299261d1648c" + }, + { + "block_index": 310498, + "block_hash": "b7058b6d1ddc325a10bf33144937e06ce6025215b416518ae120da9440ae279e", + "block_time": 310498000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "5fe6cdb0828379bf240fad99c68bba34e1889bbc19605ce5c297b82352264414", + "txlist_hash": "b488f6f0e6c233f202ee17c0843236d464144e79c870af88bae56355ae9372b7", + "messages_hash": "113207bd13dda56b5e5edf305f70a56e62cc861184e1e95a64e79ce100462c98" + }, + { + "block_index": 310497, + "block_hash": "f1118591fe79b8bf52ccf0c5de9826bfd266b1fdc24b44676cf22bbcc76d464e", + "block_time": 310497000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "28c6e92b2299b9cbbb5953f8b7ff3de0fe962d15642ba27e43faa64e1935e819", + "txlist_hash": "ff8136601b9e0138a999d1f0467af6e8535a2bcdd2b622af7be0178a083b9519", + "messages_hash": "ac8d8759fbddc8f92ad8b5d8b4637a63b8d21a04ba1a4126baf2b42a87edfce3" + }, + { + "block_index": 310496, + "block_hash": "65884816927e8c566655e85c07bc2bc2c7ee26e625742f219939d43238fb31f8", + "block_time": 310496000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "7ac6121c624b634f44695172761830926afe76bb18c4cc9195773f3a26966941", + "txlist_hash": "9eda85cce745579122ba9c6e24b63cd83f2e5161031a34e6ee9bf08b80823cb4", + "messages_hash": "1b1ed76f99d39b36f4d0737299ce15b21fed9e077d0476658c023b09819853a7" + }, + { + "block_index": 310495, + "block_hash": "4769aa7030f28a05a137a85ef4ee0c1765c37013773212b93ec90f1227168b67", + "block_time": 310495000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "5a7e5a36882466373d576bb5f4ccd1bc72ecaf548b9589baa803a7275a7a24cd", + "txlist_hash": "09e9db121649cacd979fd18bbaa35e519361e727e7e072e2f2f86291160cdb29", + "messages_hash": "56518bc9abc11bf108188834058bb9c51c9b8dc3b6276116c5b786b07c797fab" + }, + { + "block_index": 310494, + "block_hash": "7dda1d3e12785313d5651ee5314d0aecf17588196f9150b10c55695dbaebee5d", + "block_time": 310494000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "72d71bd72263699ea9f2b097ad141be5bc394f49d8b0b0a6b2ff6a87b0ee3919", + "txlist_hash": "9350c3ba33d0546d1194c5fa767ced28834b26246aedc56d89b1d48ec4f26014", + "messages_hash": "0d3c87692d49dc033eed975eb8b8ee17ff2f0f877c8ed5a5866f73ce63bf8715" + }, + { + "block_index": 310493, + "block_hash": "c19e2915b750279b2be4b52e57e5ce29f63dffb4e14d9aad30c9e820affc0cbf", + "block_time": 310493000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "29119cd30a4733916fbfd0551506eaa16f7bb1bdfbdf8d17ac4e5bb20d1cb09c", + "txlist_hash": "7ec4cfa94544900c8e8732ad51be7cee6452aa1884ea940cd5c98862fb4aaba6", + "messages_hash": "53a339feb73df3a98bc4acc84af77e111d4780533c8f5a26cf250594d9613cf2" + }, + { + "block_index": 310492, + "block_hash": "8a09b2faf0a7ad67eb4ab5c948b9769fc87eb2ec5e16108f2cde8bd9e6cf7607", + "block_time": 310492000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "98af18583618fdeed545347c013763d068e8294405d265911cc5e1bc420bc740", + "txlist_hash": "daf4d2c1a1ad5206abcf7744bdd06fae99c442fb2607a843dcabb5727d02916e", + "messages_hash": "0d34fbc26126add5a57b7e8f6f71bad150d7232abf046f3fa9b1fc72b10d61b2" + }, + { + "block_index": 310491, + "block_hash": "811abd7cf2b768cfdaa84ab44c63f4463c96a368ead52125bf149cf0c7447b16", + "block_time": 310491000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "3114d8091cfcaa9944c6fab49d51950535c4ef269877d58c372ed80b2b472ec6", + "txlist_hash": "f065728a3544adc085fae976759c0d040a34ca0a8ddd39260b55f0262cd5baa8", + "messages_hash": "9671cfedb3124b67ed996c547cb26a32e95490009ad56065c79be54a28c45994" + } + ] + }, + "http://api:api@localhost:10009/blocks/310491": { + "result": { + "block_index": 310491, + "block_hash": "811abd7cf2b768cfdaa84ab44c63f4463c96a368ead52125bf149cf0c7447b16", + "block_time": 310491000, + "previous_block_hash": null, + "difficulty": null, + "ledger_hash": "3114d8091cfcaa9944c6fab49d51950535c4ef269877d58c372ed80b2b472ec6", + "txlist_hash": "f065728a3544adc085fae976759c0d040a34ca0a8ddd39260b55f0262cd5baa8", + "messages_hash": "9671cfedb3124b67ed996c547cb26a32e95490009ad56065c79be54a28c45994" + } + }, + "http://api:api@localhost:10009/blocks/310491/transactions": { + "result": [ + { + "tx_index": 492, + "tx_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "block_index": 310491, + "block_hash": "811abd7cf2b768cfdaa84ab44c63f4463c96a368ead52125bf149cf0c7447b16", + "block_time": 310491000, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "", + "btc_amount": 0, + "fee": 6800, + "data": "0000000a00000000000000010000000005f5e100000000000000000000000000000c350007d000000000000dbba0", + "supported": 1 + } + ] + }, + "http://api:api@localhost:10009/blocks/310491/events": { + "result": [ + { + "event_index": 1183, + "event": "NEW_BLOCK", + "bindings": { + "block_hash": "8a09b2faf0a7ad67eb4ab5c948b9769fc87eb2ec5e16108f2cde8bd9e6cf7607", + "block_index": 310492, + "block_time": 310492000, + "difficulty": null, + "ledger_hash": null, + "previous_block_hash": null, + "txlist_hash": null + }, + "block_index": 310491, + "timestamp": 0 + }, + { + "event_index": 1182, + "event": "BLOCK_PARSED", + "bindings": { + "block_index": 310491, + "ledger_hash": "3114d8091cfcaa9944c6fab49d51950535c4ef269877d58c372ed80b2b472ec6", + "messages_hash": "9671cfedb3124b67ed996c547cb26a32e95490009ad56065c79be54a28c45994", + "txlist_hash": "f065728a3544adc085fae976759c0d040a34ca0a8ddd39260b55f0262cd5baa8" + }, + "block_index": 310491, + "timestamp": 0 + }, + { + "event_index": 1181, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx_index": 492 + }, + "block_index": 310491, + "timestamp": 0 + }, + { + "event_index": 1180, + "event": "OPEN_ORDER", + "bindings": { + "block_index": 310491, + "expiration": 2000, + "expire_index": 312491, + "fee_provided": 6800, + "fee_provided_remaining": 6800, + "fee_required": 900000, + "fee_required_remaining": 900000, + "get_asset": "BTC", + "get_quantity": 800000, + "get_remaining": 800000, + "give_asset": "XCP", + "give_quantity": 100000000, + "give_remaining": 100000000, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "status": "open", + "tx_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx_index": 492 + }, + "block_index": 310491, + "timestamp": 0 + }, + { + "event_index": 1179, + "event": "DEBIT", + "bindings": { + "action": "open order", + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "block_index": 310491, + "event": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "quantity": 100000000, + "tx_index": 492 + }, + "block_index": 310491, + "timestamp": 0 + }, + { + "event_index": 1178, + "event": "NEW_TRANSACTION", + "bindings": { + "block_hash": "811abd7cf2b768cfdaa84ab44c63f4463c96a368ead52125bf149cf0c7447b16", + "block_index": 310491, + "block_time": 310491000, + "btc_amount": 0, + "data": "0000000a00000000000000010000000005f5e100000000000000000000000000000c350007d000000000000dbba0", + "destination": "", + "fee": 6800, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "supported": true, + "tx_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx_index": 492 + }, + "block_index": 310491, + "timestamp": 0 + } + ] + }, + "http://api:api@localhost:10009/blocks/310491/events/counts": { + "result": [ + { + "event": "BLOCK_PARSED", + "event_count": 1 + }, + { + "event": "DEBIT", + "event_count": 1 + }, + { + "event": "NEW_BLOCK", + "event_count": 1 + }, + { + "event": "NEW_TRANSACTION", + "event_count": 1 + }, + { + "event": "OPEN_ORDER", + "event_count": 1 + }, + { + "event": "TRANSACTION_PARSED", + "event_count": 1 + } + ] + }, + "http://api:api@localhost:10009/blocks/310491/events/CREDIT": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/credits": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/debits": { + "result": [ + { + "block_index": 310491, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "open order", + "event": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx_index": 492 + } + ] + }, + "http://api:api@localhost:10009/blocks/310491/expirations": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/cancels": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/destructions": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/issuances": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/sends": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/dispenses": { + "result": [] + }, + "http://api:api@localhost:10009/blocks/310491/sweeps": { + "result": [] + }, + "http://api:api@localhost:10009/transactions/74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498": { + "result": { + "tx_index": 492, + "tx_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "block_index": 310491, + "block_hash": "811abd7cf2b768cfdaa84ab44c63f4463c96a368ead52125bf149cf0c7447b16", + "block_time": 310491000, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "", + "btc_amount": 0, + "fee": 6800, + "data": "0000000a00000000000000010000000005f5e100000000000000000000000000000c350007d000000000000dbba0", + "supported": 1, + "unpacked_data": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "XCP", + "give_quantity": 100000000, + "get_asset": "BTC", + "get_quantity": 800000, + "expiration": 2000, + "fee_required": 900000, + "status": "open" + } + } + } + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/balances": { + "result": [ + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "A95428956661682277", + "quantity": 100000000 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "CALLABLE", + "quantity": 1000 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "DIVISIBLE", + "quantity": 98800000000 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "LOCKED", + "quantity": 1000 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "MAXI", + "quantity": 9223372036854775807 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 985 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "PARENT", + "quantity": 100000000 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 91875000000 + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/balances/NODIVISIBLE": { + "result": { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 985 + } + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/credits": { + "result": [ + { + "block_index": 310000, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 93000000000, + "calling_function": "burn", + "event": "6dc5b0a33d4d4297e0f5cc2d23ae307951d32aab2d86b7fa147b385219f3a597", + "tx_index": 1 + }, + { + "block_index": 310001, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "DIVISIBLE", + "quantity": 100000000000, + "calling_function": "issuance", + "event": "1fc2e5a57f584b2f2edd05676e75c33d03eed1d3098cc0550ea33474e3ec9db1", + "tx_index": 2 + }, + { + "block_index": 310002, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 1000, + "calling_function": "issuance", + "event": "7b1bf5144346279271b1ff78664f118224fe27fd8679d6c1519345f9c6c54584", + "tx_index": 3 + }, + { + "block_index": 310003, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "CALLABLE", + "quantity": 1000, + "calling_function": "issuance", + "event": "c26f3a0c4e57e41919ff27aae95a9a9d4d65d34c6da6f1893884a17c8d407140", + "tx_index": 4 + }, + { + "block_index": 310004, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "LOCKED", + "quantity": 1000, + "calling_function": "issuance", + "event": "90b5734be98b0f2a0bd4b6a269c8db3368e2e387bb890ade239951d05423b4da", + "tx_index": 5 + }, + { + "block_index": 310016, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "MAXI", + "quantity": 9223372036854775807, + "calling_function": "issuance", + "event": "bd4e9cbbe69c2db893cd32182a2d315c89c45ba4e31aa5775d1fe42d841cea39", + "tx_index": 17 + }, + { + "block_index": 310020, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 0, + "calling_function": "filled", + "event": "5c6562ddad0bc8a1faaded18813a65522cd273709acd190cf9d3271817eefc93", + "tx_index": 21 + }, + { + "block_index": 310102, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 9, + "calling_function": "bet settled", + "event": "16462eac6c795cea6e5985ee063867d8c61ae24373df02048186d28118d25bae", + "tx_index": 103 + }, + { + "block_index": 310102, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 0, + "calling_function": "feed fee", + "event": "16462eac6c795cea6e5985ee063867d8c61ae24373df02048186d28118d25bae", + "tx_index": 103 + }, + { + "block_index": 310482, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "calling_function": "send", + "event": "c8716524f33646b9af94d6f5e52494ff3b34466497094b1db2ab920e4f79bc34", + "tx_index": 483 + }, + { + "block_index": 310497, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "PARENT", + "quantity": 100000000, + "calling_function": "issuance", + "event": "076ae3d8eeb7fb40d2ae27692340157c746d9832806766b0dac5adb1526dc78f", + "tx_index": 498 + }, + { + "block_index": 310498, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "A95428956661682277", + "quantity": 100000000, + "calling_function": "issuance", + "event": "0abfce2662c05852fd8b181a60900678643cedad47b23a853b8c4eda82cb2cbf", + "tx_index": 499 + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/debits": { + "result": [ + { + "block_index": 310001, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "1fc2e5a57f584b2f2edd05676e75c33d03eed1d3098cc0550ea33474e3ec9db1", + "tx_index": 2 + }, + { + "block_index": 310002, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "7b1bf5144346279271b1ff78664f118224fe27fd8679d6c1519345f9c6c54584", + "tx_index": 3 + }, + { + "block_index": 310003, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "c26f3a0c4e57e41919ff27aae95a9a9d4d65d34c6da6f1893884a17c8d407140", + "tx_index": 4 + }, + { + "block_index": 310004, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "90b5734be98b0f2a0bd4b6a269c8db3368e2e387bb890ade239951d05423b4da", + "tx_index": 5 + }, + { + "block_index": 310005, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 0, + "action": "issuance fee", + "event": "344dcc8909ca3a137630726d0071dfd2df4f7c855bac150c7d3a8367835c90bc", + "tx_index": 6 + }, + { + "block_index": 310006, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "open order", + "event": "4f0433ba841038e2e16328445930dd7bca35309b14b0da4451c8f94c631368b8", + "tx_index": 7 + }, + { + "block_index": 310007, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "DIVISIBLE", + "quantity": 100000000, + "action": "send", + "event": "6e91ae23de2035e3e28c3322712212333592a1f666bcff9dd91aec45d5ea2753", + "tx_index": 8 + }, + { + "block_index": 310008, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "send", + "event": "4fd55abadfdbe77c3bda2431749cca934a29994a179620a62c1b57f28bd62a43", + "tx_index": 9 + }, + { + "block_index": 310009, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "open order", + "event": "21460d5c07284f9be9baf824927d0d4e4eb790e297f3162305841607b672349b", + "tx_index": 10 + }, + { + "block_index": 310010, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "open order", + "event": "1899b2e6ec36ba4bc9d035e6640b0a62b08c3a147c77c89183a77d9ed9081b3a", + "tx_index": 11 + }, + { + "block_index": 310012, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 300000000, + "action": "send", + "event": "698e97e507da8623cf38ab42701853443c8f7fe0d93b4674aabb42f9800ee9d6", + "tx_index": 13 + }, + { + "block_index": 310013, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "DIVISIBLE", + "quantity": 1000000000, + "action": "send", + "event": "0cfeeb559ed794d067557df0376a6c213b48b174b80cdb2c3c6d365cf538e132", + "tx_index": 14 + }, + { + "block_index": 310014, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 5, + "action": "send", + "event": "1facb0072f16f6bdca64ea859c82b850f58f0ec7ff410d901679772a4727515a", + "tx_index": 15 + }, + { + "block_index": 310015, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 10, + "action": "send", + "event": "e3b6667b7baa515048a7fcf2be7818e3e7622371236b78e19b4b08e2d7e7818c", + "tx_index": 16 + }, + { + "block_index": 310016, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "bd4e9cbbe69c2db893cd32182a2d315c89c45ba4e31aa5775d1fe42d841cea39", + "tx_index": 17 + }, + { + "block_index": 310019, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 9, + "action": "bet", + "event": "2a2169991597036b6dad687ea1feffd55465a204466f40c35cbba811cb3109b1", + "tx_index": 20 + }, + { + "block_index": 310110, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "DIVISIBLE", + "quantity": 100000000, + "action": "send", + "event": "f6a0f819e899b407cbfa07b4eff3d58902af3899abfbaa47d5f31d5b398e76e7", + "tx_index": 111 + }, + { + "block_index": 310481, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "send", + "event": "b00bdf03402d81a2cbdbeac4b0df90cff5ab6bf9688f653383d49fe42b8422a5", + "tx_index": 482 + }, + { + "block_index": 310491, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "action": "open order", + "event": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx_index": 492 + }, + { + "block_index": 310497, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "076ae3d8eeb7fb40d2ae27692340157c746d9832806766b0dac5adb1526dc78f", + "tx_index": 498 + }, + { + "block_index": 310498, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 25000000, + "action": "issuance fee", + "event": "0abfce2662c05852fd8b181a60900678643cedad47b23a853b8c4eda82cb2cbf", + "tx_index": 499 + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/bets": { + "result": [ + { + "tx_index": 102, + "tx_hash": "db4ea092bea6036e3d1e5f6ec863db9b900252b4f4d6d9faa6165323f433c51e", + "block_index": 310101, + "source": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "feed_address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "bet_type": 3, + "deadline": 1388000200, + "wager_quantity": 10, + "wager_remaining": 10, + "counterwager_quantity": 10, + "counterwager_remaining": 10, + "target_value": 0.0, + "leverage": 5040, + "expiration": 1000, + "expire_index": 311101, + "fee_fraction_int": 5000000, + "status": "open" + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/broadcasts": { + "result": [ + { + "tx_index": 103, + "tx_hash": "16462eac6c795cea6e5985ee063867d8c61ae24373df02048186d28118d25bae", + "block_index": 310102, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "timestamp": 1388000002, + "value": 1.0, + "fee_fraction_int": 5000000, + "text": "Unit Test", + "locked": 0, + "status": "valid" + }, + { + "tx_index": 18, + "tx_hash": "d14388b74b63d93e4477b1fe8426028bb8ab20656703e3ce8deeb23c2fe0b8af", + "block_index": 310017, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "timestamp": 1388000000, + "value": 1.0, + "fee_fraction_int": 5000000, + "text": "Unit Test", + "locked": 0, + "status": "valid" + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/burns": { + "result": [ + { + "tx_index": 1, + "tx_hash": "6dc5b0a33d4d4297e0f5cc2d23ae307951d32aab2d86b7fa147b385219f3a597", + "block_index": 310000, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "burned": 62000000, + "earned": 93000000000, + "status": "valid" + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/sends": { + "result": [ + { + "tx_index": 8, + "tx_hash": "6e91ae23de2035e3e28c3322712212333592a1f666bcff9dd91aec45d5ea2753", + "block_index": 310007, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "DIVISIBLE", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 9, + "tx_hash": "4fd55abadfdbe77c3bda2431749cca934a29994a179620a62c1b57f28bd62a43", + "block_index": 310008, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "XCP", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 13, + "tx_hash": "698e97e507da8623cf38ab42701853443c8f7fe0d93b4674aabb42f9800ee9d6", + "block_index": 310012, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "XCP", + "quantity": 300000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 14, + "tx_hash": "0cfeeb559ed794d067557df0376a6c213b48b174b80cdb2c3c6d365cf538e132", + "block_index": 310013, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "DIVISIBLE", + "quantity": 1000000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 15, + "tx_hash": "1facb0072f16f6bdca64ea859c82b850f58f0ec7ff410d901679772a4727515a", + "block_index": 310014, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "NODIVISIBLE", + "quantity": 5, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 16, + "tx_hash": "e3b6667b7baa515048a7fcf2be7818e3e7622371236b78e19b4b08e2d7e7818c", + "block_index": 310015, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "NODIVISIBLE", + "quantity": 10, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 111, + "tx_hash": "f6a0f819e899b407cbfa07b4eff3d58902af3899abfbaa47d5f31d5b398e76e7", + "block_index": 310110, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "2MyJHMUenMWonC35Yi6PHC7i2tkS7PuomCy", + "asset": "DIVISIBLE", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 482, + "tx_hash": "b00bdf03402d81a2cbdbeac4b0df90cff5ab6bf9688f653383d49fe42b8422a5", + "block_index": 310481, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "XCP", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": "68656c6c6f" + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/receives": { + "result": [ + { + "tx_index": 483, + "tx_hash": "c8716524f33646b9af94d6f5e52494ff3b34466497094b1db2ab920e4f79bc34", + "block_index": 310482, + "source": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "destination": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "XCP", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": "fade0001" + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/sends/NODIVISIBLE": { + "result": [ + { + "tx_index": 15, + "tx_hash": "1facb0072f16f6bdca64ea859c82b850f58f0ec7ff410d901679772a4727515a", + "block_index": 310014, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "NODIVISIBLE", + "quantity": 5, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 16, + "tx_hash": "e3b6667b7baa515048a7fcf2be7818e3e7622371236b78e19b4b08e2d7e7818c", + "block_index": 310015, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "NODIVISIBLE", + "quantity": 10, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/receives/NODIVISIBLE": { + "result": [] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/dispensers": { + "result": [] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/dispensers/NODIVISIBLE": { + "result": [] + }, + "http://api:api@localhost:10009/addresses/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc/sweeps": { + "result": [] + }, + "http://api:api@localhost:10009/assets": { + "result": [ + { + "asset": "A95428956661682277", + "asset_longname": "PARENT.already.issued" + }, + { + "asset": "CALLABLE", + "asset_longname": null + }, + { + "asset": "DIVIDEND", + "asset_longname": null + }, + { + "asset": "DIVISIBLE", + "asset_longname": null + }, + { + "asset": "LOCKED", + "asset_longname": null + }, + { + "asset": "LOCKEDPREV", + "asset_longname": null + }, + { + "asset": "MAXI", + "asset_longname": null + }, + { + "asset": "NODIVISIBLE", + "asset_longname": null + }, + { + "asset": "PARENT", + "asset_longname": null + }, + { + "asset": "PAYTOSCRIPT", + "asset_longname": null + } + ] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE": { + "result": { + "asset": "NODIVISIBLE", + "asset_longname": null, + "owner": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "divisible": false, + "locked": false, + "supply": 1000, + "description": "No divisible asset", + "issuer": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "holder_count": 3 + } + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/balances": { + "result": [ + { + "address": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "NODIVISIBLE", + "quantity": 10 + }, + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 985 + }, + { + "address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "NODIVISIBLE", + "quantity": 5 + } + ] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/balances/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc": { + "result": { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 985 + } + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/orders": { + "result": [] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/credits": { + "result": [ + { + "block_index": 310002, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 1000, + "calling_function": "issuance", + "event": "7b1bf5144346279271b1ff78664f118224fe27fd8679d6c1519345f9c6c54584", + "tx_index": 3 + }, + { + "block_index": 310014, + "address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "NODIVISIBLE", + "quantity": 5, + "calling_function": "send", + "event": "1facb0072f16f6bdca64ea859c82b850f58f0ec7ff410d901679772a4727515a", + "tx_index": 15 + }, + { + "block_index": 310015, + "address": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "NODIVISIBLE", + "quantity": 10, + "calling_function": "send", + "event": "e3b6667b7baa515048a7fcf2be7818e3e7622371236b78e19b4b08e2d7e7818c", + "tx_index": 16 + } + ] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/debits": { + "result": [ + { + "block_index": 310014, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 5, + "action": "send", + "event": "1facb0072f16f6bdca64ea859c82b850f58f0ec7ff410d901679772a4727515a", + "tx_index": 15 + }, + { + "block_index": 310015, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "NODIVISIBLE", + "quantity": 10, + "action": "send", + "event": "e3b6667b7baa515048a7fcf2be7818e3e7622371236b78e19b4b08e2d7e7818c", + "tx_index": 16 + } + ] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/dividends": { + "result": [] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/issuances": { + "result": [ + { + "tx_index": 3, + "tx_hash": "7b1bf5144346279271b1ff78664f118224fe27fd8679d6c1519345f9c6c54584", + "msg_index": 0, + "block_index": 310002, + "asset": "NODIVISIBLE", + "quantity": 1000, + "divisible": 0, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "issuer": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "transfer": 0, + "callable": 0, + "call_date": 0, + "call_price": 0.0, + "description": "No divisible asset", + "fee_paid": 50000000, + "locked": 0, + "status": "valid", + "asset_longname": null, + "reset": 0 + } + ] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/sends": { + "result": [ + { + "tx_index": 15, + "tx_hash": "1facb0072f16f6bdca64ea859c82b850f58f0ec7ff410d901679772a4727515a", + "block_index": 310014, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "asset": "NODIVISIBLE", + "quantity": 5, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 16, + "tx_hash": "e3b6667b7baa515048a7fcf2be7818e3e7622371236b78e19b4b08e2d7e7818c", + "block_index": 310015, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "destination": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "asset": "NODIVISIBLE", + "quantity": 10, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/dispensers": { + "result": [] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/dispensers/mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc": { + "result": [] + }, + "http://api:api@localhost:10009/assets/NODIVISIBLE/holders": { + "result": [ + { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "address_quantity": 985, + "escrow": null + }, + { + "address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "address_quantity": 5, + "escrow": null + }, + { + "address": "1_mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc_mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns_2", + "address_quantity": 10, + "escrow": null + } + ] + }, + "http://api:api@localhost:10009/orders/74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498": { + "result": [ + { + "tx_index": 492, + "tx_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "block_index": 310492, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "give_asset": "XCP", + "give_quantity": 100000000, + "give_remaining": 0, + "get_asset": "BTC", + "get_quantity": 800000, + "get_remaining": 0, + "expiration": 2000, + "expire_index": 312491, + "fee_required": 900000, + "fee_required_remaining": 892800, + "fee_provided": 6800, + "fee_provided_remaining": 6800, + "status": "open" + } + ] + }, + "http://api:api@localhost:10009/orders/74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498/matches": { + "result": [ + { + "id": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498_1b294dd8592e76899b1c106782e4c96e63114abd8e3fa09ab6d2d52496b5bf81", + "tx0_index": 492, + "tx0_hash": "74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498", + "tx0_address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "tx1_index": 493, + "tx1_hash": "1b294dd8592e76899b1c106782e4c96e63114abd8e3fa09ab6d2d52496b5bf81", + "tx1_address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "forward_asset": "XCP", + "forward_quantity": 100000000, + "backward_asset": "BTC", + "backward_quantity": 800000, + "tx0_block_index": 310491, + "tx1_block_index": 310492, + "block_index": 310492, + "tx0_expiration": 2000, + "tx1_expiration": 2000, + "match_expire_index": 310512, + "fee_paid": 7200, + "status": "pending" + } + ] + }, + "http://api:api@localhost:10009/orders/74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498/btcpays": { + "result": [] + }, + "http://api:api@localhost:10009/bets/e566ab052d414d2c9b9d6ffc643bc5d2b31d80976dffe7acceaf2576246f9e42": { + "result": [] + }, + "http://api:api@localhost:10009/bets/e566ab052d414d2c9b9d6ffc643bc5d2b31d80976dffe7acceaf2576246f9e42/matches": { + "result": [] + }, + "http://api:api@localhost:10009/bets/e566ab052d414d2c9b9d6ffc643bc5d2b31d80976dffe7acceaf2576246f9e42/resolutions": { + "result": [] + }, + "http://api:api@localhost:10009/burns": { + "result": [ + { + "tx_index": 1, + "tx_hash": "6dc5b0a33d4d4297e0f5cc2d23ae307951d32aab2d86b7fa147b385219f3a597", + "block_index": 310000, + "source": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "burned": 62000000, + "earned": 93000000000, + "status": "valid" + }, + { + "tx_index": 104, + "tx_hash": "65d4048700fb8ae03f321be93c6669b8497f506a1f43920f96d994f43358c35b", + "block_index": 310103, + "source": "myAtcJEHAsDLbTkai6ipWDZeeL7VkxXsiM", + "burned": 62000000, + "earned": 92999138821, + "status": "valid" + }, + { + "tx_index": 105, + "tx_hash": "95332a7e3e2b04f2c10e3027327bfc31b686947fb05381e28903e3ff569bd4ff", + "block_index": 310104, + "source": "munimLLHjPhGeSU5rYB2HN79LJa8bRZr5b", + "burned": 62000000, + "earned": 92999130460, + "status": "valid" + }, + { + "tx_index": 106, + "tx_hash": "e062d1ebf4cb71bd22d80c949b956f5286080838a7607ccf87945b2b3abfcafa", + "block_index": 310105, + "source": "mwtPsLQxW9xpm7gdLmwWvJK5ABdPUVJm42", + "burned": 62000000, + "earned": 92999122099, + "status": "valid" + }, + { + "tx_index": 107, + "tx_hash": "bbf0b9f6992755a3e371fb0c0b72f6828831e81c6f7ada6f95ba1104fb901ac3", + "block_index": 310106, + "source": "mrPk7hTeZWjjSCrMTC2ET4SAUThQt7C4uK", + "burned": 10000, + "earned": 14999857, + "status": "valid" + }, + { + "tx_index": 109, + "tx_hash": "93c6d2499a0536c31c77a3db3fc9fc8456fbd0726c45b8f716af16f938727a73", + "block_index": 310108, + "source": "2MyJHMUenMWonC35Yi6PHC7i2tkS7PuomCy", + "burned": 31000000, + "earned": 46499548508, + "status": "valid" + }, + { + "tx_index": 117, + "tx_hash": "27929c4fcad307a76ea7da34dd2691084f678a22ee43ce7f3842b78730ee08f9", + "block_index": 310116, + "source": "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", + "burned": 62000000, + "earned": 92999030129, + "status": "valid" + }, + { + "tx_index": 494, + "tx_hash": "c0733e1287afb1bb3d2fdacd1db7c74ea84f14362f3a8d1c038e662e1d0b1b1a", + "block_index": 310493, + "source": "mnfAHmddVibnZNSkh8DvKaQoiEfNsxjXzH", + "burned": 62000000, + "earned": 92995878046, + "status": "valid" + } + ] + }, + "http://api:api@localhost:10009/dispensers/74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498": { + "result": [] + }, + "http://api:api@localhost:10009/dispensers/74db175c4669a3d3a59e3fcddce9e97fcd7d12c35b58ef31845a1b20a1739498/dispenses": { + "result": [] + }, + "http://api:api@localhost:10009/events?limit=5": { + "result": [ + { + "event_index": 1237, + "event": "BLOCK_PARSED", + "bindings": { + "block_index": 310500, + "ledger_hash": "5ffefc7a2724be6bd697796bb82638ec913c5cbb73627153d1a13b48c7a6c02d", + "messages_hash": "45f296a535c13129cb1aaeb4e28a03e04ad902917891c39ae59ea2894e9f868f", + "txlist_hash": "35f4a33840d002ab4e0e44f11c1749ae95b41376927fb346140508b32518edd1" + }, + "block_index": 310500, + "timestamp": 0 + }, + { + "event_index": 1236, + "event": "NEW_BLOCK", + "bindings": { + "block_hash": "54aeaf47d5387964e2d51617bf3af50520a0449410e0d096cf8c2aa9dad5550b", + "block_index": 310500, + "block_time": 310500000, + "difficulty": null, + "ledger_hash": null, + "previous_block_hash": null, + "txlist_hash": null + }, + "block_index": 310499, + "timestamp": 0 + }, + { + "event_index": 1235, + "event": "BLOCK_PARSED", + "bindings": { + "block_index": 310499, + "ledger_hash": "b9fcbdafddd46fdda061f6e9f8744b426b6ca37e32b315df1098cbc7899ae9b9", + "messages_hash": "d6aedacd4f81520d86ae47c9c776d17a213c706a5cf7c91203a4299261d1648c", + "txlist_hash": "032166892f568bb97f4f69ef5bdf49cc1b15cc9f8c7f6c1f3e1f9d54816ad7e5" + }, + "block_index": 310499, + "timestamp": 0 + }, + { + "event_index": 1234, + "event": "NEW_BLOCK", + "bindings": { + "block_hash": "1950e1a4d7fc820ed9603f6df6819c3c953c277c726340dec2a4253e261a1764", + "block_index": 310499, + "block_time": 310499000, + "difficulty": null, + "ledger_hash": null, + "previous_block_hash": null, + "txlist_hash": null + }, + "block_index": 310498, + "timestamp": 0 + }, + { + "event_index": 1233, + "event": "BLOCK_PARSED", + "bindings": { + "block_index": 310498, + "ledger_hash": "5fe6cdb0828379bf240fad99c68bba34e1889bbc19605ce5c297b82352264414", + "messages_hash": "113207bd13dda56b5e5edf305f70a56e62cc861184e1e95a64e79ce100462c98", + "txlist_hash": "b488f6f0e6c233f202ee17c0843236d464144e79c870af88bae56355ae9372b7" + }, + "block_index": 310498, + "timestamp": 0 + } + ] + }, + "http://api:api@localhost:10009/events/10?limit=5": { + "result": [ + { + "event_index": 10, + "event": "ASSET_CREATION", + "bindings": { + "asset_id": "697326324582", + "asset_longname": null, + "asset_name": "DIVISIBLE", + "block_index": 310001 + }, + "block_index": 310001, + "timestamp": 0 + } + ] + }, + "http://api:api@localhost:10009/events/counts?limit=5": { + "result": [ + { + "event": "ASSET_CREATION", + "event_count": 10 + }, + { + "event": "ASSET_ISSUANCE", + "event_count": 13 + }, + { + "event": "BET_MATCH", + "event_count": 1 + }, + { + "event": "BET_MATCH_RESOLUTON", + "event_count": 1 + }, + { + "event": "BET_MATCH_UPDATE", + "event_count": 1 + }, + { + "event": "BET_UPDATE", + "event_count": 2 + }, + { + "event": "BLOCK_PARSED", + "event_count": 502 + }, + { + "event": "BROADCAST", + "event_count": 8 + }, + { + "event": "BURN", + "event_count": 8 + }, + { + "event": "CREDIT", + "event_count": 34 + }, + { + "event": "DEBIT", + "event_count": 34 + }, + { + "event": "ENHANCED_SEND", + "event_count": 2 + }, + { + "event": "NEW_BLOCK", + "event_count": 502 + }, + { + "event": "NEW_TRANSACTION", + "event_count": 52 + }, + { + "event": "OPEN_BET", + "event_count": 5 + }, + { + "event": "OPEN_DISPENSER", + "event_count": 1 + }, + { + "event": "OPEN_ORDER", + "event_count": 6 + }, + { + "event": "ORDER_MATCH", + "event_count": 1 + }, + { + "event": "ORDER_UPDATE", + "event_count": 2 + }, + { + "event": "SEND", + "event_count": 9 + }, + { + "event": "TRANSACTION_PARSED", + "event_count": 44 + } + ] + }, + "http://api:api@localhost:10009/events/CREDIT?limit=5": { + "result": [ + { + "event_index": 1231, + "event": "CREDIT", + "bindings": { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "A95428956661682277", + "block_index": 310498, + "calling_function": "issuance", + "event": "0abfce2662c05852fd8b181a60900678643cedad47b23a853b8c4eda82cb2cbf", + "quantity": 100000000, + "tx_index": 499 + }, + "block_index": 310498, + "timestamp": 0 + }, + { + "event_index": 1223, + "event": "CREDIT", + "bindings": { + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "asset": "PARENT", + "block_index": 310497, + "calling_function": "issuance", + "event": "076ae3d8eeb7fb40d2ae27692340157c746d9832806766b0dac5adb1526dc78f", + "quantity": 100000000, + "tx_index": 498 + }, + "block_index": 310497, + "timestamp": 0 + }, + { + "event_index": 1214, + "event": "CREDIT", + "bindings": { + "address": "mqPCfvqTfYctXMUfmniXeG2nyaN8w6tPmj", + "asset": "XCP", + "block_index": 310496, + "calling_function": "send", + "event": "a35ab1736565aceddbd1d71f92fc7f39d1361006aa9099f731e54e762964d5ba", + "quantity": 92945878046, + "tx_index": 497 + }, + "block_index": 310496, + "timestamp": 0 + }, + { + "event_index": 1207, + "event": "CREDIT", + "bindings": { + "address": "mqPCfvqTfYctXMUfmniXeG2nyaN8w6tPmj", + "asset": "DIVIDEND", + "block_index": 310495, + "calling_function": "send", + "event": "02156b9a1f643fb48330396274a37620c8abbbe5eddb2f8b53dadd135f5d2e2e", + "quantity": 10, + "tx_index": 496 + }, + "block_index": 310495, + "timestamp": 0 + }, + { + "event_index": 1201, + "event": "CREDIT", + "bindings": { + "address": "mnfAHmddVibnZNSkh8DvKaQoiEfNsxjXzH", + "asset": "DIVIDEND", + "block_index": 310494, + "calling_function": "issuance", + "event": "321bed395482e034f2ce0a4dbf28d1f800592a658e26ea91ae9c5b0928204503", + "quantity": 100, + "tx_index": 495 + }, + "block_index": 310494, + "timestamp": 0 + } + ] + } +} \ No newline at end of file diff --git a/counterparty-core/counterpartycore/test/fixtures/api_v2_unpack_fixtures.json b/counterparty-core/counterpartycore/test/fixtures/api_v2_unpack_fixtures.json new file mode 100644 index 0000000000..dbe15ab232 --- /dev/null +++ b/counterparty-core/counterpartycore/test/fixtures/api_v2_unpack_fixtures.json @@ -0,0 +1,710 @@ +[ + { + "datahex": "00000014000000a25be34b66000000174876e800010000000000000000000f446976697369626c65206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 697326324582, + "asset": "DIVISIBLE", + "subasset_longname": null, + "quantity": 100000000000, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "Divisible asset", + "status": "valid" + } + } + }, + { + "datahex": "000000140006cad8dc7f0b6600000000000003e800000000000000000000124e6f20646976697369626c65206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 1911882621324134, + "asset": "NODIVISIBLE", + "subasset_longname": null, + "quantity": 1000, + "divisible": false, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "No divisible asset", + "status": "valid" + } + } + }, + { + "datahex": "0000001400000003c58e5c5600000000000003e8010000000000000000000e43616c6c61626c65206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 16199343190, + "asset": "CALLABLE", + "subasset_longname": null, + "quantity": 1000, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "Callable asset", + "status": "valid" + } + } + }, + { + "datahex": "0000001400000000082c82e300000000000003e8010000000000000000000c4c6f636b6564206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 137134819, + "asset": "LOCKED", + "subasset_longname": null, + "quantity": 1000, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "Locked asset", + "status": "valid" + } + } + }, + { + "datahex": "0000001400000000082c82e3000000000000000001000000000000000000044c4f434b", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 137134819, + "asset": "LOCKED", + "subasset_longname": null, + "quantity": 0, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "LOCK", + "status": "valid" + } + } + }, + { + "datahex": "0000000a00000000000000010000000005f5e100000000a25be34b660000000005f5e10007d00000000000000000", + "result": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "XCP", + "give_quantity": 100000000, + "get_asset": "DIVISIBLE", + "get_quantity": 100000000, + "expiration": 2000, + "fee_required": 0, + "status": "open" + } + } + }, + { + "datahex": "00000000000000a25be34b660000000005f5e100", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "DIVISIBLE", + "quantity": 100000000 + } + } + }, + { + "datahex": "0000000000000000000000010000000005f5e100", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "XCP", + "quantity": 100000000 + } + } + }, + { + "datahex": "0000000a00000000000000010000000005f5e100000000a25be34b660000000005f5e10007d00000000000000000", + "result": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "XCP", + "give_quantity": 100000000, + "get_asset": "DIVISIBLE", + "get_quantity": 100000000, + "expiration": 2000, + "fee_required": 0, + "status": "open" + } + } + }, + { + "datahex": "0000000a00000000000000010000000005f5e100000000000000000000000000000f424007d000000000000dbba0", + "result": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "XCP", + "give_quantity": 100000000, + "get_asset": "BTC", + "get_quantity": 1000000, + "expiration": 2000, + "fee_required": 900000, + "status": "open" + } + } + }, + { + "datahex": "0000000a000000000000000000000000000a2c2b00000000000000010000000005f5e10007d00000000000000000", + "result": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "BTC", + "give_quantity": 666667, + "get_asset": "XCP", + "get_quantity": 100000000, + "expiration": 2000, + "fee_required": 0, + "status": "open" + } + } + }, + { + "datahex": "0000000000000000000000010000000011e1a300", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "XCP", + "quantity": 300000000 + } + } + }, + { + "datahex": "00000000000000a25be34b66000000003b9aca00", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "DIVISIBLE", + "quantity": 1000000000 + } + } + }, + { + "datahex": "000000000006cad8dc7f0b660000000000000005", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "NODIVISIBLE", + "quantity": 5 + } + } + }, + { + "datahex": "000000000006cad8dc7f0b66000000000000000a", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "NODIVISIBLE", + "quantity": 10 + } + } + }, + { + "datahex": "000000140000000000033a3e7fffffffffffffff01000000000000000000104d6178696d756d207175616e74697479", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 211518, + "asset": "MAXI", + "subasset_longname": null, + "quantity": 9223372036854775807, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "Maximum quantity", + "status": "valid" + } + } + }, + { + "datahex": "0000001e52bb33003ff0000000000000004c4b4009556e69742054657374", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000000, + "value": 1.0, + "fee_fraction_int": 5000000, + "text": "Unit Test", + "status": "valid" + } + } + }, + { + "datahex": "0000001e4cc552003ff000000000000000000000046c6f636b", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1288000000, + "value": 1.0, + "fee_fraction_int": 0, + "text": "lock", + "status": "valid" + } + } + }, + { + "datahex": "00000028000152bb3301000000000000000900000000000000090000000000000000000013b000000064", + "result": { + "message_type": "bet", + "message_type_id": 40, + "message_data": { + "bet_type": 1, + "deadline": 1388000001, + "wager_quantity": 9, + "counterwager_quantity": 9, + "target_value": 0.0, + "leverage": 5040, + "expiration": 100, + "status": "open" + } + } + }, + { + "datahex": "00000028000052bb3301000000000000000900000000000000090000000000000000000013b000000064", + "result": { + "message_type": "bet", + "message_type_id": 40, + "message_data": { + "bet_type": 0, + "deadline": 1388000001, + "wager_quantity": 9, + "counterwager_quantity": 9, + "target_value": 0.0, + "leverage": 5040, + "expiration": 100, + "status": "open" + } + } + }, + { + "datahex": "00000028000352bb33c8000000000000000a000000000000000a0000000000000000000013b0000003e8", + "result": { + "message_type": "bet", + "message_type_id": 40, + "message_data": { + "bet_type": 3, + "deadline": 1388000200, + "wager_quantity": 10, + "counterwager_quantity": 10, + "target_value": 0.0, + "leverage": 5040, + "expiration": 1000, + "status": "open" + } + } + }, + { + "datahex": "0000001e52bb33023ff0000000000000004c4b4009556e69742054657374", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000002, + "value": 1.0, + "fee_fraction_int": 5000000, + "text": "Unit Test", + "status": "valid" + } + } + }, + { + "datahex": "0000000c000000000000000100000000000000640000000000000064000000000000006400", + "result": { + "message_type": "dispenser", + "message_type_id": 12, + "message_data": { + "asset": "XCP", + "give_quantity": 100, + "escrow_quantity": 100, + "mainchainrate": 100, + "dispenser_status": 0, + "action_address": null, + "oracle_address": null, + "status": "valid" + } + } + }, + { + "datahex": "0000001400078a8fe2e5e44100000000000003e8000000000000000000001050534820697373756564206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 2122675428648001, + "asset": "PAYTOSCRIPT", + "subasset_longname": null, + "quantity": 1000, + "divisible": false, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "PSH issued asset", + "status": "valid" + } + } + }, + { + "datahex": "00000000000000a25be34b660000000005f5e100", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "DIVISIBLE", + "quantity": 100000000 + } + } + }, + { + "datahex": "0000001e52bb33023ff0000000000000004c4b4009556e69742054657374", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000002, + "value": 1.0, + "fee_fraction_int": 5000000, + "text": "Unit Test", + "status": "valid" + } + } + }, + { + "datahex": "00000028000352bb33c8000000000000000a000000000000000a0000000000000000000013b0000003e8", + "result": { + "message_type": "bet", + "message_type_id": 40, + "message_data": { + "bet_type": 3, + "deadline": 1388000200, + "wager_quantity": 10, + "counterwager_quantity": 10, + "target_value": 0.0, + "leverage": 5040, + "expiration": 1000, + "status": "open" + } + } + }, + { + "datahex": "00000014000038fedf6d2c6900000000000003e8010000000000000000000c4c6f636b6564206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 62667321322601, + "asset": "LOCKEDPREV", + "subasset_longname": null, + "quantity": 1000, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "Locked asset", + "status": "valid" + } + } + }, + { + "datahex": "00000014000038fedf6d2c69000000000000000001000000000000000000044c4f434b", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 62667321322601, + "asset": "LOCKEDPREV", + "subasset_longname": null, + "quantity": 0, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "LOCK", + "status": "valid" + } + } + }, + { + "datahex": "00000014000038fedf6d2c69000000000000000001000000000000000000076368616e676564", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 62667321322601, + "asset": "LOCKEDPREV", + "subasset_longname": null, + "quantity": 0, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "changed", + "status": "valid" + } + } + }, + { + "datahex": "0000000200000000000000010000000005f5e1006f8d6ae8a3b381663118b4e1eff4cfc7d0954dd6ec68656c6c6f", + "result": { + "message_type": "enhanced_send", + "message_type_id": 2, + "message_data": { + "asset": "XCP", + "quantity": 100000000, + "address": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns", + "memo": "68656c6c6f" + } + } + }, + { + "datahex": "0000000200000000000000010000000005f5e1006f4838d8b3588c4c7ba7c1d06f866e9b3739c63037fade0001", + "result": { + "message_type": "enhanced_send", + "message_type_id": 2, + "message_data": { + "asset": "XCP", + "quantity": 100000000, + "address": "mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc", + "memo": "fade0001" + } + } + }, + { + "datahex": "0000001e52bb33003ff0000000000000004c4b4009556e69742054657374", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000000, + "value": 1.0, + "fee_fraction_int": 5000000, + "text": "Unit Test", + "status": "valid" + } + } + }, + { + "datahex": "00000028000152bb3301000000000000000900000000000000090000000000000000000013b000000064", + "result": { + "message_type": "bet", + "message_type_id": 40, + "message_data": { + "bet_type": 1, + "deadline": 1388000001, + "wager_quantity": 9, + "counterwager_quantity": 9, + "target_value": 0.0, + "leverage": 5040, + "expiration": 100, + "status": "open" + } + } + }, + { + "datahex": "0000001e52bb33023ff000000000000000000000096f7074696f6e732030", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000002, + "value": 1.0, + "fee_fraction_int": 0, + "text": "options 0", + "status": "valid" + } + } + }, + { + "datahex": "0000001e52bb33033ff000000000000000000000046c6f636b", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000003, + "value": 1.0, + "fee_fraction_int": 0, + "text": "lock", + "status": "valid" + } + } + }, + { + "datahex": "0000001e52bb33043ff000000000000000000000096f7074696f6e732031", + "result": { + "message_type": "broadcast", + "message_type_id": 30, + "message_data": { + "timestamp": 1388000004, + "value": 1.0, + "fee_fraction_int": 0, + "text": "options 1", + "status": "valid" + } + } + }, + { + "datahex": "0000000a00000000000000010000000005f5e100000000000000000000000000000c350007d000000000000dbba0", + "result": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "XCP", + "give_quantity": 100000000, + "get_asset": "BTC", + "get_quantity": 800000, + "expiration": 2000, + "fee_required": 900000, + "status": "open" + } + } + }, + { + "datahex": "0000000a000000000000000000000000000c350000000000000000010000000005f5e10007d00000000000000000", + "result": { + "message_type": "order", + "message_type_id": 10, + "message_data": { + "give_asset": "BTC", + "give_quantity": 800000, + "get_asset": "XCP", + "get_quantity": 100000000, + "expiration": 2000, + "fee_required": 0, + "status": "open" + } + } + }, + { + "datahex": "00000014000000063e985ffd00000000000000640100000000000000000000", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 26819977213, + "asset": "DIVIDEND", + "subasset_longname": null, + "quantity": 100, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "", + "status": "valid" + } + } + }, + { + "datahex": "00000000000000063e985ffd000000000000000a", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "DIVIDEND", + "quantity": 10 + } + } + }, + { + "datahex": "00000000000000000000000100000015a4018c1e", + "result": { + "message_type": "send", + "message_type_id": 0, + "message_data": { + "asset": "XCP", + "quantity": 92945878046 + } + } + }, + { + "datahex": "00000014000000000aa4097d0000000005f5e100010000000000000000000c506172656e74206173736574", + "result": { + "message_type": "issuance", + "message_type_id": 20, + "message_data": { + "asset_id": 178522493, + "asset": "PARENT", + "subasset_longname": null, + "quantity": 100000000, + "divisible": true, + "lock": null, + "reset": null, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "Parent asset", + "status": "valid" + } + } + }, + { + "datahex": "0000001501530821671b10650000000005f5e10001108e90a57dba9967c422e83080f22f0c684368696c64206f6620706172656e74", + "result": { + "message_type": "issuance", + "message_type_id": 21, + "message_data": { + "asset_id": null, + "asset": null, + "subasset_longname": null, + "quantity": null, + "divisible": null, + "lock": null, + "reset": null, + "callable": null, + "call_date": null, + "call_price": null, + "description": null, + "status": "invalid: could not unpack" + } + } + } +] \ No newline at end of file diff --git a/counterparty-core/counterpartycore/test/fixtures/vectors.py b/counterparty-core/counterpartycore/test/fixtures/vectors.py index 15a554dc60..43717b875b 100644 --- a/counterparty-core/counterpartycore/test/fixtures/vectors.py +++ b/counterparty-core/counterpartycore/test/fixtures/vectors.py @@ -9,13 +9,12 @@ """ import binascii -import json # noqa: F401 from fractions import Fraction import bitcoin as bitcoinlib from counterpartycore.lib import address, config, exceptions, script # noqa: F401 -from counterpartycore.lib.api import APIError +from counterpartycore.lib.api.api_v1 import APIError from counterpartycore.lib.kickstart.blocks_parser import BlockchainParser from counterpartycore.lib.ledger import CreditError, DebitError from counterpartycore.lib.messages import issuance @@ -6479,28 +6478,24 @@ { "in": ( b"o\x9c\x8d\x1fT\x05E\x1d\xe6\x07\x0b\xf1\xdb\x86\xabj\xcc\xb4\x95\xb6%\x01", - DP["default_block_index"], ), "out": {"destination": ADDR[5], "flags": 1, "memo": None}, }, { "in": ( b"o\x9c\x8d\x1fT\x05E\x1d\xe6\x07\x0b\xf1\xdb\x86\xabj\xcc\xb4\x95\xb6%\x02", - DP["default_block_index"], ), "out": {"destination": ADDR[5], "flags": 2, "memo": None}, }, { "in": ( b"o\x9c\x8d\x1fT\x05E\x1d\xe6\x07\x0b\xf1\xdb\x86\xabj\xcc\xb4\x95\xb6%\x03test", - DP["default_block_index"], ), "out": {"destination": ADDR[5], "flags": 3, "memo": "test"}, }, { "in": ( b"o\x9c\x8d\x1fT\x05E\x1d\xe6\x07\x0b\xf1\xdb\x86\xabj\xcc\xb4\x95\xb6%\x07\xca\xfe\xba\xbe", - DP["default_block_index"], ), "out": {"destination": ADDR[5], "flags": 7, "memo": b"\xca\xfe\xba\xbe"}, }, @@ -7696,7 +7691,7 @@ }, ], }, - "api": { + "api_v1": { "get_rows": [ { "in": ("balances", None, "AND", None, None, None, None, None, 1000, 0, True), diff --git a/counterparty-core/counterpartycore/test/p2sh_encoding_test.py b/counterparty-core/counterpartycore/test/p2sh_encoding_test.py index 7cf1dc9fdd..06c1bf4088 100644 --- a/counterparty-core/counterpartycore/test/p2sh_encoding_test.py +++ b/counterparty-core/counterpartycore/test/p2sh_encoding_test.py @@ -1,8 +1,6 @@ import binascii import hashlib import logging -import math # noqa: F401 -import pprint # noqa: F401 import tempfile import time @@ -20,14 +18,13 @@ logger = logging.getLogger(__name__) from counterpartycore.lib import ( # noqa: E402 - api, backend, config, exceptions, gettxinfo, ledger, script, - util, # noqa: F401 + transaction, ) from counterpartycore.lib.kickstart.blocks_parser import BlockchainParser # noqa: E402 from counterpartycore.lib.transaction_helper import p2sh_encoding, serializer # noqa: E402, F401 @@ -78,7 +75,7 @@ def test_p2sh_encoding(server_db): fee = 20000 fee_per_kb = 50000 - result = api.compose_transaction( + result = transaction.compose_transaction( server_db, "send", {"source": source, "destination": destination, "asset": "XCP", "quantity": 100}, @@ -150,7 +147,7 @@ def test_p2sh_encoding(server_db): logger.debug(f"pretxid {pretxid}") # check that when we do another, unrelated, send that it won't use our UTXO - result = api.compose_transaction( + result = transaction.compose_transaction( server_db, "send", {"source": source, "destination": destination, "asset": "XCP", "quantity": 100}, @@ -164,7 +161,7 @@ def test_p2sh_encoding(server_db): ) # now compose the data transaction - result = api.compose_transaction( + result = transaction.compose_transaction( server_db, "send", {"source": source, "destination": destination, "asset": "XCP", "quantity": 100}, @@ -256,7 +253,7 @@ def test_p2sh_encoding_long_data(server_db): # pprint.pprint(utxos) fee_per_kb = 50000 - result = api.compose_transaction( + result = transaction.compose_transaction( server_db, "broadcast", { @@ -333,7 +330,7 @@ def test_p2sh_encoding_long_data(server_db): logger.debug(f"pretxid {pretxid}") # now compose the data transaction - result = api.compose_transaction( + result = transaction.compose_transaction( server_db, "broadcast", { @@ -438,7 +435,7 @@ def test_p2sh_encoding_p2sh_source_not_supported(server_db): fee_per_kb = 50000 with pytest.raises(exceptions.TransactionError): - result = api.compose_transaction( # noqa: F841 + result = transaction.compose_transaction( # noqa: F841 server_db, "send", {"source": source, "destination": destination, "asset": "XCP", "quantity": 100}, @@ -480,7 +477,7 @@ def test_p2sh_encoding_manual_multisig_transaction(server_db): # setup transaction fee = 20000 fee_per_kb = 50000 - pretxhex = api.compose_transaction( + pretxhex = transaction.compose_transaction( server_db, "send", { @@ -506,7 +503,7 @@ def test_p2sh_encoding_manual_multisig_transaction(server_db): logger.debug(f"pretxid {pretxid}") # now compose the data transaction - result = api.compose_transaction( + result = transaction.compose_transaction( server_db, "send", {"source": source, "destination": destination, "asset": "XCP", "quantity": 100}, diff --git a/counterparty-core/counterpartycore/test/util_test.py b/counterparty-core/counterpartycore/test/util_test.py index 80bbb4493f..70bea57256 100644 --- a/counterparty-core/counterpartycore/test/util_test.py +++ b/counterparty-core/counterpartycore/test/util_test.py @@ -67,6 +67,7 @@ "testcoin": False, "rpc_port": 9999, "rpc_password": "pass", + "api_password": "api", "backend_port": 18332, "backend_password": "pass", "backend_ssl_no_verify": True, @@ -777,6 +778,9 @@ def exec_tested_method(tx_name, method, tested_method, inputs, server_db): or tx_name == "backend" or tx_name == "message_type" or tx_name == "address" + or (tx_name == "versions.enhanced_send" and method == "unpack") + or (tx_name == "versions.mpma" and method == "unpack") + or (tx_name == "sweep" and method == "unpack") ): return tested_method(*inputs) else: @@ -803,7 +807,10 @@ def check_outputs( try: tested_module = sys.modules[f"counterpartycore.lib.{tx_name}"] except KeyError: # TODO: hack - tested_module = sys.modules[f"counterpartycore.lib.messages.{tx_name}"] + if tx_name == "api_v1": + tested_module = sys.modules["counterpartycore.lib.api.api_v1"] + else: + tested_module = sys.modules[f"counterpartycore.lib.messages.{tx_name}"] tested_method = getattr(tested_module, method) with MockProtocolChangesContext(**(mock_protocol_changes or {})): diff --git a/counterparty-core/counterpartycore/test/utxolocks_test.py b/counterparty-core/counterpartycore/test/utxolocks_test.py index 733d1502c9..d6ab4cc678 100644 --- a/counterparty-core/counterpartycore/test/utxolocks_test.py +++ b/counterparty-core/counterpartycore/test/utxolocks_test.py @@ -4,7 +4,6 @@ from io import BytesIO import bitcoin -import pytest # noqa: F401 from counterpartycore.lib import transaction from counterpartycore.lib.messages import send diff --git a/counterparty-core/requirements.txt b/counterparty-core/requirements.txt index 83822330be..c3a87c474c 100644 --- a/counterparty-core/requirements.txt +++ b/counterparty-core/requirements.txt @@ -26,4 +26,6 @@ arc4==0.4.0 halo==0.0.31 termcolor==2.4.0 sentry-sdk==1.45.0 +Flask-Cors==4.0.0 +docstring_parser==0.16 counterparty-rs==10.1.1 diff --git a/counterparty-core/tools/apicache.json b/counterparty-core/tools/apicache.json new file mode 100644 index 0000000000..636107c57b --- /dev/null +++ b/counterparty-core/tools/apicache.json @@ -0,0 +1,2584 @@ +{ + "/blocks//events": { + "result": [ + { + "event_index": 14194760, + "event": "BLOCK_PARSED", + "bindings": { + "block_index": 840464, + "ledger_hash": "b3f8cbb50b0705a5c4a8495f8b5128de13a32daebd8ac5e8316a010f0d203584", + "messages_hash": "801d961c45a257f85ef0f10a6a8fdf048a520ae4861c0903f26365b3eaaaf540", + "txlist_hash": "84bdc5b9073f775a2b65de7da2b10b89a2235f3501883b0a836e41e68cd00d46" + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194759, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194758, + "event": "CREDIT", + "bindings": { + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "block_index": 840464, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "quantity": 1, + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194757, + "event": "ASSET_ISSUANCE", + "bindings": { + "asset": "UNNEGOTIABLE", + "asset_longname": null, + "block_index": 840464, + "call_date": 0, + "call_price": 0.0, + "callable": false, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "divisible": false, + "fee_paid": 50000000, + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "locked": false, + "quantity": 1, + "reset": false, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "status": "valid", + "transfer": false, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194756, + "event": "ASSET_CREATION", + "bindings": { + "asset_id": "75313533584419238", + "asset_longname": null, + "asset_name": "UNNEGOTIABLE", + "block_index": 840464 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194755, + "event": "DEBIT", + "bindings": { + "action": "issuance fee", + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "XCP", + "block_index": 840464, + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "quantity": 50000000, + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + }, + { + "event_index": 14194754, + "event": "NEW_TRANSACTION", + "bindings": { + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_index": 840464, + "block_time": 1713852783, + "btc_amount": 0, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "destination": "", + "fee": 56565, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852779 + }, + { + "event_index": 14194753, + "event": "NEW_BLOCK", + "bindings": { + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_index": 840464, + "block_time": 1713852783, + "difficulty": 86388558925171.02, + "previous_block_hash": "00000000000000000002db1e5aa19784eec3de949f98ec757e7a7f2fc392079d" + }, + "block_index": 840464, + "timestamp": 1713852779 + } + ] + }, + "/blocks//events/counts": { + "result": [ + { + "event": "ASSET_CREATION", + "event_count": 1 + }, + { + "event": "ASSET_ISSUANCE", + "event_count": 1 + }, + { + "event": "BLOCK_PARSED", + "event_count": 1 + }, + { + "event": "CREDIT", + "event_count": 1 + }, + { + "event": "DEBIT", + "event_count": 1 + }, + { + "event": "NEW_BLOCK", + "event_count": 1 + }, + { + "event": "NEW_TRANSACTION", + "event_count": 1 + }, + { + "event": "TRANSACTION_PARSED", + "event_count": 1 + } + ] + }, + "/blocks//events/": { + "result": [ + { + "event_index": 14194758, + "event": "CREDIT", + "bindings": { + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "block_index": 840464, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "quantity": 1, + "tx_index": 2726605 + }, + "block_index": 840464, + "timestamp": 1713852780 + } + ] + }, + "/blocks//credits": { + "result": [ + { + "block_index": 840464, + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "quantity": 1, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + } + ] + }, + "/blocks//debits": { + "result": [ + { + "block_index": 840464, + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "XCP", + "quantity": 50000000, + "action": "issuance fee", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + } + ] + }, + "/blocks//expirations": { + "result": [ + { + "type": "order", + "object_id": "533d5c0ecd8ca9c2946d3298cc5e570eee55b62b887dd85c95de6de4fdc7f441" + }, + { + "type": "order", + "object_id": "b048661afeee3f266792481168024abc0d7648fe0e019e4a1e0fd9867c2c0ffc" + } + ] + }, + "/blocks//cancels": { + "result": [ + { + "tx_index": 2725738, + "tx_hash": "793af9129c7368f974c3ea0c87ad38131f0d82d19fbaf1adf8aaf2e657ec42b8", + "block_index": 839746, + "source": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "offer_hash": "04b258ac37f73e3b9a8575110320d67c752e1baace0f516da75845f388911735", + "status": "valid" + }, + { + "tx_index": 2725739, + "tx_hash": "2071e8a6fbc0c443b152d513c754356f8f962db2fa694de8c6826b57413cc190", + "block_index": 839746, + "source": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "offer_hash": "b1622dbe4f0ce740cb6c18f6f136876bc4949c40a62bc8cceefa81fd6679a57f", + "status": "valid" + } + ] + }, + "/blocks//destructions": { + "result": [ + { + "tx_index": 2726496, + "tx_hash": "f5609facc8dac6cdf70b15c514ea15a9acc24a9bd86dcac2b845d5740fbcc50b", + "block_index": 839988, + "source": "1FpLAtreZjTVCMcj1pq1AHWuqcs3n7obMm", + "asset": "COBBEE", + "quantity": 50000, + "tag": "", + "status": "valid" + } + ] + }, + "/blocks//issuances": { + "result": [ + { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "msg_index": 0, + "block_index": 840464, + "asset": "UNNEGOTIABLE", + "quantity": 1, + "divisible": 0, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "transfer": 0, + "callable": 0, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "fee_paid": 50000000, + "locked": 0, + "status": "valid", + "asset_longname": null, + "reset": 0 + } + ] + }, + "/blocks//sends": { + "result": [ + { + "tx_index": 2726604, + "tx_hash": "b4bbb14c99dd260eb634243e5c595e1b7213459979857a32850de84989bb71ec", + "block_index": 840459, + "source": "13Hnmhs5gy2yXKVBx4wSM5HCBdKnaSBZJH", + "destination": "1LfT83WAxbN9qKhtrXxcQA6xgdhfZk21Hz", + "asset": "GAMESOFTRUMP", + "quantity": 1, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "/blocks//dispenses": { + "result": [ + { + "tx_index": 2726580, + "dispense_index": 0, + "tx_hash": "e7f0f2c9bef7a492b714a5952ec61b283be344419c5bc33f405f9af41ebfa48b", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "bc1qzcdkhnexpjc8wvkyrpyrsn0f5xzcpu877mjmgj", + "asset": "FLOCK", + "dispense_quantity": 90000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + } + ] + }, + "/blocks//sweeps": { + "result": [ + { + "tx_index": 2720536, + "tx_hash": "9309a4c0aed426e281a52e5d48acadd1464999269a5e75cf2293edd0277d743d", + "block_index": 836519, + "source": "1DMVnJuqBobXA9xYioabBsR4mN8bvVtCAW", + "destination": "1HC2q92SfH1ZHzS4CrDwp6KAipV4FqUL4T", + "flags": 3, + "status": "valid", + "memo": null, + "fee_paid": 1400000 + }, + { + "tx_index": 2720537, + "tx_hash": "d8db6281abffdbf6c320d5ade06aeb6fad2f7bfa1a2c2243c6726020a27107d3", + "block_index": 836519, + "source": "18szqTVJUWwYrtRHq98Wn4DhCGGiy3jZ87", + "destination": "1HC2q92SfH1ZHzS4CrDwp6KAipV4FqUL4T", + "flags": 3, + "status": "valid", + "memo": null, + "fee_paid": 1400000 + } + ] + }, + "/transactions/info": { + "result": { + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "destination": "", + "btc_amount": 0, + "fee": 56565, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "unpacked_data": { + "message_type": "issuance", + "message_type_id": 22, + "message_data": { + "asset_id": 75313533584419238, + "asset": "UNNEGOTIABLE", + "subasset_longname": null, + "quantity": 1, + "divisible": false, + "lock": false, + "reset": false, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "status": "valid" + } + } + } + }, + "/transactions/unpack": { + "result": { + "message_type": "issuance", + "message_type_id": 22, + "message_data": { + "asset_id": 75313533584419238, + "asset": "UNNEGOTIABLE", + "subasset_longname": null, + "quantity": 1, + "divisible": false, + "lock": false, + "reset": false, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "status": "valid" + } + } + }, + "/addresses/
/balances": { + "result": [ + { + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000 + } + ] + }, + "/addresses/
/balances/": { + "result": { + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000 + } + }, + "/addresses/
/credits": { + "result": [ + { + "block_index": 830981, + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000, + "calling_function": "send", + "event": "7e4fbb0a1eeeee34bf499955f1027fb78c514d63a3c8ff2e28c6dad005e4d850", + "tx_index": 2677412 + } + ] + }, + "/addresses/
/debits": { + "result": [ + { + "block_index": 836949, + "address": "bc1q7787j6msqczs58asdtetchl3zwe8ruj57p9r9y", + "asset": "XCP", + "quantity": 40000000000, + "action": "open dispenser", + "event": "53ed08176d3479f49986e9282293da85cebc03835b128d8e790ee587f9f1c750", + "tx_index": 2721524 + }, + { + "block_index": 840388, + "address": "bc1q7787j6msqczs58asdtetchl3zwe8ruj57p9r9y", + "asset": "XCP", + "quantity": 250000000000, + "action": "send", + "event": "bc54968ba7d0a59a47b276602e2dbdcf01b14009742e0d7b50272cbae529a9a4", + "tx_index": 2726594 + } + ] + }, + "/addresses/
/bets": { + "result": [ + { + "tx_index": 15106, + "tx_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "block_index": 304063, + "source": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 3, + "deadline": 1401828300, + "wager_quantity": 50000000, + "wager_remaining": 0, + "counterwager_quantity": 50000000, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 11, + "expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "filled" + }, + { + "tx_index": 61338, + "tx_hash": "0fcc7f5190c028f6c5534554d10ec5b4a9246d63826421cd58be2d572d11f088", + "block_index": 320704, + "source": "1Ew38GxczvV1KxjzZsq9f8UuRzHkHQrL5C", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 2, + "deadline": 1410728400, + "wager_quantity": 1000000, + "wager_remaining": 0, + "counterwager_quantity": 1999991, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 13, + "expire_index": 320715, + "fee_fraction_int": 1000000, + "status": "filled" + } + ] + }, + "/addresses/
/broadcasts": { + "result": [ + { + "tx_index": 15055, + "tx_hash": "774887e555a6ae5a8c058ebc0185058307977f01a2d4d326e71f37d6dd977154", + "block_index": 304048, + "source": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "timestamp": 1401815290, + "value": -1.0, + "fee_fraction_int": 1000000, + "text": "xbet.io/feed/1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "locked": 0, + "status": "valid" + }, + { + "tx_index": 61477, + "tx_hash": "5d49993bec727622c7b41c84e2b1e65c368f33390d633d217131ffcc5b592f0d", + "block_index": 320718, + "source": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "timestamp": 1410732503, + "value": 1.0, + "fee_fraction_int": 1000000, + "text": "xbet.io/feed/1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "locked": 0, + "status": "valid" + } + ] + }, + "/addresses/
/burns": { + "result": [ + { + "tx_index": 3070, + "tx_hash": "4560d0e3d04927108b615ab106040489aca9c4aceedcf69d2b71f63b3139c7ae", + "block_index": 283810, + "source": "1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W", + "burned": 10000000, + "earned": 10000000000, + "status": "valid" + } + ] + }, + "/addresses/
/sends": { + "result": [ + { + "tx_index": 163106, + "tx_hash": "1c447b41816f1cfbb83f125c8e05faeaae70dbf27255745ba7393f809bd388eb", + "block_index": 343049, + "source": "1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W", + "destination": "16cRBUNnTWiUh2sXWNn1P7KHyJUmyMkdfH", + "asset": "XCP", + "quantity": 10000000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "/addresses/
/receives": { + "result": [ + { + "tx_index": 2677412, + "tx_hash": "7e4fbb0a1eeeee34bf499955f1027fb78c514d63a3c8ff2e28c6dad005e4d850", + "block_index": 830981, + "source": "bc1qqxr9grqw73dm95cen3g56mzswuj6eqjedu6csx", + "destination": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "/addresses/
/sends/": { + "result": [ + { + "tx_index": 163106, + "tx_hash": "1c447b41816f1cfbb83f125c8e05faeaae70dbf27255745ba7393f809bd388eb", + "block_index": 343049, + "source": "1HVgrYx3U258KwvBEvuG7R8ss1RN2Z9J1W", + "destination": "16cRBUNnTWiUh2sXWNn1P7KHyJUmyMkdfH", + "asset": "XCP", + "quantity": 10000000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "/addresses/
/receives/": { + "result": [ + { + "tx_index": 2677412, + "tx_hash": "7e4fbb0a1eeeee34bf499955f1027fb78c514d63a3c8ff2e28c6dad005e4d850", + "block_index": 830981, + "source": "bc1qqxr9grqw73dm95cen3g56mzswuj6eqjedu6csx", + "destination": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "/addresses/
/dispensers": { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + }, + "/addresses/
/dispensers/": { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + }, + "/addresses/
/sweeps": { + "result": [ + { + "tx_index": 2720537, + "tx_hash": "d8db6281abffdbf6c320d5ade06aeb6fad2f7bfa1a2c2243c6726020a27107d3", + "block_index": 836519, + "source": "18szqTVJUWwYrtRHq98Wn4DhCGGiy3jZ87", + "destination": "1HC2q92SfH1ZHzS4CrDwp6KAipV4FqUL4T", + "flags": 3, + "status": "valid", + "memo": null, + "fee_paid": 1400000 + } + ] + }, + "/addresses/
/compose/btcpay": { + "result": { + "rawtransaction": "0200000000010161101e1990879ee64168cce92c9caf338bb571e9cb246b1c2ab87124b95091900200000016001482f2ccc569325050e36c13b55a4065113d985066ffffffff0383c3040000000000160014a9943f67bcd30331d5a4ec6d902cbe03789a1b9700000000000000004b6a49aae396d448ed266a7785be1f6fcfa38dbe3e6e043e3d67691f678d6aa3b30e423f66ffad71eaf3231ef8f05dd5cc2f5b1ea14d33274b9cddacca5bd816a1ce6d5b4d498eb66a981db7add758000000000016001482f2ccc569325050e36c13b55a4065113d98506602000000000000", + "params": { + "source": "bc1qsteve3tfxfg9pcmvzw645sr9zy7es5rx645p6l", + "order_match_id": "e470416a9500fb046835192da013f48e6468a07dba1bede4a0b68e666ed23c8d_4953bde3d9417b103615c2d3d4b284d4fcf7cbd820e5dd19ac0084e9ebd090b2" + }, + "name": "btcpay" + } + }, + "/assets": { + "result": [ + { + "asset": "A100000000000000000", + "asset_longname": null + }, + { + "asset": "A1000000000000000000", + "asset_longname": null + }, + { + "asset": "A10000000000000000000", + "asset_longname": null + }, + { + "asset": "A10000000000000000001", + "asset_longname": null + }, + { + "asset": "A10000000000000000002", + "asset_longname": null + } + ] + }, + "/assets/": { + "result": { + "asset": "UNNEGOTIABLE", + "asset_longname": null, + "owner": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "divisible": false, + "locked": false, + "supply": 1, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "holder_count": 1 + } + }, + "/assets//balances": { + "result": [ + { + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "quantity": 1 + } + ] + }, + "/assets//balances/
": { + "result": { + "address": "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + "asset": "XCP", + "quantity": 104200000000 + } + }, + "/assets//orders": { + "result": [ + { + "tx_index": 825373, + "tx_hash": "0129611a0aece52adddf6d929e75c703baa9cdcb7e4ce887aa859f9640aa9640", + "block_index": 455461, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 400000000000, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 46098, + "fee_provided_remaining": 46098, + "status": "filled" + }, + { + "tx_index": 2225134, + "tx_hash": "5b6e0c741d765ebd883dc16eecfb5c340c52865cabf297ca2c1432437c1348b7", + "block_index": 772817, + "source": "1FnM7akSCD8G3fRQHCUEXRCfL35gptsPZB", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "XCP", + "get_quantity": 80800000000, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 777817, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 5544, + "fee_provided_remaining": 5544, + "status": "filled" + }, + { + "tx_index": 1946026, + "tx_hash": "75dc6ee1f67317e674ef33b617d3a9839ee53bf4a2e8274c88d6202d4d89b59a", + "block_index": 727444, + "source": "1GotRejB6XsGgMsM79TvcypeanDJRJbMtg", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "XCP", + "get_quantity": 70000000000, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 732381, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 264, + "fee_provided_remaining": 264, + "status": "filled" + }, + { + "tx_index": 2202451, + "tx_hash": "77f568fc6604dbe209d2ea1b0158d7de20723c0178107eb570f4f2a719b0d7c7", + "block_index": 772817, + "source": "184gKLQTtQU29LXbxbYJkUV4if9SmW6v2d", + "give_asset": "XCP", + "give_quantity": 80800000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 773300, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 264, + "fee_provided_remaining": 264, + "status": "filled" + }, + { + "tx_index": 825411, + "tx_hash": "7b2369f40078f4d98a3d3a7733315a1c4efd7977c75f7066dd447d5c7eed7f20", + "block_index": 455461, + "source": "18cmgoX99Nrm411YKpmTQsp23qczWdxS6w", + "give_asset": "PEPECASH", + "give_quantity": 300000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 460461, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 40000, + "fee_provided_remaining": 40000, + "status": "filled" + }, + { + "tx_index": 825403, + "tx_hash": "7e1abf6ad57eb61227015fc7a333da034b4dd2f1c4e23cf106864b60a20feef7", + "block_index": 455460, + "source": "18cmgoX99Nrm411YKpmTQsp23qczWdxS6w", + "give_asset": "PEPECASH", + "give_quantity": 200000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456460, + "fee_required": 20000, + "fee_required_remaining": 20000, + "fee_provided": 50766, + "fee_provided_remaining": 50766, + "status": "filled" + }, + { + "tx_index": 825370, + "tx_hash": "8e4d324407b62de773af53f8f7a556882ac82a217c216491a28072f293918fe6", + "block_index": 455457, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 100000000000, + "get_remaining": -1100000000, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 75791, + "fee_provided_remaining": 75791, + "status": "filled" + }, + { + "tx_index": 825413, + "tx_hash": "927878fa98edb6d24310c45254c324f3d5a7f625e2a3a0e7fd1e749b49493750", + "block_index": 455461, + "source": "18cmgoX99Nrm411YKpmTQsp23qczWdxS6w", + "give_asset": "PEPECASH", + "give_quantity": 400000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 460461, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 40000, + "fee_provided_remaining": 40000, + "status": "filled" + }, + { + "tx_index": 1946587, + "tx_hash": "b747f290cbbad6faa1c1c05d5c6d001b5a3ef487027bb0d4eefcdc9f6e865c39", + "block_index": 727444, + "source": "1AtcSh7uxenQ6AR5xqr6agAegWRUF5N4uh", + "give_asset": "XCP", + "give_quantity": 70000000000, + "give_remaining": 0, + "get_asset": "NEEDPEPE", + "get_quantity": 1, + "get_remaining": 0, + "expiration": 5000, + "expire_index": 732444, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 792, + "fee_provided_remaining": 792, + "status": "filled" + }, + { + "tx_index": 825371, + "tx_hash": "b83c96217214decb6316c3619bc88a3471d17e46eb3708406c8f878dedd61610", + "block_index": 455460, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 200000000000, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 46098, + "fee_provided_remaining": 46098, + "status": "filled" + }, + { + "tx_index": 825372, + "tx_hash": "e32154f8ade796df0b121604de140703d062d22d1e82e77e629e6096668c812f", + "block_index": 455461, + "source": "1Fpx9NPBJsRbx6RXkvfZ3n1iCYj7n7VaJR", + "give_asset": "NEEDPEPE", + "give_quantity": 1, + "give_remaining": 0, + "get_asset": "PEPECASH", + "get_quantity": 300000000000, + "get_remaining": 0, + "expiration": 1000, + "expire_index": 456457, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 46098, + "fee_provided_remaining": 46098, + "status": "filled" + } + ] + }, + "/assets//credits": { + "result": [ + { + "block_index": 840464, + "address": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "asset": "UNNEGOTIABLE", + "quantity": 1, + "calling_function": "issuance", + "event": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "tx_index": 2726605 + } + ] + }, + "/assets//debits": { + "result": [ + { + "block_index": 280091, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1000000000, + "action": "send", + "event": "1c20d6596f6be031c94def5ad93a52217d76371885adcc53c91c3b1eaf76ccce", + "tx_index": 729 + }, + { + "block_index": 280112, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "action": "send", + "event": "4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6", + "tx_index": 749 + }, + { + "block_index": 280112, + "address": "1PMacKVWDszkBRbb2iWWvX63BwhKUTsSBd", + "asset": "XCP", + "quantity": 100000000, + "action": "send", + "event": "057d10cc33455f4f7af44d2f030b3866e3a16416ecf984e304c76abe98393c1d", + "tx_index": 752 + }, + { + "block_index": 280114, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "action": "send", + "event": "3ac6ea5b329832e2dc31ead6c5277beccb7d95f0d9f20f256f97067223c81e00", + "tx_index": 755 + }, + { + "block_index": 280156, + "address": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "action": "send", + "event": "66fc1409ac6646bd8c267de89c57d2204e31bb6dfce9ee2a3ab18416fadf9e9c", + "tx_index": 766 + } + ] + }, + "/assets//dividends": { + "result": [ + { + "tx_index": 1914456, + "tx_hash": "30760e413947ebdc80ed7a5ada1bd4466800b87e9976bbe811ad4e2b46546359", + "block_index": 724381, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "ENDTHEFED", + "quantity_per_unit": 1, + "fee_paid": 2520000, + "status": "valid" + }, + { + "tx_index": 1915246, + "tx_hash": "827794cbab3299f80a5b8b8cb8ec29ec3aee1373f7da2c05a156bed902bf4684", + "block_index": 724479, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "TRUMPDANCING", + "quantity_per_unit": 100, + "fee_paid": 2520000, + "status": "valid" + }, + { + "tx_index": 1920208, + "tx_hash": "7014f1e259531ba9632ca5000c35df5bd47f237318e48955900453ce9c07e917", + "block_index": 724931, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "CTRWOJACK", + "quantity_per_unit": 1111, + "fee_paid": 2700000, + "status": "valid" + }, + { + "tx_index": 1927909, + "tx_hash": "5556fd2b0802cf3bc0abd5001ecbac3adbc5b7c5c46a145a78daeef358c308de", + "block_index": 725654, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "WHITERUSSIAN", + "quantity_per_unit": 1, + "fee_paid": 3220000, + "status": "valid" + }, + { + "tx_index": 1983693, + "tx_hash": "cda646285cc63f758d19b5403070f23e2a6e4b34eb3b86b63a0f56f971345657", + "block_index": 730568, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "A4520591452211866149", + "quantity_per_unit": 1, + "fee_paid": 4040000, + "status": "valid" + }, + { + "tx_index": 1983842, + "tx_hash": "e4b73dc974cc279b873b78e5dc4a347c08788b02143ae27aa0582f900289be10", + "block_index": 730588, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "NCSWIC", + "quantity_per_unit": 1, + "fee_paid": 4040000, + "status": "valid" + }, + { + "tx_index": 1996395, + "tx_hash": "b342feb1421df107010ad3c8ee2043ded802bdf6cd619862459da3d0f87d6a99", + "block_index": 731994, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "FUCKTHEFED", + "quantity_per_unit": 1, + "fee_paid": 4380000, + "status": "valid" + }, + { + "tx_index": 2035947, + "tx_hash": "02d715fd9e8b7bbc782b1b2d92a1b9ffae9326bfc88ba76c453c515ad7c8c2bc", + "block_index": 738763, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "HOLDTHELINE", + "quantity_per_unit": 1, + "fee_paid": 4940000, + "status": "valid" + }, + { + "tx_index": 2174481, + "tx_hash": "b935a06fc34d8fa4f0c526984085b1b12c78e899415e595b625f1bee84ce3709", + "block_index": 762733, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "EOXIXIZERO", + "quantity_per_unit": 1, + "fee_paid": 6500000, + "status": "valid" + }, + { + "tx_index": 2198534, + "tx_hash": "a063e9a745b9f6bc3201f72abff196de20ec106bcc71d820673d516ddbb3aa90", + "block_index": 767569, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "TRUMPCARDS", + "quantity_per_unit": 1, + "fee_paid": 6660000, + "status": "valid" + }, + { + "tx_index": 2704948, + "tx_hash": "437102ca4698f63a12e369f6168e3c7f5f8eef3e225395d515775673e33d39c1", + "block_index": 832745, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "FUCKYOUWAR", + "quantity_per_unit": 1, + "fee_paid": 6840000, + "status": "valid" + }, + { + "tx_index": 2704949, + "tx_hash": "7d3807cc58fa2d9751b2b0089bfa8fa86ef795821be6d8e9418ab3a819eba299", + "block_index": 832745, + "source": "1JJP986hdU9Qy9b49rafM9FoXdbz1Mgbjo", + "asset": "GMONEYPEPE", + "dividend_asset": "MEDICINEPEPE", + "quantity_per_unit": 1, + "fee_paid": 6840000, + "status": "valid" + } + ] + }, + "/assets//issuances": { + "result": [ + { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "msg_index": 0, + "block_index": 840464, + "asset": "UNNEGOTIABLE", + "quantity": 1, + "divisible": 0, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "issuer": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "transfer": 0, + "callable": 0, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "fee_paid": 50000000, + "locked": 0, + "status": "valid", + "asset_longname": null, + "reset": 0 + } + ] + }, + "/assets//sends": { + "result": [ + { + "tx_index": 729, + "tx_hash": "1c20d6596f6be031c94def5ad93a52217d76371885adcc53c91c3b1eaf76ccce", + "block_index": 280091, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1000000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 749, + "tx_hash": "4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6", + "block_index": 280112, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 752, + "tx_hash": "057d10cc33455f4f7af44d2f030b3866e3a16416ecf984e304c76abe98393c1d", + "block_index": 280112, + "source": "1PMacKVWDszkBRbb2iWWvX63BwhKUTsSBd", + "destination": "1PMacKVWDszkBRbb2iWWvX63BwhKUTsSBd", + "asset": "XCP", + "quantity": 100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 755, + "tx_hash": "3ac6ea5b329832e2dc31ead6c5277beccb7d95f0d9f20f256f97067223c81e00", + "block_index": 280114, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "status": "valid", + "msg_index": 0, + "memo": null + }, + { + "tx_index": 766, + "tx_hash": "66fc1409ac6646bd8c267de89c57d2204e31bb6dfce9ee2a3ab18416fadf9e9c", + "block_index": 280156, + "source": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "destination": "1Pcpxw6wJwXABhjCspe3CNf3gqSeh6eien", + "asset": "XCP", + "quantity": 1100000000, + "status": "valid", + "msg_index": 0, + "memo": null + } + ] + }, + "/assets//dispensers": { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + }, + "/assets//dispensers/
": { + "result": [ + { + "tx_index": 2726460, + "tx_hash": "b592d8ca4994d182e4ec63e1659dc4282b1a84466b7d71ed68c281ce63ed4897", + "block_index": 839964, + "source": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "asset": "ERYKAHPEPU", + "give_quantity": 1, + "escrow_quantity": 25, + "satoshirate": 50000, + "status": 0, + "give_remaining": 25, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "dispense_count": 0 + } + ] + }, + "/assets//holders": { + "result": [ + { + "address": "1E6tyJ2zCyX74XgEK8t9iNMjxjNVLCGR1u", + "address_quantity": 63, + "escrow": null + }, + { + "address": "16yRstRXStVJJ1TN2S4DCWifyrCsetpma7", + "address_quantity": 1, + "escrow": null + }, + { + "address": "bc1qsvqsa9arwz30g2z0w09twzn8gz3380h36yxacs", + "address_quantity": 2, + "escrow": null + }, + { + "address": "17PnWBjHkekZKQPVagmTR5HiD51pN8WHC8", + "address_quantity": 1, + "escrow": null + }, + { + "address": "1FRxFpP9XoRsvZFVqGtt4fjjgKe1h5tbAh", + "address_quantity": 1, + "escrow": null + }, + { + "address": "1AdHg2q3M2rMFRgZyZ7RQyNHdwjSib7wSZ", + "address_quantity": 2, + "escrow": null + }, + { + "address": "1CTnziWXidHzY3qT8gwLa1ZxZK37A7HreR", + "address_quantity": 1, + "escrow": null + }, + { + "address": "bc1qlzkcy8c5fa6y6xvd8zn4axnvmhndfhku3hmdpz", + "address_quantity": 25, + "escrow": null + } + ] + }, + "/orders/": { + "result": [ + { + "tx_index": 2724132, + "tx_hash": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776", + "block_index": 840381, + "source": "15L7U55PAsHLEpQkZqz62e3eqWd9AHb2DH", + "give_asset": "PEPECASH", + "give_quantity": 6966600000000, + "give_remaining": 900000000000, + "get_asset": "XCP", + "get_quantity": 11076894000, + "get_remaining": 1431000000, + "expiration": 5000, + "expire_index": 843055, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 4488, + "fee_provided_remaining": 4488, + "status": "open" + } + ] + }, + "/orders//matches": { + "result": [ + { + "id": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776_5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947", + "tx0_index": 2724132, + "tx0_hash": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776", + "tx0_address": "15L7U55PAsHLEpQkZqz62e3eqWd9AHb2DH", + "tx1_index": 2726591, + "tx1_hash": "5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947", + "tx1_address": "15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA", + "forward_asset": "PEPECASH", + "forward_quantity": 6066600000000, + "backward_asset": "XCP", + "backward_quantity": 9645894000, + "tx0_block_index": 838055, + "tx1_block_index": 840381, + "block_index": 840381, + "tx0_expiration": 5000, + "tx1_expiration": 8064, + "match_expire_index": 840401, + "fee_paid": 0, + "status": "completed" + } + ] + }, + "/orders//btcpays": { + "result": [ + { + "tx_index": 2719343, + "tx_hash": "6cfa7f31b43a46e5ad74a9db810bd6cac56235a8ebc73ec63d01b38ea7ea2414", + "block_index": 836188, + "source": "1NfJnJdAdmm2rJCFW54NsAKqqTTMexCNJ3", + "destination": "1BepkwAhEmEuEGF349XjmEUrRvoy9a7Biv", + "btc_amount": 4500000, + "order_match_id": "0a1387df82a8a7e9cec01c52c8fee01f6995c4e39dc5804e1d2bf40d9368f5c5_299b5b648f54eacb839f3487232d49aea373cdd681b706d4cc0b5e0b03688db4", + "status": "valid" + } + ] + }, + "/bets/": { + "result": [ + { + "tx_index": 15106, + "tx_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "block_index": 304063, + "source": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 3, + "deadline": 1401828300, + "wager_quantity": 50000000, + "wager_remaining": 0, + "counterwager_quantity": 50000000, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 11, + "expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "filled" + } + ] + }, + "/bets//matches": { + "result": [ + { + "id": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed_cb5f888c299a50967d523513daed71636d927e6ef3dbda85feb11ff112ae4330", + "tx0_index": 15106, + "tx0_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "tx0_address": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "tx1_index": 15108, + "tx1_hash": "cb5f888c299a50967d523513daed71636d927e6ef3dbda85feb11ff112ae4330", + "tx1_address": "1PTqJmRCMGs4qBEh2APAFSrBv95Uf1hfiD", + "tx0_bet_type": 3, + "tx1_bet_type": 2, + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "initial_value": -1, + "deadline": 1401828300, + "target_value": 1.0, + "leverage": 5040, + "forward_quantity": 50000000, + "backward_quantity": 50000000, + "tx0_block_index": 304062, + "tx1_block_index": 304063, + "block_index": 306379, + "tx0_expiration": 11, + "tx1_expiration": 1459, + "match_expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "expired" + } + ] + }, + "/bets//resolutions": { + "result": [ + { + "bet_match_id": "36bbbb7dbd85054dac140a8ad8204eda2ee859545528bd2a9da69ad77c277ace_d70ee4e44f02fe6258ee0c267f33f304a0fc61d4ce424852f58c28967dc1924f", + "bet_match_type_id": 5, + "block_index": 401128, + "winner": "Equal", + "settled": null, + "bull_credit": null, + "bear_credit": null, + "escrow_less_fee": 2000000, + "fee": 0 + } + ] + }, + "/burns": { + "result": [ + { + "tx_index": 10, + "tx_hash": "41bbe1ec81da008a0e92758efb6084af3a6b6acf483983456ec797ee59c0e0f1", + "block_index": 278511, + "source": "12crRpZpn93PKTQ4WYxHMw4xi6ckh1CFR3", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 11, + "tx_hash": "c403a92281b568c7d428d942354d026594dc54ae35c21f53ecf5c918208c45de", + "block_index": 278511, + "source": "13UXh9dBEhA48gJiegJNodqe91PK88f4pW", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 12, + "tx_hash": "749ba1c2bd314f7b98e9cfb44575495b4ad2cf624901c65488fbc4f57a3dc0ac", + "block_index": 278511, + "source": "19Ht3rkW7JB9VuC7rsZEGZju96ujzchaZZ", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 13, + "tx_hash": "da330160b71138f9bda5e126df0d5d6248c0879d88e16255c74135274d8ebd27", + "block_index": 278511, + "source": "16Fu8Edsvxqixg6VnaHKPWE2TEsqQMwXfV", + "burned": 99900000, + "earned": 148024554545, + "status": "valid" + }, + { + "tx_index": 14, + "tx_hash": "66994176733650e77ae0cf34349f63e6538649f40f86d2719013d915bbb7701e", + "block_index": 278517, + "source": "14FFaRsfzYQxhZQv1YsMn65MvMLfJShgM8", + "burned": 99900000, + "earned": 147970063636, + "status": "valid" + } + ] + }, + "/dispensers/": { + "result": [ + { + "tx_index": 2536311, + "tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "asset": "FLOCK", + "give_quantity": 10000000000, + "escrow_quantity": 250000000000, + "satoshirate": 330000, + "status": 0, + "give_remaining": 140000000000, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "dispense_count": 2, + "asset_longname": null + } + ] + }, + "/dispensers//dispenses": { + "result": [ + { + "tx_index": 2610745, + "dispense_index": 0, + "tx_hash": "8c95cc6afc8fd466c784fd1c02749c585988999bbc66251b944c443dc31af757", + "block_index": 821450, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "1FKYM1CP9RfttJhNG8HTNQdE2uV3YvwbRB", + "asset": "FLOCK", + "dispense_quantity": 20000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + }, + { + "tx_index": 2726580, + "dispense_index": 0, + "tx_hash": "e7f0f2c9bef7a492b714a5952ec61b283be344419c5bc33f405f9af41ebfa48b", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "bc1qzcdkhnexpjc8wvkyrpyrsn0f5xzcpu877mjmgj", + "asset": "FLOCK", + "dispense_quantity": 90000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + } + ] + }, + "/events": { + "result": [ + { + "event_index": 10665092, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665091, + "event": "ENHANCED_SEND", + "bindings": { + "asset": "THOTHPEPE", + "block_index": 744232, + "destination": "13re7J5Y5a8nZZSp8o1a3sEUqGik4NMXhS", + "memo": null, + "quantity": 1, + "source": "173cE6ScUFCmBLCqZeG18ij6r9KHRPbAjC", + "status": "valid", + "tx_hash": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665090, + "event": "CREDIT", + "bindings": { + "address": "13re7J5Y5a8nZZSp8o1a3sEUqGik4NMXhS", + "asset": "THOTHPEPE", + "block_index": 744232, + "calling_function": "send", + "event": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "quantity": 1, + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665089, + "event": "DEBIT", + "bindings": { + "action": "send", + "address": "173cE6ScUFCmBLCqZeG18ij6r9KHRPbAjC", + "asset": "THOTHPEPE", + "block_index": 744232, + "event": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "quantity": 1, + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665088, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "bbb2dfa7e7a32288a702ef0091ece8b2a929f94fd967a18e6071cd9c2b085eaf", + "tx_index": 2056159 + }, + "block_index": 744232, + "timestamp": 1712256340 + } + ] + }, + "/events/": { + "result": [ + { + "event_index": 10665092, + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + } + ] + }, + "/events/counts": { + "result": [ + { + "event": "ASSET_CREATION", + "event_count": 235860 + }, + { + "event": "ASSET_DESTRUCTION", + "event_count": 11141 + }, + { + "event": "ASSET_DIVIDEND", + "event_count": 4092 + }, + { + "event": "ASSET_ISSUANCE", + "event_count": 322678 + }, + { + "event": "ASSET_TRANSFER", + "event_count": 10639 + }, + { + "event": "BET_EXPIRATION", + "event_count": 588 + }, + { + "event": "BET_MATCH", + "event_count": 397 + }, + { + "event": "BET_MATCH_EXPIRATION", + "event_count": 9 + }, + { + "event": "BET_MATCH_RESOLUTON", + "event_count": 387 + }, + { + "event": "BET_MATCH_UPDATE", + "event_count": 397 + }, + { + "event": "BET_UPDATE", + "event_count": 1474 + }, + { + "event": "BLOCK_PARSED", + "event_count": 562364 + }, + { + "event": "BROADCAST", + "event_count": 106518 + }, + { + "event": "BTC_PAY", + "event_count": 2921 + }, + { + "event": "BURN", + "event_count": 2576 + }, + { + "event": "CANCEL_BET", + "event_count": 101 + }, + { + "event": "CANCEL_ORDER", + "event_count": 80168 + }, + { + "event": "CREDIT", + "event_count": 3659293 + }, + { + "event": "DEBIT", + "event_count": 2617404 + }, + { + "event": "DISPENSE", + "event_count": 190873 + }, + { + "event": "DISPENSER_UPDATE", + "event_count": 228954 + }, + { + "event": "ENHANCED_SEND", + "event_count": 538426 + }, + { + "event": "MPMA_SEND", + "event_count": 279142 + }, + { + "event": "NEW_BLOCK", + "event_count": 1992 + }, + { + "event": "NEW_TRANSACTION", + "event_count": 4498 + }, + { + "event": "NEW_TRANSACTION_OUTPUT", + "event_count": 596 + }, + { + "event": "OPEN_BET", + "event_count": 1149 + }, + { + "event": "OPEN_DISPENSER", + "event_count": 88229 + }, + { + "event": "OPEN_ORDER", + "event_count": 530117 + }, + { + "event": "OPEN_RPS", + "event_count": 266 + }, + { + "event": "ORDER_EXPIRATION", + "event_count": 195968 + }, + { + "event": "ORDER_FILLED", + "event_count": 805 + }, + { + "event": "ORDER_MATCH", + "event_count": 209415 + }, + { + "event": "ORDER_MATCH_EXPIRATION", + "event_count": 20860 + }, + { + "event": "ORDER_MATCH_UPDATE", + "event_count": 23689 + }, + { + "event": "ORDER_UPDATE", + "event_count": 732646 + }, + { + "event": "REFILL_DISPENSER", + "event_count": 187 + }, + { + "event": "RESET_ISSUANCE", + "event_count": 454 + }, + { + "event": "RPS_EXPIRATION", + "event_count": 59 + }, + { + "event": "RPS_MATCH", + "event_count": 171 + }, + { + "event": "RPS_MATCH_EXPIRATION", + "event_count": 145 + }, + { + "event": "RPS_MATCH_UPDATE", + "event_count": 271 + }, + { + "event": "RPS_RESOLVE", + "event_count": 129 + }, + { + "event": "RPS_UPDATE", + "event_count": 540 + }, + { + "event": "SEND", + "event_count": 805983 + }, + { + "event": "SWEEP", + "event_count": 1020 + }, + { + "event": "TRANSACTION_PARSED", + "event_count": 2723802 + } + ] + }, + "/events/": { + "result": [ + { + "event_index": 10665090, + "event": "CREDIT", + "bindings": { + "address": "13re7J5Y5a8nZZSp8o1a3sEUqGik4NMXhS", + "asset": "THOTHPEPE", + "block_index": 744232, + "calling_function": "send", + "event": "7b39d3ebd9fe8293004a1a8b8eb2d01f1664e5d8b05e8cb94f30b1da2c2f9650", + "quantity": 1, + "tx_index": 2056160 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665085, + "event": "CREDIT", + "bindings": { + "address": "1LfDk3Ex9KPYS6L1WGwNdt1TvEg6Le8uq", + "asset": "XCP", + "block_index": 744232, + "calling_function": "dispense", + "event": "bbb2dfa7e7a32288a702ef0091ece8b2a929f94fd967a18e6071cd9c2b085eaf", + "quantity": 10000000000, + "tx_index": 2056159 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665082, + "event": "CREDIT", + "bindings": { + "address": "173cE6ScUFCmBLCqZeG18ij6r9KHRPbAjC", + "asset": "FREEDOMKEK", + "block_index": 744232, + "calling_function": "send", + "event": "b419d19729c2be813405c548431f4840d5c909b875f94b7c56aeca134e328ef6", + "quantity": 1, + "tx_index": 2056158 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665078, + "event": "CREDIT", + "bindings": { + "address": "1P8nYZwLmecAkQUHsx2H9Nkxd51UJ2Asau", + "asset": "PEPEFRIDAY", + "block_index": 744232, + "calling_function": "send", + "event": "145ebf6c563c4e91a2bc488954ef701dad730fc065697979c80d6d85cbba63e1", + "quantity": 1, + "tx_index": 2056157 + }, + "block_index": 744232, + "timestamp": 1712256340 + }, + { + "event_index": 10665074, + "event": "CREDIT", + "bindings": { + "address": "1NzDQ7HLm6PqJ2Wy6jEKMT7Zw1UbtjUV5a", + "asset": "PEPEFRIDAY", + "block_index": 744232, + "calling_function": "send", + "event": "388c7208d52bf617c1a3eef238a668f694a4f72dc97b3be92562fe636ca646fa", + "quantity": 2, + "tx_index": 2056156 + }, + "block_index": 744232, + "timestamp": 1712256340 + } + ] + }, + "/addresses/
/compose/broadcast": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0200000000000000002b6a290d1e454cefefcbe17b1100cb21d3398ec45d2594e5d1d822df41d03a332741261ce2f9aee7827cd91c340c0406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "timestamp": 4003903983, + "value": 100.0, + "fee_fraction": 0.05, + "text": "\"Hello, world!\"" + }, + "name": "broadcast" + } + }, + "/addresses/
/compose/bet": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0322020000000000001976a914bce6191bf2fd5981313cae869e9fafe164f7dbaf88ac0000000000000000316a2f0d1e454cefefcbe14dffa4c01ecd608ec45d2594e5d27c699f4ef2725648c509bf828ec195ee18f83e052061236deff2db0306000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "feed_address": "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + "bet_type": 2, + "deadline": 3000000000, + "wager_quantity": 1000, + "counterwager_quantity": 1000, + "target_value": 1000, + "leverage": 5040, + "expiration": 100 + }, + "name": "bet" + } + }, + "/addresses/
/compose/burn": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff02e8030000000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ace61b0406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "quantity": 1000, + "overburn": false + }, + "name": "burn" + } + }, + "/addresses/
/compose/cancel": { + "result": { + "rawtransaction": "01000000014709bd6af5d4d7f518f80539d4fe9acd5220a520a7b4287416a7379af9e66154020000001976a91432dff6deb7ca3bbc14f7037fa6ef8a8cf8e39fb988acffffffff0200000000000000002b6a292f3720d2b8ae7343c6d0456802c531e1216f466ceb12b96c6fbe417a97291a0660e51fc47fcc1ee1a878667900000000001976a91432dff6deb7ca3bbc14f7037fa6ef8a8cf8e39fb988ac00000000", + "params": { + "source": "15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA", + "offer_hash": "8ce3335391bf71f8f12c0573b4f85b9adc4882a9955d9f8e5ababfdd0060279a" + }, + "name": "cancel" + } + }, + "/addresses/
/compose/destroy": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000226a200d1e454cefefcbe10bffa672ce93608ec55d2594e5d1946a776c900731380c6b94160406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "asset": "XCP", + "quantity": 1000, + "tag": "\"bugs!\"" + }, + "name": "destroy" + } + }, + "/addresses/
/compose/dispenser": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0200000000000000002c6a2a0d1e454cefefcbe169ffa672ce93608ec55d2594e5d1946a774ef272564b2d4ad8c28ec195ee18f85a160c0b0406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "asset": "XCP", + "give_quantity": 1000, + "escrow_quantity": 1000, + "mainchainrate": 100, + "status": 0, + "open_address": null, + "oracle_address": null + }, + "name": "dispenser" + } + }, + "/addresses/
/compose/dividend": { + "result": { + "rawtransaction": "01000000010af94458ae5aa794c49cd27f7b800a7c68c8dd4f59ff66c99db4e9e353c06d93010000001976a914a9055398b92818794b38b15794096f752167e25f88acffffffff020000000000000000236a21068a00268d252c3a8ed0bddb5ef79f823894aa7de1e196c005510f4d787c936a979b230000000000001976a914a9055398b92818794b38b15794096f752167e25f88ac00000000", + "params": { + "source": "1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD", + "quantity_per_unit": 1, + "asset": "PEPECASH", + "dividend_asset": "XCP" + }, + "name": "dividend" + } + }, + "/addresses/
/compose/issuance": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff0322020000000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac0000000000000000236a210d1e454cefefcbe173ffa672cf3a36751b5d2594e5d1946a774ff272960578057c17ec0306000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "asset": "XCPTEST", + "quantity": 1000, + "transfer_destination": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "divisible": true, + "lock": false, + "reset": false, + "description": null + }, + "name": "issuance" + } + }, + "/addresses/
/compose/mpma": { + "result": { + "rawtransaction": "0100000001fc9b7b3a0552bdfc3c62096e9d7669fb72d5482c7b4f9618138fdffdc831d60b000000001976a914a39dbfab6f1da182af53a4d14799ee545a6176be88acffffffff04e80300000000000069512103ce014780415d0eafbdadfacfa0cf2604a005a87157042f277627c952eedcbb1f2103abf2b72459ee70e6240a7b2ade1a6fa41c7f38cc1db5e63c6f92c01b859017ee2102e849a65234e77627daab722dd75aee7a8f35981ec1dbd5ec5ee7220075b2cd2d53aee80300000000000069512102ce014780415d0eafbd2fcbf00e308d420b59df89ebba83369fea96a9a06fcf562102373ec5e1389ccadf0a972ec451f8aea015104ded7a57b936d374d0ecfe8067412102e849a65234e77627daab722dd75aee7a8f35981ec1dbd5ec5ee7220075b2cd2d53aee80300000000000069512103d0014780415d0eafbd76dacca0b613dda4b8f37e3015031f11220ac5cf43ef4e21034051b78cdcbde85f0c120261e6ab383015104ded7a57b93cd374d900776d4e132102e849a65234e77627daab722dd75aee7a8f35981ec1dbd5ec5ee7220075b2cd2d53ae22fd0200000000001976a914a39dbfab6f1da182af53a4d14799ee545a6176be88ac00000000", + "params": { + "source": "1Fv87qmdtjQDP9d4p9E5ncBQvYB4a3Rhy6", + "asset_dest_quant_list": [ + [ + "BAABAABLKSHP", + "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + 1 + ], + [ + "BADHAIRDAY", + "1GQhaWqejcGJ4GhQar7SjcCfadxvf5DNBD", + 2 + ], + [ + "BADWOJAK", + "1C3uGcoSGzKVgFqyZ3kM2DBq9CYttTMAVs", + 3 + ] + ], + "memo": "\"Hello, world!\"", + "memo_is_hex": false + }, + "name": "mpma" + } + }, + "/addresses/
/compose/order": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000356a330d1e454cefefcbe16fffa672ce93608ec55d2594e5d1946a774ef2724a2a4f457bc28ec195ee18fbd616f461236d8be718616dac000406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "give_asset": "XCP", + "give_quantity": 1000, + "get_asset": "PEPECASH", + "get_quantity": 1000, + "expiration": 100, + "fee_required": 100 + }, + "name": "order" + } + }, + "/addresses/
/compose/send": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000306a2e0d1e454cefefcbe167ffa672ce93608ec55d2594e5d1946a774e4e944f50dfb46943bffd3b68866791f7f496f8c270060406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "destination": "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + "asset": "XCP", + "quantity": 1000, + "memo": null, + "memo_is_hex": false, + "use_enhanced_send": true + }, + "name": "send" + } + }, + "/addresses/
/compose/sweep": { + "result": { + "rawtransaction": "01000000017004c1186a4a6a11708e1739839488180dbb6dbf4a9bf52228faa5b3173cdb05000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188acffffffff020000000000000000236a210d1e454cefefcbe161ff1a94d78892739ddc14a84b570af630af96858de42ab6cf6e150406000000001976a914818895f3dc2c178629d3d2d8fa3ec4a3f817982188ac00000000", + "params": { + "source": "1CounterpartyXXXXXXXXXXXXXXXUWLpVr", + "destination": "1JDogZS6tQcSxwfxhv6XKKjcyicYA4Feev", + "flags": 7, + "memo": "FFFF" + }, + "name": "sweep" + } + }, + "/mempool/events": { + "result": [ + { + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "event": "NEW_TRANSACTION", + "bindings": { + "block_hash": "mempool", + "block_index": 9999999, + "block_time": 1713952590, + "btc_amount": 0, + "data": "0200454ceacf416ccf0000000000000001005461639d06ebc42d541b54b1c5525543ae4d6db3", + "destination": "", + "fee": 9900, + "source": "14PxDTVUMCjLoAcGPZGQf6cEtn7yLzdHp1", + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "tx_index": 2726767 + }, + "timestamp": 1713952691 + }, + { + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "event": "ENHANCED_SEND", + "bindings": { + "asset": "FIERCERABBIT", + "destination": "18hARq2fFJxiypHSnZ8yLcbPNpUfaozD8U", + "memo": null, + "quantity": 1, + "source": "14PxDTVUMCjLoAcGPZGQf6cEtn7yLzdHp1", + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e" + }, + "timestamp": 1713952691 + }, + { + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "event": "TRANSACTION_PARSED", + "bindings": { + "supported": true, + "tx_hash": "8a154886671713cae6d72d2996f07fac61529c0d8d1ebc476f448212ff3d535e", + "tx_index": 2726767 + }, + "timestamp": 1713952691 + } + ] + }, + "/mempool/events/": { + "result": [ + { + "tx_hash": "90ba95c4578b9ab7866515d66736c5b4132e88a0bd9b0fca7b2f1be830a1bb81", + "event": "OPEN_ORDER", + "bindings": { + "expiration": 5000, + "expire_index": 10004999, + "fee_provided": 5016, + "fee_provided_remaining": 5016, + "fee_required": 0, + "fee_required_remaining": 0, + "get_asset": "XCP", + "get_quantity": 3300000000, + "get_remaining": 3300000000, + "give_asset": "PEPEPASSPORT", + "give_quantity": 100000000, + "give_remaining": 100000000, + "source": "1A36UrLHxeg9ABoS4zPsRUegyCWTWER2kF", + "tx_hash": "90ba95c4578b9ab7866515d66736c5b4132e88a0bd9b0fca7b2f1be830a1bb81" + }, + "timestamp": 1713952690 + }, + { + "tx_hash": "bc553f3d4349a266b70e7ed98e2198a18d634a5b247997f59817f69e19de2ad6", + "event": "OPEN_ORDER", + "bindings": { + "expiration": 5000, + "expire_index": 10004999, + "fee_provided": 5016, + "fee_provided_remaining": 5016, + "fee_required": 0, + "fee_required_remaining": 0, + "get_asset": "XCP", + "get_quantity": 1185000000, + "get_remaining": 1185000000, + "give_asset": "FRATPEPE", + "give_quantity": 3, + "give_remaining": 3, + "source": "1A36UrLHxeg9ABoS4zPsRUegyCWTWER2kF", + "tx_hash": "bc553f3d4349a266b70e7ed98e2198a18d634a5b247997f59817f69e19de2ad6" + }, + "timestamp": 1713952690 + } + ] + }, + "/backend/addresses/
/transactions": { + "result": [ + { + "tx_hash": "eae4f1dba4d75bda9dd0de12f69a980be267bbc16b7a280a2a4b40c4b3bbb70a" + }, + { + "tx_hash": "7ec16c461e3ba2d3acae48fcc8f58c04fba9f307b00c391eab507337ddc0bf16" + }, + { + "tx_hash": "ad35f05767aadd39019122b4f4828ccb059b8121c07be6d36eb1e2ddbe9ac317" + }, + { + "tx_hash": "3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018" + }, + { + "tx_hash": "aba5810714aa6196fec5538a83bbc281077a84ef2cbce2045b4c9f3c4439f14f" + }, + { + "tx_hash": "23758832e0fc92a7ea303623b8f743219cb8e637e7e7ac9fb6f90641efac9379" + }, + { + "tx_hash": "98bef616ef265dd2f6004683e908d7df97e0c5f322cdf2fb2ebea9a9131cfa79" + }, + { + "tx_hash": "687b875d1dc472aa2fb994c5753c9b9b56e5c6fd1a6de18a92fcb3dc7ba8067e" + }, + { + "tx_hash": "ec97c11ff5cb318505ebe20d7aa3c033816824a79f9a49821ffb584ed7d6c78f" + }, + { + "tx_hash": "c732f0906eeada2113524c6652c17b2784780110bffd4333eb8f719ac0eff3be" + }, + { + "tx_hash": "2c8bc3eede9ec60d26c6fd7f44829adc64da593552044a28c673022220f560c3" + }, + { + "tx_hash": "a209e345549cffef6e2190b53ac0222afc965fd618843df5ccbd645a6a7999ee" + } + ] + }, + "/backend/addresses/
/transactions/oldest": { + "result": { + "block_index": 833187, + "tx_hash": "2c8bc3eede9ec60d26c6fd7f44829adc64da593552044a28c673022220f560c3" + } + }, + "/backend/addresses/
/utxos": { + "result": [ + { + "vout": 6, + "height": 833559, + "value": 34611, + "confirmations": 7083, + "amount": 0.00034611, + "txid": "98bef616ef265dd2f6004683e908d7df97e0c5f322cdf2fb2ebea9a9131cfa79" + }, + { + "vout": 0, + "height": 833187, + "value": 619481, + "confirmations": 7455, + "amount": 0.00619481, + "txid": "2c8bc3eede9ec60d26c6fd7f44829adc64da593552044a28c673022220f560c3" + }, + { + "vout": 0, + "height": 837379, + "value": 992721, + "confirmations": 3263, + "amount": 0.00992721, + "txid": "ad35f05767aadd39019122b4f4828ccb059b8121c07be6d36eb1e2ddbe9ac317" + }, + { + "vout": 0, + "height": 840640, + "value": 838185, + "confirmations": 2, + "amount": 0.00838185, + "txid": "3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018" + }, + { + "vout": 0, + "height": 839421, + "value": 336973, + "confirmations": 1221, + "amount": 0.00336973, + "txid": "c732f0906eeada2113524c6652c17b2784780110bffd4333eb8f719ac0eff3be" + }, + { + "vout": 0, + "height": 839462, + "value": 78615, + "confirmations": 1180, + "amount": 0.00078615, + "txid": "eae4f1dba4d75bda9dd0de12f69a980be267bbc16b7a280a2a4b40c4b3bbb70a" + }, + { + "vout": 0, + "height": 838442, + "value": 557283, + "confirmations": 2200, + "amount": 0.00557283, + "txid": "aba5810714aa6196fec5538a83bbc281077a84ef2cbce2045b4c9f3c4439f14f" + }, + { + "vout": 0, + "height": 838608, + "value": 77148, + "confirmations": 2034, + "amount": 0.00077148, + "txid": "ec97c11ff5cb318505ebe20d7aa3c033816824a79f9a49821ffb584ed7d6c78f" + }, + { + "vout": 0, + "height": 837402, + "value": 70501, + "confirmations": 3240, + "amount": 0.00070501, + "txid": "687b875d1dc472aa2fb994c5753c9b9b56e5c6fd1a6de18a92fcb3dc7ba8067e" + }, + { + "vout": 0, + "height": 839021, + "value": 12354, + "confirmations": 1621, + "amount": 0.00012354, + "txid": "23758832e0fc92a7ea303623b8f743219cb8e637e7e7ac9fb6f90641efac9379" + } + ] + }, + "/backend/addresses/
/pubkey": { + "result": "0388ef0905568d425f1ffd4031d93dda4ef0e220c9b5fc4a6cbaf11544c4a5ca49" + }, + "/backend/transactions/": { + "result": { + "txid": "3190047bf2320bdcd0fade655ae49be309519d151330aa478573815229cc0018", + "hash": "417c24d7a5539bc5b8496e26528382ac297a85a1c6b891b220f72712405ec300", + "version": 2, + "size": 195, + "vsize": 113, + "weight": 450, + "locktime": 0, + "vin": [ + { + "txid": "fc940430637d22a3d276bde8f7eb489760265cab642d8392f6017d73df94cd7a", + "vout": 2, + "scriptSig": { + "asm": "", + "hex": "" + }, + "txinwitness": [ + "3045022100e4a30e5c0e0f7a28dfcec566cda00d0775a4207744ed6f223a4234cbed87a8ac02205b2403279ba7d8235ea1e8b6497465b97b46f3b3066a58c326822a9b1c25b4a501", + "020e66cffeb4657b40a89063340cf7066030af3c6ce55744ed3570a7aecaa6b0da" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00838185, + "n": 0, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 25f70b0f1512c1742d3301fe34370894c79127bb OP_EQUALVERIFY OP_CHECKSIG", + "desc": "addr(14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS)#68uhm9u9", + "hex": "76a91425f70b0f1512c1742d3301fe34370894c79127bb88ac", + "address": "14TjwxgnuqgB4HcDcSZk2m7WKwcGVYxRjS", + "type": "pubkeyhash" + } + } + ], + "hex": "020000000001017acd94df737d01f692832d64ab5c26609748ebf7e8bd76d2a3227d63300494fc0200000000ffffffff0129ca0c00000000001976a91425f70b0f1512c1742d3301fe34370894c79127bb88ac02483045022100e4a30e5c0e0f7a28dfcec566cda00d0775a4207744ed6f223a4234cbed87a8ac02205b2403279ba7d8235ea1e8b6497465b97b46f3b3066a58c326822a9b1c25b4a50121020e66cffeb4657b40a89063340cf7066030af3c6ce55744ed3570a7aecaa6b0da00000000", + "blockhash": "000000000000000000020f596ed481076b7754143284b47fc8d32642202e5f76", + "confirmations": 2, + "time": 1713951767, + "blocktime": 1713951767 + } + }, + "/blocks": { + "result": [ + { + "block_index": 840000, + "block_hash": "0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5", + "block_time": 1713571767, + "previous_block_hash": "0000000000000000000172014ba58d66455762add0512355ad651207918494ab", + "difficulty": 86388558925171.02, + "ledger_hash": "b91dd54cfbd3aff07b358a038bf6174ddc06f36bd00cdccf048e8281bcd56224", + "txlist_hash": "b641c3e190b9941fcd5c84a7c07e66c03559ef26dcea892e2db1cf1d8392a4f2", + "messages_hash": "5c5de34009839ee66ebc3097ecd28bd5deee9553966b3ee39e8a08e123ac9adc" + }, + { + "block_index": 839999, + "block_hash": "0000000000000000000172014ba58d66455762add0512355ad651207918494ab", + "block_time": 1713571533, + "previous_block_hash": "00000000000000000001dcce6ce7c8a45872cafd1fb04732b447a14a91832591", + "difficulty": 86388558925171.02, + "ledger_hash": "e2b2e23c2ac1060dafe2395da01fe5907f323b5a644816f45f003411c612ac30", + "txlist_hash": "f33f800ef166e6ef5b3df15a0733f9fd3ebb0b799f39ef1951e6709118b7c0fd", + "messages_hash": "16b7d40543b7b80587f4d98c84fcdfdceb2d1c18abba82c7064c09c2795b7ab2" + } + ] + }, + "/blocks/": { + "result": { + "block_index": 840464, + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_time": 1713852783, + "previous_block_hash": "00000000000000000002db1e5aa19784eec3de949f98ec757e7a7f2fc392079d", + "difficulty": 86388558925171.02, + "ledger_hash": "b3f8cbb50b0705a5c4a8495f8b5128de13a32daebd8ac5e8316a010f0d203584", + "txlist_hash": "84bdc5b9073f775a2b65de7da2b10b89a2235f3501883b0a836e41e68cd00d46", + "messages_hash": "801d961c45a257f85ef0f10a6a8fdf048a520ae4861c0903f26365b3eaaaf540" + } + }, + "/blocks//transactions": { + "result": [ + { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "block_index": 840464, + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_time": 1713852783, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "destination": "", + "btc_amount": 0, + "fee": 56565, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "supported": 1 + } + ] + }, + "/healthz": { + "result": { + "status": "Healthy" + } + }, + "/backend/estimatesmartfee": { + "result": 295443 + }, + "/orders/": { + "result": [ + { + "tx_index": 2724132, + "tx_hash": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776", + "block_index": 840381, + "source": "15L7U55PAsHLEpQkZqz62e3eqWd9AHb2DH", + "give_asset": "PEPECASH", + "give_quantity": 6966600000000, + "give_remaining": 900000000000, + "get_asset": "XCP", + "get_quantity": 11076894000, + "get_remaining": 1431000000, + "expiration": 5000, + "expire_index": 843055, + "fee_required": 0, + "fee_required_remaining": 0, + "fee_provided": 4488, + "fee_provided_remaining": 4488, + "status": "open" + } + ] + }, + "/orders//matches": { + "result": [ + { + "id": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776_5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947", + "tx0_index": 2724132, + "tx0_hash": "23f68fdf934e81144cca31ce8ef69062d553c521321a039166e7ba99aede0776", + "tx0_address": "15L7U55PAsHLEpQkZqz62e3eqWd9AHb2DH", + "tx1_index": 2726591, + "tx1_hash": "5461e6f99a37a7167428b4a720a52052cd9afed43905f818f5d7d4f56abd0947", + "tx1_address": "15e15ua6A3FJqjMevtrWcFSzKn9k6bMQeA", + "forward_asset": "PEPECASH", + "forward_quantity": 6066600000000, + "backward_asset": "XCP", + "backward_quantity": 9645894000, + "tx0_block_index": 838055, + "tx1_block_index": 840381, + "block_index": 840381, + "tx0_expiration": 5000, + "tx1_expiration": 8064, + "match_expire_index": 840401, + "fee_paid": 0, + "status": "completed" + } + ] + }, + "/orders//btcpays": { + "result": [ + { + "tx_index": 2719343, + "tx_hash": "6cfa7f31b43a46e5ad74a9db810bd6cac56235a8ebc73ec63d01b38ea7ea2414", + "block_index": 836188, + "source": "1NfJnJdAdmm2rJCFW54NsAKqqTTMexCNJ3", + "destination": "1BepkwAhEmEuEGF349XjmEUrRvoy9a7Biv", + "btc_amount": 4500000, + "order_match_id": "0a1387df82a8a7e9cec01c52c8fee01f6995c4e39dc5804e1d2bf40d9368f5c5_299b5b648f54eacb839f3487232d49aea373cdd681b706d4cc0b5e0b03688db4", + "status": "valid" + } + ] + }, + "/bets/": { + "result": [ + { + "tx_index": 15106, + "tx_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "block_index": 304063, + "source": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "bet_type": 3, + "deadline": 1401828300, + "wager_quantity": 50000000, + "wager_remaining": 0, + "counterwager_quantity": 50000000, + "counterwager_remaining": 0, + "target_value": 1.0, + "leverage": 5040, + "expiration": 11, + "expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "filled" + } + ] + }, + "/bets//matches": { + "result": [ + { + "id": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed_cb5f888c299a50967d523513daed71636d927e6ef3dbda85feb11ff112ae4330", + "tx0_index": 15106, + "tx0_hash": "5d097b4729cb74d927b4458d365beb811a26fcee7f8712f049ecbe780eb496ed", + "tx0_address": "18ZNyaAcH4HugeofwbrpLoUNiayxJRH65c", + "tx1_index": 15108, + "tx1_hash": "cb5f888c299a50967d523513daed71636d927e6ef3dbda85feb11ff112ae4330", + "tx1_address": "1PTqJmRCMGs4qBEh2APAFSrBv95Uf1hfiD", + "tx0_bet_type": 3, + "tx1_bet_type": 2, + "feed_address": "1QKEpuxEmdp428KEBSDZAKL46noSXWJBkk", + "initial_value": -1, + "deadline": 1401828300, + "target_value": 1.0, + "leverage": 5040, + "forward_quantity": 50000000, + "backward_quantity": 50000000, + "tx0_block_index": 304062, + "tx1_block_index": 304063, + "block_index": 306379, + "tx0_expiration": 11, + "tx1_expiration": 1459, + "match_expire_index": 304073, + "fee_fraction_int": 1000000, + "status": "expired" + } + ] + }, + "/bets//resolutions": { + "result": [ + { + "bet_match_id": "36bbbb7dbd85054dac140a8ad8204eda2ee859545528bd2a9da69ad77c277ace_d70ee4e44f02fe6258ee0c267f33f304a0fc61d4ce424852f58c28967dc1924f", + "bet_match_type_id": 5, + "block_index": 401128, + "winner": "Equal", + "settled": null, + "bull_credit": null, + "bear_credit": null, + "escrow_less_fee": 2000000, + "fee": 0 + } + ] + }, + "/dispensers/": { + "result": [ + { + "tx_index": 2536311, + "tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "asset": "FLOCK", + "give_quantity": 10000000000, + "escrow_quantity": 250000000000, + "satoshirate": 330000, + "status": 0, + "give_remaining": 140000000000, + "oracle_address": null, + "last_status_tx_hash": null, + "origin": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "dispense_count": 2, + "asset_longname": null + } + ] + }, + "/dispensers//dispenses": { + "result": [ + { + "tx_index": 2610745, + "dispense_index": 0, + "tx_hash": "8c95cc6afc8fd466c784fd1c02749c585988999bbc66251b944c443dc31af757", + "block_index": 821450, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "1FKYM1CP9RfttJhNG8HTNQdE2uV3YvwbRB", + "asset": "FLOCK", + "dispense_quantity": 20000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + }, + { + "tx_index": 2726580, + "dispense_index": 0, + "tx_hash": "e7f0f2c9bef7a492b714a5952ec61b283be344419c5bc33f405f9af41ebfa48b", + "block_index": 840322, + "source": "bc1qq735dv8peps2ayr3qwwwdwylq4ddwcgrpyg9r2", + "destination": "bc1qzcdkhnexpjc8wvkyrpyrsn0f5xzcpu877mjmgj", + "asset": "FLOCK", + "dispense_quantity": 90000000000, + "dispenser_tx_hash": "753787004d6e93e71f6e0aa1e0932cc74457d12276d53856424b2e4088cc542a" + } + ] + }, + "/transactions/": { + "result": { + "tx_index": 2726605, + "tx_hash": "876a6cfbd4aa22ba4fa85c2e1953a1c66649468a43a961ad16ea4d5329e3e4c5", + "block_index": 840464, + "block_hash": "00000000000000000001093d4d6b21b80800fff6e5ea15cce6d65066f482cce9", + "block_time": 1713852783, + "source": "178etygrwEeeyQso9we85rUqYZbkiqzL4A", + "destination": "", + "btc_amount": 0, + "fee": 56565, + "data": "16010b9142801429a60000000000000001000000554e4e45474f544941424c45205745204d555354204245434f4d4520554e4e45474f544941424c4520574520415245", + "supported": 1, + "unpacked_data": { + "message_type": "issuance", + "message_type_id": 22, + "message_data": { + "asset_id": 75313533584419238, + "asset": "UNNEGOTIABLE", + "subasset_longname": null, + "quantity": 1, + "divisible": false, + "lock": false, + "reset": false, + "callable": false, + "call_date": 0, + "call_price": 0.0, + "description": "UNNEGOTIABLE WE MUST BECOME UNNEGOTIABLE WE ARE", + "status": "valid" + } + } + } + } +} \ No newline at end of file diff --git a/counterparty-core/tools/genapidoc.py b/counterparty-core/tools/genapidoc.py new file mode 100644 index 0000000000..ee0b315b12 --- /dev/null +++ b/counterparty-core/tools/genapidoc.py @@ -0,0 +1,193 @@ +import json +import os +import sys + +import requests +from counterpartycore import server + +CURR_DIR = os.path.dirname(os.path.realpath(__file__)) +API_DOC_FILE = os.path.join(CURR_DIR, "../../../Documentation/docs/advanced/api-v2/rest.md") +API_BLUEPRINT_FILE = os.path.join(CURR_DIR, "../../apiary.apib") +CACHE_FILE = os.path.join(CURR_DIR, "apicache.json") +API_ROOT = "http://api:api@localhost:4000" +USE_API_CACHE = True + +TARGET_FILE = API_DOC_FILE +TARGET = "docusaurus" + +if len(sys.argv) > 1 and sys.argv[1] == "blueprint": + TARGET_FILE = API_BLUEPRINT_FILE + TARGET = "apiary" + + +def get_example_output(path, args): + url_keys = [] + for key, value in args.items(): + if f"{key}>" in path: + path = path.replace(f"<{key}>", value) + path = path.replace(f"", value) + url_keys.append(key) + for key in url_keys: + args.pop(key) + url = f"{API_ROOT}{path}" + print(f"GET {url}") + response = requests.get(url, params=args) # noqa S113 + return response.json() + + +root_path = "`/`" if TARGET == "docusaurus" else "/" + +GROUPS = [ + "/blocks", + "/transactions", + "/addresses", + "/assets", + "/orders", + "/bets", + "/dispensers", + "/burns", + "/events", + "/mempool", + "/backend", +] + + +def gen_groups_toc(): + toc = "" + for group in GROUPS: + if TARGET == "docusaurus": + toc += f"- [`{group}`](#group-{group[1:]})\n" + else: + toc += f"- [`{group}`](#/reference{group})\n" + return toc + + +if TARGET == "docusaurus": + md = """--- +title: REST API V2 +--- + +""" +else: + md = "" + +md += """FORMAT: 1A +HOST: https://api.counterparty.io + +# Counterparty Core API + +The Counterparty Core API is the recommended (and only supported) way to query the state of a Counterparty node. + +API routes are divided into 11 groups: +""" + +md += gen_groups_toc() + +md += """ +Notes: + +- When the server is not ready, that is to say when all the blocks are not yet parsed, all routes return a 503 error except `/` and those in the `/blocks`, `/transactions` and `/backend` groups which always return a result. + +- All API responses contain the following 3 headers: + + * `X-COUNTERPARTY-HEIGHT` contains the last block parsed by Counterparty + * `X-BACKEND-HEIGHT` contains the last block known to Bitcoin Core + * `X-COUNTERPARTY-READY` contains true if `X-COUNTERPARTY-HEIGHT` >= `X-BACKEND-HEIGHT` - 1 + +- All API responses follow the following format: + + ``` + { + "error": , + "result": + } + ``` + +- Routes in the `/backend` group serve as a proxy to make requests to AddrindexRS. + +# Counterparty API Root [{root_path}] + +### Get Server Info [GET] + +Returns server information and the list of documented routes in JSON format. + ++ Response 200 (application/json) + + ``` + { + "server_ready": true, + "network": "mainnet", + "version": "10.1.1", + "backend_height": 840796, + "counterparty_height": 840796, + "routes": [ + + ] + } + ``` + +""" +md = md.replace("{root_path}", root_path) + +cache = {} +if USE_API_CACHE and os.path.exists(CACHE_FILE): + with open(CACHE_FILE, "r") as f: + cache = json.load(f) + +current_group = None +for path, route in server.routes.ROUTES.items(): + route_group = path.split("/")[1] + if route_group != current_group: + current_group = route_group + md += f"\n## Group {current_group.capitalize()}\n" + blueprint_path = path.replace("<", "{").replace(">", "}").replace("int:", "") + + title = " ".join([part.capitalize() for part in str(route["function"].__name__).split("_")]) + md += f"\n### {title} " + if TARGET == "docusaurus": + md += f"[GET `{blueprint_path}`]\n\n" + else: + for arg in route["args"]: + if f"{{{arg['name']}}}" in blueprint_path: + continue + else: + blueprint_path += f"{{?{arg['name']}}}" + md += f"[GET {blueprint_path}]\n\n" + + md += route["description"] + + example_args = {} + if len(route["args"]) > 0: + md += "\n\n+ Parameters\n" + for arg in route["args"]: + required = "required" if arg["required"] else "optional" + description = arg.get("description", "") + example_arg = "" + if "(e.g. " in description: + desc_arr = description.split("(e.g. ") + description = desc_arr[0].replace("\n", " ") + example_args[arg["name"]] = desc_arr[1].replace(")", "") + example_arg = f": `{example_args[arg['name']]}`" + md += f" + {arg['name']}{example_arg} ({arg['type']}, {required}) - {description}\n" + if not arg["required"]: + md += f" + Default: `{arg.get('default', '')}`\n" + + if example_args != {} or route["args"] == []: + if not USE_API_CACHE or path not in cache: + example_output = get_example_output(path, example_args) + cache[path] = example_output + else: + example_output = cache[path] + example_output_json = json.dumps(example_output, indent=4) + md += "\n+ Response 200 (application/json)\n\n" + md += " ```\n" + for line in example_output_json.split("\n"): + md += f" {line}\n" + md += " ```\n" + +with open(CACHE_FILE, "w") as f: + json.dump(cache, f, indent=4) + +with open(TARGET_FILE, "w") as f: + f.write(md) + print(f"API documentation written to {TARGET_FILE}") diff --git a/counterparty-wallet/counterpartywallet/messages.py b/counterparty-wallet/counterpartywallet/messages.py index f044a41771..84c2cd0291 100755 --- a/counterparty-wallet/counterpartywallet/messages.py +++ b/counterparty-wallet/counterpartywallet/messages.py @@ -350,6 +350,3 @@ def compose(message, args): return compose_transaction(args, message, param_names) else: raise ArgumentError("Invalid message name") - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/counterparty-wallet/counterpartywallet/wallet/__init__.py b/counterparty-wallet/counterpartywallet/wallet/__init__.py index ae2abc3c1f..06de7fda96 100644 --- a/counterparty-wallet/counterpartywallet/wallet/__init__.py +++ b/counterparty-wallet/counterpartywallet/wallet/__init__.py @@ -20,7 +20,7 @@ class LockedWalletError(WalletError): def wallet(): - return sys.modules[f"counterpartycli.wallet.{config.WALLET_NAME}"] + return sys.modules[f"counterpartywallet.wallet.{config.WALLET_NAME}"] def get_wallet_addresses(): diff --git a/release-notes/release-notes-v10.1.2.md b/release-notes/release-notes-v10.1.2.md index fadded4993..d54c75b652 100644 --- a/release-notes/release-notes-v10.1.2.md +++ b/release-notes/release-notes-v10.1.2.md @@ -4,6 +4,11 @@ # Upgrading +To continue using the old API you must: +- start `counterparty-server` with the flag `----enable-api-v1` +- replace port `4100` with port `4000` for mainnet and port `14000` with port `14100` +- prefix all endpoints with `/v1/` +To easily migrate to the new API, an equivalence table is available in the documentation # ChangeLog @@ -11,6 +16,7 @@ * Fix logging of some raw tracebacks (#1715) ## Codebase +* New REST API ## Command-Line Interface